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

refactor[devtools/extension]: refactored messaging logic across different parts of the extension #27417

Merged
merged 1 commit into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ const contentScriptsToInject = IS_FIREFOX
persistAcrossSessions: true,
runAt: 'document_end',
},
{
id: '@react-devtools/file-fetcher',
js: ['build/fileFetcher.js'],
matches: ['<all_urls>'],
persistAcrossSessions: true,
runAt: 'document_end',
},
]
: [
{
Expand All @@ -23,6 +30,14 @@ const contentScriptsToInject = IS_FIREFOX
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'],
Expand Down
58 changes: 58 additions & 0 deletions packages/react-devtools-extensions/src/background/executeScript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* global chrome */

import {IS_FIREFOX} from '../utils';

// 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,
files,
world: chrome.scripting.ExecutionWorld.ISOLATED,
});
}

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

return chrome.scripting.executeScript({
target,
files,
world: chrome.scripting.ExecutionWorld.MAIN,
});
}
84 changes: 21 additions & 63 deletions packages/react-devtools-extensions/src/background/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

'use strict';

import {IS_FIREFOX, EXTENSION_CONTAINED_VERSIONS} from '../utils';

import './dynamicallyInjectContentScripts';
import './tabsManager';
import setExtensionIconAndPopup from './setExtensionIconAndPopup';

import {
handleDevToolsPageMessage,
handleBackendManagerMessage,
handleReactDevToolsHookMessage,
handleFetchResourceContentScriptMessage,
} from './messageHandlers';

/*
{
Expand Down Expand Up @@ -173,67 +177,21 @@ function connectExtensionAndProxyPorts(extensionPort, proxyPort, tabId) {
}

chrome.runtime.onMessage.addListener((message, sender) => {
const tab = sender.tab;
// sender.tab.id from content script points to the tab that injected the content script
if (tab) {
const id = tab.id;
// This is sent from the hook content script.
// It tells us a renderer has attached.
if (message.hasDetectedReact) {
setExtensionIconAndPopup(message.reactBuildType, id);
} else {
const extensionPort = ports[id]?.extension;

switch (message.payload?.type) {
case 'fetch-file-with-cache-complete':
case 'fetch-file-with-cache-error':
// Forward the result of fetch-in-page requests back to the extension.
extensionPort?.postMessage(message);
break;
// This is sent from the backend manager running on a page
case 'react-devtools-required-backends':
const backendsToDownload = [];
message.payload.versions.forEach(version => {
if (EXTENSION_CONTAINED_VERSIONS.includes(version)) {
if (!IS_FIREFOX) {
// equivalent logic for Firefox is in prepareInjection.js
chrome.scripting.executeScript({
target: {tabId: id},
files: [`/build/react_devtools_backend_${version}.js`],
world: chrome.scripting.ExecutionWorld.MAIN,
});
}
} else {
backendsToDownload.push(version);
}
});

// Request the necessary backends in the extension DevTools UI
// TODO: handle this message in index.js to build the UI
extensionPort?.postMessage({
payload: {
type: 'react-devtools-additional-backends',
versions: backendsToDownload,
},
});
break;
}
switch (message?.source) {
case 'devtools-page': {
handleDevToolsPageMessage(message);
break;
}
}

// This is sent from the devtools page when it is ready for injecting the backend
if (message?.payload?.type === 'react-devtools-inject-backend-manager') {
// sender.tab.id from devtools page may not exist, or point to the undocked devtools window
// so we use the payload to get the tab id
const tabId = message.payload.tabId;

if (tabId && !IS_FIREFOX) {
// equivalent logic for Firefox is in prepareInjection.js
chrome.scripting.executeScript({
target: {tabId},
files: ['/build/backendManager.js'],
world: chrome.scripting.ExecutionWorld.MAIN,
});
case 'react-devtools-fetch-resource-content-script': {
handleFetchResourceContentScriptMessage(message);
break;
}
case 'react-devtools-backend-manager': {
handleBackendManagerMessage(message, sender);
break;
}
case 'react-devtools-hook': {
handleReactDevToolsHookMessage(message, sender);
}
}
});
Expand Down
12 changes: 0 additions & 12 deletions packages/react-devtools-extensions/src/background/injectProxy.js

This file was deleted.

103 changes: 103 additions & 0 deletions packages/react-devtools-extensions/src/background/messageHandlers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/* global chrome */

