Skip to content

Commit

Permalink
refactor[devtools/extension]: refactored messaging logic across diffe…
Browse files Browse the repository at this point in the history
…rent parts of the extension
  • Loading branch information
hoxyq committed Sep 25, 2023
1 parent 69728fd commit 0e7f122
Show file tree
Hide file tree
Showing 14 changed files with 389 additions and 291 deletions.
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.

101 changes: 101 additions & 0 deletions packages/react-devtools-extensions/src/background/messageHandlers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* 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);
}
}
}

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,51 @@
/* global chrome */

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

const resolve = value => {
console.log('fetch resolve');
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'
) {
console.log('fetch start');
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

0 comments on commit 0e7f122

Please sign in to comment.