Skip to content

Commit 9ad80cc

Browse files
committed
feat: separate firefox shell with manifest v2
1 parent aa6ffe0 commit 9ad80cc

File tree

14 files changed

+608
-2
lines changed

14 files changed

+608
-2
lines changed

extension-zips.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function bytesToSize(bytes) {
3131

3232
(async () => {
3333
await writeZip('devtools-chrome.zip', 'shell-chrome')
34-
await writeZip('devtools-firefox.zip', 'shell-chrome')
34+
await writeZip('devtools-firefox.zip', 'shell-firefox')
3535

3636
async function writeZip(fileName, packageDir) {
3737
// create a file to stream archive data to.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"build": "lerna run build",
2929
"build:watch": "lerna run build --scope @vue-devtools/app-backend* --scope @vue-devtools/shared-* --scope @vue/devtools-api && lerna run build:watch --stream --no-sort --concurrency 99",
3030
"lint": "eslint .",
31-
"run:firefox": "web-ext run -s packages/shell-chrome -a dist -i src",
31+
"run:firefox": "web-ext run -s packages/shell-firefox -a dist -i src -u http://localhost:8090/target.html",
3232
"zip": "node ./extension-zips.js",
3333
"sign:firefox": "node ./sign-firefox.js",
3434
"release": "npm run test && node release.js && npm run build && npm run zip && npm run pub",

packages/shell-firefox/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
icons/
2+
popups/
3+
*.html

packages/shell-firefox/copy.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/bash
2+
3+
rm -rf icons
4+
5+
cp -r ../shell-chrome/icons .
6+
cp -r ../shell-chrome/popups .
7+
cp ../shell-chrome/*.html .

packages/shell-firefox/manifest.json

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"name": "Vue.js devtools",
3+
"version": "6.5.1",
4+
"version_name": "6.5.1",
5+
"description": "Browser DevTools extension for debugging Vue.js applications.",
6+
"manifest_version": 2,
7+
"icons": {
8+
"16": "icons/16.png",
9+
"48": "icons/48.png",
10+
"128": "icons/128.png"
11+
},
12+
"browser_action": {
13+
"default_icon": {
14+
"16": "icons/16-gray.png",
15+
"48": "icons/48-gray.png",
16+
"128": "icons/128-gray.png"
17+
},
18+
"default_title": "Vue Devtools",
19+
"default_popup": "popups/not-found.html"
20+
},
21+
"web_accessible_resources": [
22+
"devtools.html",
23+
"devtools-background.html",
24+
"build/backend.js"
25+
],
26+
"devtools_page": "devtools-background.html",
27+
"background": {
28+
"scripts": [
29+
"build/background.js"
30+
],
31+
"persistent": true
32+
},
33+
"permissions": [
34+
"<all_urls>",
35+
"storage"
36+
],
37+
"content_scripts": [
38+
{
39+
"matches": [
40+
"<all_urls>"
41+
],
42+
"js": [
43+
"build/hook.js"
44+
],
45+
"run_at": "document_start"
46+
},
47+
{
48+
"matches": [
49+
"<all_urls>"
50+
],
51+
"js": [
52+
"build/detector.js"
53+
],
54+
"run_at": "document_idle"
55+
}
56+
],
57+
"content_security_policy": "script-src 'self'; object-src 'self'"
58+
}

packages/shell-firefox/package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "@vue-devtools/shell-firefox",
3+
"version": "0.0.0",
4+
"scripts": {
5+
"build": "rimraf ./build && ./copy.sh && cross-env NODE_ENV=production webpack --progress"
6+
},
7+
"dependencies": {
8+
"@vue-devtools/app-backend-core": "^0.0.0",
9+
"@vue-devtools/app-frontend": "^0.0.0",
10+
"@vue-devtools/shared-utils": "^0.0.0"
11+
},
12+
"devDependencies": {
13+
"@vue-devtools/build-tools": "^0.0.0",
14+
"rimraf": "^3.0.2",
15+
"webpack": "^5.35.1",
16+
"webpack-cli": "^4.6.0"
17+
}
18+
}

packages/shell-firefox/src/backend.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// this is injected to the app page when the panel is activated.
2+
3+
import { initBackend } from '@back'
4+
import { Bridge } from '@vue-devtools/shared-utils'
5+
6+
window.addEventListener('message', handshake)
7+
8+
function sendListening() {
9+
window.postMessage({
10+
source: 'vue-devtools-backend-injection',
11+
payload: 'listening',
12+
}, '*')
13+
}
14+
sendListening()
15+
16+
function handshake(e) {
17+
if (e.data.source === 'vue-devtools-proxy' && e.data.payload === 'init') {
18+
window.removeEventListener('message', handshake)
19+
20+
let listeners = []
21+
const bridge = new Bridge({
22+
listen(fn) {
23+
const listener = (evt) => {
24+
if (evt.data.source === 'vue-devtools-proxy' && evt.data.payload) {
25+
fn(evt.data.payload)
26+
}
27+
}
28+
window.addEventListener('message', listener)
29+
listeners.push(listener)
30+
},
31+
send(data) {
32+
// if (process.env.NODE_ENV !== 'production') {
33+
// console.log('[chrome] backend -> devtools', data)
34+
// }
35+
window.postMessage({
36+
source: 'vue-devtools-backend',
37+
payload: data,
38+
}, '*')
39+
},
40+
})
41+
42+
bridge.on('shutdown', () => {
43+
listeners.forEach((l) => {
44+
window.removeEventListener('message', l)
45+
})
46+
listeners = []
47+
window.addEventListener('message', handshake)
48+
})
49+
50+
initBackend(bridge)
51+
}
52+
else {
53+
sendListening()
54+
}
55+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// the background script runs all the time and serves as a central message
2+
// hub for each vue devtools (panel + proxy + backend) instance.
3+
4+
const ports = {}
5+
6+
chrome.runtime.onConnect.addListener((port) => {
7+
let tab
8+
let name
9+
if (isNumeric(port.name)) {
10+
tab = port.name
11+
name = 'devtools'
12+
installProxy(+port.name)
13+
}
14+
else {
15+
tab = port.sender.tab.id
16+
name = 'backend'
17+
}
18+
19+
if (!ports[tab]) {
20+
ports[tab] = {
21+
devtools: null,
22+
backend: null,
23+
}
24+
}
25+
ports[tab][name] = port
26+
27+
if (ports[tab].devtools && ports[tab].backend) {
28+
doublePipe(tab, ports[tab].devtools, ports[tab].backend)
29+
}
30+
})
31+
32+
function isNumeric(str) {
33+
return `${+str}` === str
34+
}
35+
36+
function installProxy(tabId) {
37+
chrome.tabs.executeScript(tabId, {
38+
file: '/build/proxy.js',
39+
}, (res) => {
40+
if (!res) {
41+
ports[tabId].devtools.postMessage('proxy-fail')
42+
}
43+
else {
44+
if (process.env.NODE_ENV !== 'production') {
45+
// eslint-disable-next-line no-console
46+
console.log(`injected proxy to tab ${tabId}`)
47+
}
48+
}
49+
})
50+
}
51+
52+
function doublePipe(id, one, two) {
53+
one.onMessage.addListener(lOne)
54+
function lOne(message) {
55+
if (message.event === 'log') {
56+
// eslint-disable-next-line no-console
57+
return console.log(`tab ${id}`, message.payload)
58+
}
59+
if (process.env.NODE_ENV !== 'production') {
60+
// eslint-disable-next-line no-console
61+
console.log('%cdevtools -> backend', 'color:#888;', message)
62+
}
63+
two.postMessage(message)
64+
}
65+
two.onMessage.addListener(lTwo)
66+
function lTwo(message) {
67+
if (message.event === 'log') {
68+
// eslint-disable-next-line no-console
69+
return console.log(`tab ${id}`, message.payload)
70+
}
71+
if (process.env.NODE_ENV !== 'production') {
72+
// eslint-disable-next-line no-console
73+
console.log('%cbackend -> devtools', 'color:#888;', message)
74+
}
75+
one.postMessage(message)
76+
}
77+
function shutdown() {
78+
if (process.env.NODE_ENV !== 'production') {
79+
// eslint-disable-next-line no-console
80+
console.log(`tab ${id} disconnected.`)
81+
}
82+
one.onMessage.removeListener(lOne)
83+
two.onMessage.removeListener(lTwo)
84+
one.disconnect()
85+
two.disconnect()
86+
ports[id] = null
87+
}
88+
one.onDisconnect.addListener(shutdown)
89+
two.onDisconnect.addListener(shutdown)
90+
if (process.env.NODE_ENV !== 'production') {
91+
// eslint-disable-next-line no-console
92+
console.log(`tab ${id} connected.`)
93+
}
94+
}
95+
96+
chrome.runtime.onMessage.addListener((req, sender) => {
97+
if (sender.tab && req.vueDetected) {
98+
const suffix = req.nuxtDetected ? '.nuxt' : ''
99+
100+
chrome.browserAction.setIcon({
101+
tabId: sender.tab.id,
102+
path: {
103+
16: `icons/16${suffix}.png`,
104+
48: `icons/48${suffix}.png`,
105+
128: `icons/128${suffix}.png`,
106+
},
107+
})
108+
chrome.browserAction.setPopup({
109+
tabId: sender.tab.id,
110+
popup: req.devtoolsEnabled ? `popups/enabled${suffix}.html` : `popups/disabled${suffix}.html`,
111+
})
112+
}
113+
114+
if (req.action === 'vue-take-screenshot' && sender.envType === 'devtools_child') {
115+
browser.tabs.captureVisibleTab({
116+
format: 'png',
117+
}).then((dataUrl) => {
118+
browser.runtime.sendMessage({
119+
action: 'vue-screenshot-result',
120+
id: req.id,
121+
dataUrl,
122+
})
123+
})
124+
}
125+
})
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { installToast } from '@back/toast'
2+
import { isFirefox } from '@vue-devtools/shared-utils'
3+
4+
window.addEventListener('message', (e) => {
5+
if (e.source === window && e.data.vueDetected) {
6+
chrome.runtime.sendMessage(e.data)
7+
}
8+
})
9+
10+
function detect(win) {
11+
let delay = 1000
12+
let detectRemainingTries = 10
13+
14+
function runDetect() {
15+
// Method 1: Check Nuxt
16+
const nuxtDetected = !!(window.__NUXT__ || window.$nuxt)
17+
18+
if (nuxtDetected) {
19+
let Vue
20+
21+
if (window.$nuxt) {
22+
Vue = window.$nuxt.$root && window.$nuxt.$root.constructor
23+
}
24+
25+
win.postMessage({
26+
devtoolsEnabled: (/* Vue 2 */ Vue && Vue.config.devtools)
27+
|| (/* Vue 3.2.14+ */ window.__VUE_DEVTOOLS_GLOBAL_HOOK__ && window.__VUE_DEVTOOLS_GLOBAL_HOOK__.enabled),
28+
vueDetected: true,
29+
nuxtDetected: true,
30+
}, '*')
31+
32+
return
33+
}
34+
35+
// Method 2: Check Vue 3
36+
const vueDetected = !!(window.__VUE__)
37+
if (vueDetected) {
38+
win.postMessage({
39+
devtoolsEnabled: /* Vue 3.2.14+ */ window.__VUE_DEVTOOLS_GLOBAL_HOOK__ && window.__VUE_DEVTOOLS_GLOBAL_HOOK__.enabled,
40+
vueDetected: true,
41+
}, '*')
42+
43+
return
44+
}
45+
46+
// Method 3: Scan all elements inside document
47+
const all = document.querySelectorAll('*')
48+
let el
49+
for (let i = 0; i < all.length; i++) {
50+
if (all[i].__vue__) {
51+
el = all[i]
52+
break
53+
}
54+
}
55+
if (el) {
56+
let Vue = Object.getPrototypeOf(el.__vue__).constructor
57+
while (Vue.super) {
58+
Vue = Vue.super
59+
}
60+
win.postMessage({
61+
devtoolsEnabled: Vue.config.devtools,
62+
vueDetected: true,
63+
}, '*')
64+
return
65+
}
66+
67+
if (detectRemainingTries > 0) {
68+
detectRemainingTries--
69+
setTimeout(() => {
70+
runDetect()
71+
}, delay)
72+
delay *= 5
73+
}
74+
}
75+
76+
setTimeout(() => {
77+
runDetect()
78+
}, 100)
79+
}
80+
81+
// inject the hook
82+
if (document instanceof HTMLDocument) {
83+
installScript(detect)
84+
installScript(installToast)
85+
}
86+
87+
function installScript(fn) {
88+
const source = `;(${fn.toString()})(window)`
89+
90+
if (isFirefox) {
91+
// eslint-disable-next-line no-eval
92+
window.eval(source) // in Firefox, this evaluates on the content window
93+
}
94+
else {
95+
const script = document.createElement('script')
96+
script.textContent = source
97+
document.documentElement.appendChild(script)
98+
script.parentNode.removeChild(script)
99+
}
100+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy