Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix[react-devtools/extensions]: fixed tabs API calls and displaying restricted access popup #30825

Merged
merged 3 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 2 additions & 1 deletion packages/react-devtools-extensions/chrome/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
},
"permissions": [
"storage",
"scripting"
"scripting",
"tabs"
],
"host_permissions": [
"<all_urls>"
Expand Down
3 changes: 2 additions & 1 deletion packages/react-devtools-extensions/edge/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
},
"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
Loading