import setExtensionIconAndPopup from './setExtensionIconAndPopup';
import {executeScriptInMainWorld} from './executeScript';

import {EXTENSION_CONTAINED_VERSIONS} from '../utils';

export function handleReactDevToolsHookMessage(message, sender) {
const {payload} = message;

switch (payload?.type) {
case 'react-renderer-attached': {
setExtensionIconAndPopup(payload.reactBuildType, sender.tab.id);

break;
}
hoxyq marked this conversation as resolved.
Show resolved Hide resolved
}
}

export function handleBackendManagerMessage(message, sender) {
const {payload} = message;

switch (payload?.type) {
case 'require-backends': {
payload.versions.forEach(version => {
if (EXTENSION_CONTAINED_VERSIONS.includes(version)) {
executeScriptInMainWorld({
target: {tabId: sender.tab.id},
files: [`/build/react_devtools_backend_${version}.js`],
});
}
});

break;
}
}
}

export function handleDevToolsPageMessage(message) {
const {payload} = message;

switch (payload?.type) {
// Proxy this message from DevTools page to content script via chrome.tabs.sendMessage
case 'fetch-file-with-cache': {
const {
payload: {tabId, url},
} = message;

if (!tabId) {
throw new Error("Couldn't fetch file sources: tabId not specified");
}

if (!url) {
throw new Error("Couldn't fetch file sources: url not specified");
}

chrome.tabs.sendMessage(tabId, {
source: 'devtools-page',
payload: {
type: 'fetch-file-with-cache',
url,
},
});

break;
}

case 'inject-backend-manager': {
const {
payload: {tabId},
} = message;

if (!tabId) {
throw new Error("Couldn't inject backend manager: tabId not specified");
}

executeScriptInMainWorld({
target: {tabId},
files: ['/build/backendManager.js'],
});

break;
}
}
}

export function handleFetchResourceContentScriptMessage(message) {
const {payload} = message;

switch (payload?.type) {
case 'fetch-file-with-cache-complete':
case 'fetch-file-with-cache-error':
// Forward the result of fetch-in-page requests back to the DevTools page.
// We switch the source here because of inconsistency between Firefox and Chrome
// In Chromium this message will be propagated from content script to DevTools page
// For Firefox, only background script will get this message, so we need to forward it to DevTools page
chrome.runtime.sendMessage({
source: 'react-devtools-background',
payload,
});
break;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ function updateRequiredBackends() {
{
source: 'react-devtools-backend-manager',
payload: {
type: 'react-devtools-required-backends',
type: 'require-backends',
versions: Array.from(requiredBackends),
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* global chrome */

function fetchResource(url) {
const reject = value => {
chrome.runtime.sendMessage({
source: 'react-devtools-fetch-resource-content-script',
payload: {
type: 'fetch-file-with-cache-error',
url,
value,
},
});
};

const resolve = value => {
chrome.runtime.sendMessage({
source: 'react-devtools-fetch-resource-content-script',
payload: {
type: 'fetch-file-with-cache-complete',
url,
value,
},
});
};

fetch(url, {cache: 'force-cache'}).then(
response => {
if (response.ok) {
response
.text()
.then(text => resolve(text))
.catch(error => reject(null));
} else {
reject(null);
}
},
error => reject(null),
);
}

chrome.runtime.onMessage.addListener(message => {
if (
message?.source === 'devtools-page' &&
message?.payload?.type === 'fetch-file-with-cache'
) {
fetchResource(message.payload.url);
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ if (!window.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) {
function ({reactBuildType}) {
window.postMessage(
{
source: 'react-devtools-detector',
reactBuildType,
source: 'react-devtools-hook',
payload: {
type: 'react-renderer-attached',
reactBuildType,
},
},
'*',
);
Expand Down
Loading