Skip to content

Commit

Permalink
[DevTools] upgrade to Manifest V3 (#25145)
Browse files Browse the repository at this point in the history
## Summary

resolves #24522

To upgrade to Manifest V3, one of the biggest issue is that we are no
longer allowed to add a script element with code in textContent so that
it would run synchronously. It's necessary for us because we need to
inject a global hook for react reconciler to detect whether devtools
exist.
To do that, we'll leverage a new API
`chrome.scripting.registerContentScripts` in V3. Particularly, we rely
on the "world" option (added in Chrome v102
[commit](https://chromium.googlesource.com/chromium/src/+/e5ad3451c17b21341b0b9019b074801c44c92c9f))
to run it in the "main world" on the page.

This PR also renames a few content script files so that it's easier to
tell them apart from other extension scripts and understand the purpose
of each of them.

Manifest V3 is not yet ready for Firefox, so we need to keep some code
for compatibility.

## How did you test this change?

`yarn build:chrome && yarn test:chrome`
`yarn build:edge && yarn test:edge`
`yarn build:firefox && yarn test:firefox`
  • Loading branch information
mondaychen committed Oct 22, 2022
1 parent 973b90b commit 6dbccb9
Show file tree
Hide file tree
Showing 14 changed files with 268 additions and 152 deletions.
43 changes: 27 additions & 16 deletions packages/react-devtools-extensions/chrome/manifest.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"manifest_version": 2,
"manifest_version": 3,
"name": "React Developer Tools",
"description": "Adds React debugging tools to the Chrome Developer Tools.",
"version": "4.26.1",
"version_name": "4.26.1",
"minimum_chrome_version": "60",
"minimum_chrome_version": "88",
"icons": {
"16": "icons/16-production.png",
"32": "icons/32-production.png",
"48": "icons/48-production.png",
"128": "icons/128-production.png"
},
"browser_action": {
"action": {
"default_icon": {
"16": "icons/16-disabled.png",
"32": "icons/32-disabled.png",
Expand All @@ -21,31 +21,42 @@
"default_popup": "popups/disabled.html"
},
"devtools_page": "main.html",
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
},
"web_accessible_resources": [
"main.html",
"panel.html",
"build/react_devtools_backend.js",
"build/renderer.js"
{
"resources": [
"main.html",
"panel.html",
"build/react_devtools_backend.js",
"build/proxy.js",
"build/renderer.js",
"build/installHook.js"
],
"matches": [
"<all_urls>"
],
"extension_ids": []
}
],
"background": {
"scripts": [
"build/background.js"
],
"persistent": false
"service_worker": "build/background.js"
},
"permissions": [
"file:///*",
"http://*/*",
"https://*/*"
"storage",
"scripting"
],
"host_permissions": [
"<all_urls>"
],
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"build/injectGlobalHook.js"
"build/prepareInjection.js"
],
"run_at": "document_start"
}
Expand Down
43 changes: 27 additions & 16 deletions packages/react-devtools-extensions/edge/manifest.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"manifest_version": 2,
"manifest_version": 3,
"name": "React Developer Tools",
"description": "Adds React debugging tools to the Microsoft Edge Developer Tools.",
"version": "4.26.1",
"version_name": "4.26.1",
"minimum_chrome_version": "60",
"minimum_chrome_version": "88",
"icons": {
"16": "icons/16-production.png",
"32": "icons/32-production.png",
"48": "icons/48-production.png",
"128": "icons/128-production.png"
},
"browser_action": {
"action": {
"default_icon": {
"16": "icons/16-disabled.png",
"32": "icons/32-disabled.png",
Expand All @@ -21,31 +21,42 @@
"default_popup": "popups/disabled.html"
},
"devtools_page": "main.html",
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
},
"web_accessible_resources": [
"main.html",
"panel.html",
"build/react_devtools_backend.js",
"build/renderer.js"
{
"resources": [
"main.html",
"panel.html",
"build/react_devtools_backend.js",
"build/proxy.js",
"build/renderer.js",
"build/installHook.js"
],
"matches": [
"<all_urls>"
],
"extension_ids": []
}
],
"background": {
"scripts": [
"build/background.js"
],
"persistent": false
"service_worker": "build/background.js"
},
"permissions": [
"file:///*",
"http://*/*",
"https://*/*"
"storage",
"scripting"
],
"host_permissions": [
"<all_urls>"
],
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"build/injectGlobalHook.js"
"build/prepareInjection.js"
],
"run_at": "document_start"
}
Expand Down
6 changes: 4 additions & 2 deletions packages/react-devtools-extensions/firefox/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
"main.html",
"panel.html",
"build/react_devtools_backend.js",
"build/renderer.js"
"build/proxy.js",
"build/renderer.js",
"build/installHook.js"
],
"background": {
"scripts": [
Expand All @@ -50,7 +52,7 @@
"<all_urls>"
],
"js": [
"build/injectGlobalHook.js"
"build/prepareInjection.js"
],
"run_at": "document_start"
}
Expand Down
61 changes: 43 additions & 18 deletions packages/react-devtools-extensions/src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,41 @@

