Skip to content

Commit 9ad80cc

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

14 files changed

+608
-2
lines changed

extension-zips.js

+1-1
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

+1-1
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

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
icons/
2+
popups/
3+
*.html

packages/shell-firefox/copy.sh

+7
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

+58
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

+18
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

+55
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+
}
+125
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+
})
+100
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)