Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/react-devtools-extensions/chrome/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"service_worker": "build/background.js"
},
"permissions": [
"storage",
"scripting"
"scripting",
"tabs"
],
"host_permissions": [
"<all_urls>"
Expand Down
4 changes: 2 additions & 2 deletions packages/react-devtools-extensions/edge/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"service_worker": "build/background.js"
},
"permissions": [
"storage",
"scripting"
"scripting",
"tabs"
],
"host_permissions": [
"<all_urls>"
Expand Down
39 changes: 24 additions & 15 deletions packages/react-devtools-extensions/firefox/manifest.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"manifest_version": 2,
"manifest_version": 3,
"name": "React Developer Tools",
"description": "Adds React debugging tools to the Firefox Developer Tools.",
"version": "5.3.1",
"applications": {
"browser_specific_settings": {
"gecko": {
"id": "@react-devtools",
"strict_min_version": "102.0"
"strict_min_version": "128.0"
}
},
"icons": {
Expand All @@ -15,35 +15,44 @@
"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",
"48": "icons/48-disabled.png",
"128": "icons/128-disabled.png"
},
"default_popup": "popups/disabled.html",
"browser_style": true
"default_popup": "popups/disabled.html"
},
"devtools_page": "main.html",
"content_security_policy": "script-src 'self' 'unsafe-eval' blob:; object-src 'self'",
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
},
"web_accessible_resources": [
"main.html",
"panel.html",
"build/*.js"
{
"resources": [
"main.html",
"panel.html",
"build/*.js",
"build/*.js.map"
],
"matches": [
"<all_urls>"
],
"extension_ids": []
}
],
"background": {
"scripts": [
"build/background.js"
]
},
"permissions": [
"file:///*",
"http://*/*",
"https://*/*",
"clipboardWrite",
"scripting",
"devtools"
"tabs"
],
"host_permissions": [
"<all_urls>"
],
"content_scripts": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,58 +1,39 @@
/* global chrome */

// Firefox doesn't support ExecutionWorld.MAIN yet
// equivalent logic for Firefox is in prepareInjection.js
const contentScriptsToInject = __IS_FIREFOX__
? [
{
id: '@react-devtools/proxy',
js: ['build/proxy.js'],
matches: ['<all_urls>'],
persistAcrossSessions: true,
runAt: 'document_end',
},
{
id: '@react-devtools/file-fetcher',
js: ['build/fileFetcher.js'],
matches: ['<all_urls>'],
persistAcrossSessions: true,
runAt: 'document_end',
},
]
: [
{
id: '@react-devtools/proxy',
js: ['build/proxy.js'],
matches: ['<all_urls>'],
persistAcrossSessions: true,
runAt: 'document_end',
world: chrome.scripting.ExecutionWorld.ISOLATED,
},
{
id: '@react-devtools/file-fetcher',
js: ['build/fileFetcher.js'],
matches: ['<all_urls>'],
persistAcrossSessions: true,
runAt: 'document_end',
world: chrome.scripting.ExecutionWorld.ISOLATED,
},
{
id: '@react-devtools/hook',
js: ['build/installHook.js'],
matches: ['<all_urls>'],
persistAcrossSessions: true,
runAt: 'document_start',
world: chrome.scripting.ExecutionWorld.MAIN,
},
{
id: '@react-devtools/renderer',
js: ['build/renderer.js'],
matches: ['<all_urls>'],
persistAcrossSessions: true,
runAt: 'document_start',
world: chrome.scripting.ExecutionWorld.MAIN,
},
];
const contentScriptsToInject = [
{
id: '@react-devtools/proxy',
js: ['build/proxy.js'],
matches: ['<all_urls>'],
persistAcrossSessions: true,
runAt: 'document_end',
world: chrome.scripting.ExecutionWorld.ISOLATED,
},
{
id: '@react-devtools/file-fetcher',
js: ['build/fileFetcher.js'],
matches: ['<all_urls>'],
persistAcrossSessions: true,
runAt: 'document_end',
world: chrome.scripting.ExecutionWorld.ISOLATED,
},
{
id: '@react-devtools/hook',
js: ['build/installHook.js'],
matches: ['<all_urls>'],
persistAcrossSessions: true,
runAt: 'document_start',
world: chrome.scripting.ExecutionWorld.MAIN,
},
{
id: '@react-devtools/renderer',
js: ['build/renderer.js'],
matches: ['<all_urls>'],
persistAcrossSessions: true,
runAt: 'document_start',
world: chrome.scripting.ExecutionWorld.MAIN,
},
];