'use strict';

import {IS_FIREFOX} from './utils';

const ports = {};

const IS_FIREFOX = navigator.userAgent.indexOf('Firefox') >= 0;
if (!IS_FIREFOX) {
// Manifest V3 method of injecting content scripts (not yet supported in Firefox)
// Note: the "world" option in registerContentScripts is only available in Chrome v102+
// It's critical since it allows us to directly run scripts on the "main" world on the page
// "document_start" allows it to run before the page's scripts
// so the hook can be detected by react reconciler
chrome.scripting.registerContentScripts([
{
id: 'hook',
matches: ['<all_urls>'],
js: ['build/installHook.js'],
runAt: 'document_start',
world: chrome.scripting.ExecutionWorld.MAIN,
},
{
id: 'renderer',
matches: ['<all_urls>'],
js: ['build/renderer.js'],
runAt: 'document_start',
world: chrome.scripting.ExecutionWorld.MAIN,
},
]);
}

chrome.runtime.onConnect.addListener(function(port) {
let tab = null;
let name = null;
if (isNumeric(port.name)) {
tab = port.name;
name = 'devtools';
installContentScript(+port.name);
installProxy(+port.name);
} else {
tab = port.sender.tab.id;
name = 'content-script';
Expand All @@ -35,12 +59,15 @@ function isNumeric(str: string): boolean {
return +str + '' === str;
}

function installContentScript(tabId: number) {
chrome.tabs.executeScript(
tabId,
{file: '/build/contentScript.js'},
function() {},
);
function installProxy(tabId: number) {
if (IS_FIREFOX) {
chrome.tabs.executeScript(tabId, {file: '/build/proxy.js'}, function() {});
} else {
chrome.scripting.executeScript({
target: {tabId: tabId},
files: ['/build/proxy.js'],
});
}
}

function doublePipe(one, two) {
Expand All @@ -63,18 +90,19 @@ function doublePipe(one, two) {
}

function setIconAndPopup(reactBuildType, tabId) {
chrome.browserAction.setIcon({
const action = IS_FIREFOX ? chrome.browserAction : chrome.action;
action.setIcon({
tabId: tabId,
path: {
'16': 'icons/16-' + reactBuildType + '.png',
'32': 'icons/32-' + reactBuildType + '.png',
'48': 'icons/48-' + reactBuildType + '.png',
'128': 'icons/128-' + reactBuildType + '.png',
'16': chrome.runtime.getURL(`icons/16-${reactBuildType}.png`),
'32': chrome.runtime.getURL(`icons/32-${reactBuildType}.png`),
'48': chrome.runtime.getURL(`icons/48-${reactBuildType}.png`),
'128': chrome.runtime.getURL(`icons/128-${reactBuildType}.png`),
},
});
chrome.browserAction.setPopup({
action.setPopup({
tabId: tabId,
popup: 'popups/' + reactBuildType + '.html',
popup: chrome.runtime.getURL(`popups/${reactBuildType}.html`),
});
}

Expand Down Expand Up @@ -123,9 +151,6 @@ chrome.runtime.onMessage.addListener((request, sender) => {
// This is sent from the hook content script.
// It tells us a renderer has attached.
if (request.hasDetectedReact) {
// We use browserAction instead of pageAction because this lets us
// display a custom default popup when React is *not* detected.
// It is specified in the manifest.
setIconAndPopup(request.reactBuildType, id);
} else {
switch (request.payload?.type) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {installHook} from 'react-devtools-shared/src/hook';

// avoid double execution
if (!window.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) {
installHook(window);

// detect react
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('renderer', function({
reactBuildType,
}) {
window.postMessage(
{
source: 'react-devtools-detector',
reactBuildType,
},
'*',
);
});

// save native values
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeObjectCreate = Object.create;
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeMap = Map;
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeWeakMap = WeakMap;
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeSet = Set;
}
Loading

0 comments on commit 6dbccb9

Please sign in to comment.