mirror of
https://github.com/facebook/react.git
synced 2026-02-21 19:31:52 +00:00
wip
This commit is contained in:
@@ -41,6 +41,7 @@
|
||||
"service_worker": "build/background.js"
|
||||
},
|
||||
"permissions": [
|
||||
"contextMenus",
|
||||
"scripting",
|
||||
"storage",
|
||||
"tabs"
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"service_worker": "build/background.js"
|
||||
},
|
||||
"permissions": [
|
||||
"contextMenus",
|
||||
"scripting",
|
||||
"storage",
|
||||
"tabs"
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
]
|
||||
},
|
||||
"permissions": [
|
||||
"contextMenus",
|
||||
"scripting",
|
||||
"storage",
|
||||
"tabs",
|
||||
|
||||
192
packages/react-devtools-extensions/src/background/contextMenuManager.js
vendored
Normal file
192
packages/react-devtools-extensions/src/background/contextMenuManager.js
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
/* global chrome */
|
||||
|
||||
import {
|
||||
executeScriptInMainWorld,
|
||||
executeScriptInIsolatedWorld,
|
||||
} from './executeScript';
|
||||
|
||||
const CONTEXT_MENU_ID = 'react-devtools-inspect-element';
|
||||
|
||||
// Track inspection state per tab
|
||||
const inspectionStatePerTab: {[tabId: number]: boolean} = {};
|
||||
|
||||
// Create context menu on extension install/startup
|
||||
chrome.runtime.onInstalled.addListener(() => {
|
||||
createContextMenu();
|
||||
});
|
||||
|
||||
// Also create on startup in case extension was already installed
|
||||
chrome.runtime.onStartup?.addListener(() => {
|
||||
createContextMenu();
|
||||
});
|
||||
|
||||
function createContextMenu(): void {
|
||||
// Remove existing menu item first to avoid duplicates
|
||||
chrome.contextMenus.remove(CONTEXT_MENU_ID, () => {
|
||||
// Access lastError to suppress "Unchecked runtime.lastError" warnings
|
||||
void chrome.runtime.lastError;
|
||||
|
||||
chrome.contextMenus.create({
|
||||
id: CONTEXT_MENU_ID,
|
||||
title: 'Inspect React Component',
|
||||
contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image'],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle context menu click
|
||||
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
||||
if (info.menuItemId === CONTEXT_MENU_ID && tab?.id != null) {
|
||||
toggleInspectionMode(tab.id);
|
||||
}
|
||||
});
|
||||
|
||||
async function toggleInspectionMode(tabId: number): Promise<void> {
|
||||
const isActive = inspectionStatePerTab[tabId] || false;
|
||||
|
||||
if (isActive) {
|
||||
await stopInspectionMode(tabId);
|
||||
} else {
|
||||
await startInspectionMode(tabId);
|
||||
}
|
||||
}
|
||||
|
||||
async function startInspectionMode(tabId: number): Promise<void> {
|
||||
try {
|
||||
// Phase 1: Inject loading indicator and hook in parallel (no dependencies)
|
||||
await Promise.all([
|
||||
// Show loading indicator for immediate visual feedback
|
||||
executeScriptInMainWorld({
|
||||
target: {tabId},
|
||||
files: ['build/inspectionLoading.js'],
|
||||
injectImmediately: true,
|
||||
}),
|
||||
// Inject the hook if it doesn't exist (has guard against double installation)
|
||||
executeScriptInMainWorld({
|
||||
target: {tabId},
|
||||
files: ['build/installHook.js'],
|
||||
injectImmediately: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
// Phase 2: Inject backend and proxy in parallel (backend needs hook, proxy has no deps)
|
||||
await Promise.all([
|
||||
// Backend script registers Agent/Bridge/initBackend in hook.backends
|
||||
executeScriptInMainWorld({
|
||||
target: {tabId},
|
||||
files: ['build/react_devtools_backend_compact.js'],
|
||||
injectImmediately: true,
|
||||
}),
|
||||
// Proxy script bridges chrome.runtime messages to postMessage
|
||||
executeScriptInIsolatedWorld({
|
||||
target: {tabId},
|
||||
files: ['build/standaloneInspectorProxy.js'],
|
||||
}),
|
||||
]);
|
||||
|
||||
// Phase 3: Inject main inspector script (needs hook + backend)
|
||||
await executeScriptInMainWorld({
|
||||
target: {tabId},
|
||||
files: ['build/standaloneInspector.js'],
|
||||
injectImmediately: true,
|
||||
});
|
||||
|
||||
// Send message to start inspection
|
||||
chrome.tabs.sendMessage(tabId, {
|
||||
source: 'react-devtools-context-menu',
|
||||
type: 'startInspection',
|
||||
});
|
||||
|
||||
inspectionStatePerTab[tabId] = true;
|
||||
updateContextMenuTitleForTab(tabId);
|
||||
} catch (error) {
|
||||
console.error('Failed to start React DevTools inspection mode:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function stopInspectionMode(tabId: number): Promise<void> {
|
||||
try {
|
||||
chrome.tabs.sendMessage(tabId, {
|
||||
source: 'react-devtools-context-menu',
|
||||
type: 'stopInspection',
|
||||
});
|
||||
|
||||
inspectionStatePerTab[tabId] = false;
|
||||
updateContextMenuTitleForTab(tabId);
|
||||
} catch (error) {
|
||||
console.error('Failed to stop React DevTools inspection mode:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function updateContextMenuTitleForTab(tabId: number): void {
|
||||
const isActive = inspectionStatePerTab[tabId] || false;
|
||||
chrome.contextMenus.update(CONTEXT_MENU_ID, {
|
||||
title: isActive ? 'Stop Inspecting React' : 'Inspect React Component',
|
||||
});
|
||||
}
|
||||
|
||||
async function updateContextMenuForActiveTab(): Promise<void> {
|
||||
try {
|
||||
const [activeTab] = await chrome.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
if (activeTab?.id != null) {
|
||||
updateContextMenuTitleForTab(activeTab.id);
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors (e.g., no active tab)
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up when tab closes
|
||||
chrome.tabs.onRemoved.addListener(tabId => {
|
||||
const wasActive = inspectionStatePerTab[tabId];
|
||||
delete inspectionStatePerTab[tabId];
|
||||
// Update menu title in case the closed tab was active
|
||||
if (wasActive) {
|
||||
updateContextMenuForActiveTab();
|
||||
}
|
||||
});
|
||||
|
||||
// Reset state when tab navigates
|
||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
|
||||
if (changeInfo.status === 'loading' && inspectionStatePerTab[tabId]) {
|
||||
// Page is navigating - reset inspection state
|
||||
inspectionStatePerTab[tabId] = false;
|
||||
updateContextMenuForActiveTab();
|
||||
}
|
||||
});
|
||||
|
||||
// Update context menu title when switching tabs
|
||||
chrome.tabs.onActivated.addListener(activeInfo => {
|
||||
updateContextMenuTitleForTab(activeInfo.tabId);
|
||||
});
|
||||
|
||||
// Handle messages from standalone inspector
|
||||
export function handleStandaloneInspectorMessage(
|
||||
message: {type: string, ...},
|
||||
sender: {tab?: {id?: number}, ...},
|
||||
): void {
|
||||
const tabId = sender.tab?.id;
|
||||
if (tabId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.type) {
|
||||
case 'inspectionStopped': {
|
||||
inspectionStatePerTab[tabId] = false;
|
||||
updateContextMenuTitleForTab(tabId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import './dynamicallyInjectContentScripts';
|
||||
import './tabsManager';
|
||||
import './contextMenuManager';
|
||||
|
||||
import {
|
||||
handleDevToolsPageMessage,
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
handleReactDevToolsHookMessage,
|
||||
handleFetchResourceContentScriptMessage,
|
||||
} from './messageHandlers';
|
||||
import {handleStandaloneInspectorMessage} from './contextMenuManager';
|
||||
|
||||
/*
|
||||
{
|
||||
@@ -192,6 +194,11 @@ chrome.runtime.onMessage.addListener((message, sender) => {
|
||||
}
|
||||
case 'react-devtools-hook': {
|
||||
handleReactDevToolsHookMessage(message, sender);
|
||||
break;
|
||||
}
|
||||
case 'react-devtools-standalone-inspector': {
|
||||
handleStandaloneInspectorMessage(message, sender);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
72
packages/react-devtools-extensions/src/contentScripts/inspectionLoading.js
vendored
Normal file
72
packages/react-devtools-extensions/src/contentScripts/inspectionLoading.js
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
// Lightweight script that shows loading indicator immediately
|
||||
// This runs before the heavy backend scripts are injected
|
||||
|
||||
if (!window.__REACT_DEVTOOLS_INSPECTION_LOADING__) {
|
||||
window.__REACT_DEVTOOLS_INSPECTION_LOADING__ = true;
|
||||
|
||||
const doc = document;
|
||||
const loadingIndicator = doc.createElement('div');
|
||||
loadingIndicator.id = '__react-devtools-loading-indicator__';
|
||||
|
||||
Object.assign(loadingIndicator.style, {
|
||||
position: 'fixed',
|
||||
top: '10px',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
zIndex: '10000002',
|
||||
backgroundColor: 'rgba(97, 218, 251, 0.95)',
|
||||
color: '#1a1a2e',
|
||||
borderRadius: '6px',
|
||||
padding: '8px 16px',
|
||||
fontFamily:
|
||||
'"SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace',
|
||||
fontSize: '12px',
|
||||
fontWeight: 'bold',
|
||||
whiteSpace: 'nowrap',
|
||||
pointerEvents: 'none',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
});
|
||||
|
||||
// Add a simple loading spinner
|
||||
const spinner = doc.createElement('div');
|
||||
Object.assign(spinner.style, {
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
border: '2px solid #1a1a2e',
|
||||
borderTopColor: 'transparent',
|
||||
borderRadius: '50%',
|
||||
animation: 'react-devtools-spin 0.8s linear infinite',
|
||||
});
|
||||
|
||||
// Add keyframe animation
|
||||
const style = doc.createElement('style');
|
||||
style.id = '__react-devtools-loading-style__';
|
||||
style.textContent = `
|
||||
@keyframes react-devtools-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
`;
|
||||
doc.head.appendChild(style);
|
||||
|
||||
loadingIndicator.appendChild(spinner);
|
||||
loadingIndicator.appendChild(
|
||||
doc.createTextNode('React DevTools: Starting inspection...'),
|
||||
);
|
||||
|
||||
doc.body.appendChild(loadingIndicator);
|
||||
|
||||
// Also change cursor to indicate loading
|
||||
doc.body.style.cursor = 'progress';
|
||||
}
|
||||
286
packages/react-devtools-extensions/src/contentScripts/standaloneInspector.js
vendored
Normal file
286
packages/react-devtools-extensions/src/contentScripts/standaloneInspector.js
vendored
Normal file
@@ -0,0 +1,286 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {DevToolsHook} from 'react-devtools-shared/src/backend/types';
|
||||
import type Agent from 'react-devtools-shared/src/backend/agent';
|
||||
|
||||
import Overlay from 'react-devtools-shared/src/backend/views/Highlighter/Overlay';
|
||||
import {COMPACT_VERSION_NAME} from 'react-devtools-extensions/src/utils';
|
||||
|
||||
// Guard against multiple injections
|
||||
if (!window.__REACT_DEVTOOLS_STANDALONE_INSPECTOR__) {
|
||||
window.__REACT_DEVTOOLS_STANDALONE_INSPECTOR__ = true;
|
||||
|
||||
// Use IIFE to create proper scope for function declarations
|
||||
(function () {
|
||||
let inspectionActive = false;
|
||||
let agent: Agent | null = null;
|
||||
let overlay: Overlay | null = null;
|
||||
let notReactBubble: HTMLElement | null = null;
|
||||
let nonReactHoverTimer: TimeoutID | null = null;
|
||||
let lastHoveredElement: EventTarget | null = null;
|
||||
|
||||
window.addEventListener('message', handleMessage);
|
||||
|
||||
function handleMessage(event: MessageEvent): void {
|
||||
if (event.source !== window) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = (event.data: any);
|
||||
if (data?.source !== 'react-devtools-standalone-inspector-proxy') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type === 'startInspection') {
|
||||
startInspection();
|
||||
} else if (data.type === 'stopInspection') {
|
||||
stopInspection();
|
||||
}
|
||||
}
|
||||
|
||||
function hideLoadingIndicator(): void {
|
||||
const indicator = document.getElementById(
|
||||
'__react-devtools-loading-indicator__',
|
||||
);
|
||||
indicator?.parentNode?.removeChild(indicator);
|
||||
|
||||
const style = document.getElementById(
|
||||
'__react-devtools-loading-style__',
|
||||
);
|
||||
style?.parentNode?.removeChild(style);
|
||||
|
||||
delete window.__REACT_DEVTOOLS_INSPECTION_LOADING__;
|
||||
}
|
||||
|
||||
function showReadyIndicator(): void {
|
||||
const indicator = document.createElement('div');
|
||||
Object.assign(indicator.style, {
|
||||
position: 'fixed',
|
||||
top: '10px',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
zIndex: '10000002',
|
||||
backgroundColor: 'rgba(97, 218, 251, 0.95)',
|
||||
color: '#1a1a2e',
|
||||
borderRadius: '6px',
|
||||
padding: '8px 16px',
|
||||
fontFamily:
|
||||
'"SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace',
|
||||
fontSize: '12px',
|
||||
fontWeight: 'bold',
|
||||
whiteSpace: 'nowrap',
|
||||
pointerEvents: 'none',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
||||
transition: 'opacity 0.3s ease-out',
|
||||
});
|
||||
indicator.textContent = '✓ Inspection mode active (Press Esc to exit)';
|
||||
document.body.appendChild(indicator);
|
||||
document.body.style.cursor = 'crosshair';
|
||||
|
||||
// Fade out and remove after 2 seconds
|
||||
setTimeout(() => {
|
||||
indicator.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
indicator.parentNode?.removeChild(indicator);
|
||||
}, 300);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function startInspection(): void {
|
||||
if (inspectionActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hook: ?DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
if (!hook) {
|
||||
hideLoadingIndicator();
|
||||
document.body.style.cursor = '';
|
||||
console.warn(
|
||||
'React DevTools: Global hook not found. Is React DevTools extension installed?',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Use existing agent if DevTools panel is open, otherwise activate standalone backend
|
||||
agent = hook.reactDevtoolsAgent ?? activateStandaloneBackend(hook);
|
||||
|
||||
hideLoadingIndicator();
|
||||
|
||||
if (!agent) {
|
||||
document.body.style.cursor = '';
|
||||
console.warn(
|
||||
'React DevTools: Could not activate backend. The page may not have React loaded.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
overlay = new Overlay(agent);
|
||||
inspectionActive = true;
|
||||
window.addEventListener('pointermove', onPointerMove, true);
|
||||
window.addEventListener('pointerdown', onPointerDown, true);
|
||||
window.addEventListener('keydown', onKeyDown, true);
|
||||
showReadyIndicator();
|
||||
}
|
||||
|
||||
function activateStandaloneBackend(hook: DevToolsHook): Agent | null {
|
||||
let backend = hook.backends.get(COMPACT_VERSION_NAME);
|
||||
if (!backend && hook.backends.size > 0) {
|
||||
backend = hook.backends.values().next().value;
|
||||
}
|
||||
|
||||
if (!backend) {
|
||||
console.warn(
|
||||
'React DevTools: No backend available. The backend script may not have been injected.',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const {Agent: AgentClass, Bridge, initBackend} = backend;
|
||||
|
||||
// Create a no-op bridge since we don't communicate with a frontend panel
|
||||
const bridge = new Bridge({
|
||||
listen: () => () => {},
|
||||
send: () => {},
|
||||
});
|
||||
|
||||
const newAgent = new AgentClass(bridge, false);
|
||||
initBackend(hook, newAgent, window, false);
|
||||
window.__REACT_DEVTOOLS_STANDALONE_BACKEND_ACTIVE__ = true;
|
||||
|
||||
return newAgent;
|
||||
}
|
||||
|
||||
function stopInspection(): void {
|
||||
if (!inspectionActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
inspectionActive = false;
|
||||
window.removeEventListener('pointermove', onPointerMove, true);
|
||||
window.removeEventListener('pointerdown', onPointerDown, true);
|
||||
window.removeEventListener('keydown', onKeyDown, true);
|
||||
|
||||
if (overlay) {
|
||||
overlay.remove();
|
||||
overlay = null;
|
||||
}
|
||||
|
||||
hideNotReactBubble();
|
||||
hideLoadingIndicator();
|
||||
clearNonReactTimer();
|
||||
document.body.style.cursor = '';
|
||||
agent = null;
|
||||
lastHoveredElement = null;
|
||||
|
||||
window.postMessage(
|
||||
{
|
||||
source: 'react-devtools-standalone-inspector',
|
||||
type: 'inspectionStopped',
|
||||
},
|
||||
'*',
|
||||
);
|
||||
}
|
||||
|
||||
function onPointerMove(event: PointerEvent): void {
|
||||
if (!inspectionActive || !agent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = getEventTarget(event);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target === lastHoveredElement) {
|
||||
return;
|
||||
}
|
||||
lastHoveredElement = target;
|
||||
|
||||
clearNonReactTimer();
|
||||
hideNotReactBubble();
|
||||
|
||||
const componentName = agent.getComponentNameForHostInstance(target);
|
||||
if (componentName) {
|
||||
overlay?.inspect([target], componentName);
|
||||
} else {
|
||||
if (overlay) {
|
||||
overlay.remove();
|
||||
overlay = new Overlay(agent);
|
||||
}
|
||||
nonReactHoverTimer = setTimeout(() => {
|
||||
showNotReactBubble(target);
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function onPointerDown(event: PointerEvent): void {
|
||||
if (inspectionActive) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
function onKeyDown(event: KeyboardEvent): void {
|
||||
if (event.key === 'Escape') {
|
||||
stopInspection();
|
||||
}
|
||||
}
|
||||
|
||||
function getEventTarget(event: PointerEvent): HTMLElement | null {
|
||||
const path = event.composedPath();
|
||||
if (path.length > 0 && path[0] instanceof HTMLElement) {
|
||||
return path[0];
|
||||
}
|
||||
return event.target instanceof HTMLElement ? event.target : null;
|
||||
}
|
||||
|
||||
function clearNonReactTimer(): void {
|
||||
if (nonReactHoverTimer) {
|
||||
clearTimeout(nonReactHoverTimer);
|
||||
nonReactHoverTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function showNotReactBubble(target: HTMLElement): void {
|
||||
hideNotReactBubble();
|
||||
|
||||
notReactBubble = document.createElement('div');
|
||||
Object.assign(notReactBubble.style, {
|
||||
position: 'fixed',
|
||||
zIndex: '10000001',
|
||||
backgroundColor: 'rgba(51, 55, 64, 0.9)',
|
||||
color: '#d7d7d7',
|
||||
borderRadius: '4px',
|
||||
padding: '6px 10px',
|
||||
fontFamily:
|
||||
'"SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace',
|
||||
fontSize: '11px',
|
||||
whiteSpace: 'nowrap',
|
||||
pointerEvents: 'none',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',
|
||||
transform: 'translateX(-50%)',
|
||||
});
|
||||
notReactBubble.textContent = 'Not rendered with React';
|
||||
|
||||
const rect = target.getBoundingClientRect();
|
||||
notReactBubble.style.left = `${Math.max(10, rect.left + rect.width / 2)}px`;
|
||||
notReactBubble.style.top = `${Math.max(10, rect.top - 30)}px`;
|
||||
|
||||
document.body.appendChild(notReactBubble);
|
||||
|
||||
setTimeout(hideNotReactBubble, 2000);
|
||||
}
|
||||
|
||||
function hideNotReactBubble(): void {
|
||||
notReactBubble?.parentNode?.removeChild(notReactBubble);
|
||||
notReactBubble = null;
|
||||
}
|
||||
})();
|
||||
}
|
||||
43
packages/react-devtools-extensions/src/contentScripts/standaloneInspectorProxy.js
vendored
Normal file
43
packages/react-devtools-extensions/src/contentScripts/standaloneInspectorProxy.js
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
/* global chrome */
|
||||
|
||||
// This content script runs in the isolated world and bridges
|
||||
// chrome.runtime messages to/from the main world standaloneInspector.js
|
||||
|
||||
// Forward messages from background script to main world
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message?.source === 'react-devtools-context-menu') {
|
||||
window.postMessage(
|
||||
{
|
||||
source: 'react-devtools-standalone-inspector-proxy',
|
||||
type: message.type,
|
||||
payload: message.payload,
|
||||
},
|
||||
'*',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Forward messages from main world back to background script
|
||||
window.addEventListener('message', event => {
|
||||
if (event.source !== window) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = event.data;
|
||||
if (data?.source === 'react-devtools-standalone-inspector') {
|
||||
chrome.runtime.sendMessage({
|
||||
source: 'react-devtools-standalone-inspector',
|
||||
type: data.type,
|
||||
payload: data.payload,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -77,6 +77,10 @@ module.exports = {
|
||||
prepareInjection: './src/contentScripts/prepareInjection.js',
|
||||
installHook: './src/contentScripts/installHook.js',
|
||||
hookSettingsInjector: './src/contentScripts/hookSettingsInjector.js',
|
||||
standaloneInspector: './src/contentScripts/standaloneInspector.js',
|
||||
standaloneInspectorProxy:
|
||||
'./src/contentScripts/standaloneInspectorProxy.js',
|
||||
inspectionLoading: './src/contentScripts/inspectionLoading.js',
|
||||
},
|
||||
output: {
|
||||
path: __dirname + '/build',
|
||||
|
||||
Reference in New Issue
Block a user