async function dynamicallyInjectContentScripts() {
try {
Expand All @@ -61,9 +42,6 @@ async function dynamicallyInjectContentScripts() {
// This fixes registering proxy content script in incognito mode
await chrome.scripting.unregisterContentScripts();

// equivalent logic for Firefox is in prepareInjection.js
// Manifest V3 method of injecting content script
// TODO(hoxyq): migrate Firefox to V3 manifests
// 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
Expand Down
39 changes: 0 additions & 39 deletions packages/react-devtools-extensions/src/background/executeScript.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,5 @@
/* global chrome */

// Firefox doesn't support ExecutionWorld.MAIN yet
// https://bugzilla.mozilla.org/show_bug.cgi?id=1736575
function executeScriptForFirefoxInMainWorld({target, files}) {
return chrome.scripting.executeScript({
target,
func: fileNames => {
function injectScriptSync(src) {
let code = '';
const request = new XMLHttpRequest();
request.addEventListener('load', function () {
code = this.responseText;
});
request.open('GET', src, false);
request.send();

const script = document.createElement('script');
script.textContent = code;

// This script runs before the <head> element is created,
// so we add the script to <html> instead.
if (document.documentElement) {
document.documentElement.appendChild(script);
}

if (script.parentNode) {
script.parentNode.removeChild(script);
}
}

fileNames.forEach(file => injectScriptSync(chrome.runtime.getURL(file)));
},
args: [files],
});
}

export function executeScriptInIsolatedWorld({target, files}) {
return chrome.scripting.executeScript({
target,
Expand All @@ -44,10 +9,6 @@ export function executeScriptInIsolatedWorld({target, files}) {
}

export function executeScriptInMainWorld({target, files}) {
if (__IS_FIREFOX__) {
return executeScriptForFirefoxInMainWorld({target, files});
}

return chrome.scripting.executeScript({
target,
files,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
'use strict';

function setExtensionIconAndPopup(reactBuildType, tabId) {
const action = __IS_FIREFOX__ ? chrome.browserAction : chrome.action;

action.setIcon({
chrome.action.setIcon({
tabId,
path: {
'16': chrome.runtime.getURL(`icons/16-${reactBuildType}.png`),
Expand All @@ -15,7 +13,7 @@ function setExtensionIconAndPopup(reactBuildType, tabId) {
},
});

action.setPopup({
chrome.action.setPopup({
tabId,
popup: chrome.runtime.getURL(`popups/${reactBuildType}.html`),
});
Expand Down
34 changes: 11 additions & 23 deletions packages/react-devtools-extensions/src/background/tabsManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
import setExtensionIconAndPopup from './setExtensionIconAndPopup';

function isRestrictedBrowserPage(url) {
return !url || new URL(url).protocol === 'chrome:';
if (!url) {
return true;
}

const urlProtocol = new URL(url).protocol;
return urlProtocol === 'chrome:' || urlProtocol === 'about:';
}

function checkAndHandleRestrictedPageIfSo(tab) {
Expand All @@ -14,30 +19,13 @@ function checkAndHandleRestrictedPageIfSo(tab) {
}
}

// update popup page of any existing open tabs, if they are restricted browser pages.
// we can't update for any other types (prod,dev,outdated etc)
// as the content script needs to be injected at document_start itself for those kinds of detection
// TODO: Show a different popup page(to reload current page probably) for old tabs, opened before the extension is installed
if (__IS_CHROME__ || __IS_EDGE__) {
chrome.tabs.query({}, tabs => tabs.forEach(checkAndHandleRestrictedPageIfSo));
chrome.tabs.onCreated.addListener((tabId, changeInfo, tab) =>
checkAndHandleRestrictedPageIfSo(tab),
);
}
// Update popup page of any existing open tabs, if they are restricted browser pages
chrome.tabs.query({}, tabs => tabs.forEach(checkAndHandleRestrictedPageIfSo));
chrome.tabs.onCreated.addListener(tab => checkAndHandleRestrictedPageIfSo(tab));

// Listen to URL changes on the active tab and update the DevTools icon.
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (__IS_FIREFOX__) {
// We don't properly detect protected URLs in Firefox at the moment.
// However, we can reset the DevTools icon to its loading state when the URL changes.
// It will be updated to the correct icon by the onMessage callback below.
if (tab.active && changeInfo.status === 'loading') {
setExtensionIconAndPopup('disabled', tabId);
}
} else {
// Don't reset the icon to the loading state for Chrome or Edge.
// The onUpdated callback fires more frequently for these browsers,
// often after onMessage has been called.
checkAndHandleRestrictedPageIfSo(tab);
if (changeInfo.url && isRestrictedBrowserPage(changeInfo.url)) {
setExtensionIconAndPopup('restricted', tabId);
}
});
Original file line number Diff line number Diff line change
@@ -1,31 +1,5 @@
/* global chrome */

import nullthrows from 'nullthrows';

// We run scripts on the page via the service worker (background/index.js) for
// Manifest V3 extensions (Chrome & Edge).
// We need to inject this code for Firefox only because it does not support ExecutionWorld.MAIN
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/ExecutionWorld
// In this content script we have access to DOM, but don't have access to the webpage's window,
// so we inject this inline script tag into the webpage (allowed in Manifest V2).
function injectScriptSync(src) {
let code = '';
const request = new XMLHttpRequest();
request.addEventListener('load', function () {
code = this.responseText;
});
request.open('GET', src, false);
request.send();

const script = document.createElement('script');
script.textContent = code;

// This script runs before the <head> element is created,
// so we add the script to <html> instead.
nullthrows(document.documentElement).appendChild(script);
nullthrows(script.parentNode).removeChild(script);
}

let lastSentDevToolsHookMessage;

// We want to detect when a renderer attaches, and notify the "background page"
Expand Down Expand Up @@ -60,17 +34,3 @@ window.addEventListener('pageshow', function ({target}) {

chrome.runtime.sendMessage(lastSentDevToolsHookMessage);
});

if (__IS_FIREFOX__) {
injectScriptSync(chrome.runtime.getURL('build/renderer.js'));

// Inject a __REACT_DEVTOOLS_GLOBAL_HOOK__ global for React to interact with.
// Only do this for HTML documents though, to avoid e.g. breaking syntax highlighting for XML docs.
switch (document.contentType) {
case 'text/html':
case 'application/xhtml+xml': {
injectScriptSync(chrome.runtime.getURL('build/installHook.js'));
break;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,8 @@ import {registerDevToolsEventLogger} from 'react-devtools-shared/src/registerDev

function registerEventsLogger() {
registerDevToolsEventLogger('extension', async () => {
// TODO: after we upgrade to Firefox Manifest V3, chrome.tabs.query returns a Promise without the callback.
return new Promise(resolve => {
chrome.tabs.query({active: true}, tabs => {
resolve({
page_url: tabs[0]?.url,
});
});
});
const tabs = await chrome.tabs.query({active: true});
return {page_url: tabs[0]?.url};
});
}

Expand Down
2 changes: 1 addition & 1 deletion packages/react-devtools-shared/babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const firefoxManifest = require('../react-devtools-extensions/firefox/manifest.j

const minChromeVersion = parseInt(chromeManifest.minimum_chrome_version, 10);
const minFirefoxVersion = parseInt(
firefoxManifest.applications.gecko.strict_min_version,
firefoxManifest.browser_specific_settings.gecko.strict_min_version,
10,
);
validateVersion(minChromeVersion);
Expand Down