Skip to content

Commit 82984e5

Browse files
committed
Refactor and improve error handling
1 parent 99a1740 commit 82984e5

File tree

8 files changed

+179
-80
lines changed

8 files changed

+179
-80
lines changed

packages/react-devtools-extensions/src/contentScripts/fallbackEvalContext.js

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,23 @@
1-
/*
2-
Can not access `Developer Tools Console API` (e.g., inspect(), $0) in this context.
3-
So some functions are no-op or throw error.
4-
*/
5-
const evalScripts = {
6-
checkIfReactPresentInInspectedWindow: () =>
7-
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ &&
8-
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.size > 0,
9-
reload: () => window.location.reload(),
10-
setBrowserSelectionFromReact: () => {
11-
throw new Error('Not supported in fallback eval context');
12-
},
13-
setReactSelectionFromBrowser: () => {
14-
throw new Error('Not supported in fallback eval context');
15-
},
16-
viewAttributeSource: ({rendererID, elementID, path}) => {
17-
return false; // Not supported in fallback eval context
18-
},
19-
viewElementSource: ({rendererID, elementID}) => {
20-
return false; // Not supported in fallback eval context
21-
},
22-
};
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import {evalScripts} from '../evalScripts';
2311

2412
window.addEventListener('message', event => {
2513
if (event.data?.source === 'react-devtools-content-script-eval') {
2614
const {scriptId, args, requestId} = event.data.payload;
2715
const response = {result: null, error: null};
2816
try {
29-
response.result = evalScripts[scriptId].apply(null, args);
17+
if (!evalScripts[scriptId]) {
18+
throw new Error(`No eval script with id "${scriptId}" exists.`);
19+
}
20+
response.result = evalScripts[scriptId].fn.apply(null, args);
3021
} catch (err) {
3122
response.error = err.message;
3223
}

packages/react-devtools-extensions/src/contentScripts/proxy.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ function connectPort() {
119119
}
120120

121121
let evalRequestId = 0;
122-
const evalRequestCallbacks = new Map();
122+
const evalRequestCallbacks = new Map<number, Function>();
123123

124124
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
125125
switch (msg?.source) {
@@ -148,8 +148,11 @@ window.addEventListener('message', event => {
148148
const {requestId, response} = event.data.payload;
149149
const callback = evalRequestCallbacks.get(requestId);
150150
if (callback) {
151-
callback(response);
152-
evalRequestCallbacks.delete(requestId);
151+
try {
152+
callback(response);
153+
} finally {
154+
evalRequestCallbacks.delete(requestId);
155+
}
153156
}
154157
}
155158
});
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
export type EvalScriptIds =
11+
| 'checkIfReactPresentInInspectedWindow'
12+
| 'reload'
13+
| 'setBrowserSelectionFromReact'
14+
| 'setReactSelectionFromBrowser'
15+
| 'viewAttributeSource'
16+
| 'viewElementSource';
17+
18+
/*
19+
.fn for fallback in Content Script context
20+
.code for chrome.devtools.inspectedWindow.eval()
21+
*/
22+
type EvalScriptEntry = {
23+
fn: (...args: any[]) => any,
24+
code: (...args: any[]) => string,
25+
};
26+
27+
/*
28+
Can not access `Developer Tools Console API` (e.g., inspect(), $0) in this context.
29+
So some fallback functions are no-op or throw error.
30+
*/
31+
export const evalScripts: {[key: EvalScriptIds]: EvalScriptEntry} = {
32+
checkIfReactPresentInInspectedWindow: {
33+
fn: () =>
34+
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ &&
35+
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.size > 0,
36+
code: () =>
37+
'window.__REACT_DEVTOOLS_GLOBAL_HOOK__ &&' +
38+
'window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.size > 0',
39+
},
40+
reload: {
41+
fn: () => window.location.reload(),
42+
code: () => 'window.location.reload();',
43+
},
44+
setBrowserSelectionFromReact: {
45+
fn: () => {
46+
throw new Error('Not supported in fallback eval context');
47+
},
48+
code: () =>
49+
'(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' +
50+
'(inspect(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0), true) :' +
51+
'false',
52+
},
53+
setReactSelectionFromBrowser: {
54+
fn: () => {
55+
throw new Error('Not supported in fallback eval context');
56+
},
57+
code: () =>
58+
'(window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' +
59+
'(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 = $0, true) :' +
60+
'false',
61+
},
62+
viewAttributeSource: {
63+
fn: ({rendererID, elementID, path}) => {
64+
return false; // Not supported in fallback eval context
65+
},
66+
code: ({rendererID, elementID, path}) =>
67+
'{' + // The outer block is important because it means we can declare local variables.
68+
'const renderer = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces.get(' +
69+
JSON.stringify(rendererID) +
70+
');' +
71+
'if (renderer) {' +
72+
' const value = renderer.getElementAttributeByPath(' +
73+
JSON.stringify(elementID) +
74+
',' +
75+
JSON.stringify(path) +
76+
');' +
77+
' if (value) {' +
78+
' inspect(value);' +
79+
' true;' +
80+
' } else {' +
81+
' false;' +
82+
' }' +
83+
'} else {' +
84+
' false;' +
85+
'}' +
86+
'}',
87+
},
88+
viewElementSource: {
89+
fn: ({rendererID, elementID}) => {
90+
return false; // Not supported in fallback eval context
91+
},
92+
code: ({rendererID, elementID}) =>
93+
'{' + // The outer block is important because it means we can declare local variables.
94+
'const renderer = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces.get(' +
95+
JSON.stringify(rendererID) +
96+
');' +
97+
'if (renderer) {' +
98+
' const value = renderer.getElementSourceFunctionById(' +
99+
JSON.stringify(elementID) +
100+
');' +
101+
' if (value) {' +
102+
' inspect(value);' +
103+
' true;' +
104+
' } else {' +
105+
' false;' +
106+
' }' +
107+
'} else {' +
108+
' false;' +
109+
'}' +
110+
'}',
111+
},
112+
};

packages/react-devtools-extensions/src/main/elementSelection.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ export function setBrowserSelectionFromReact() {
77
evalInInspectedWindow(
88
'setBrowserSelectionFromReact',
99
[],
10-
'(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' +
11-
'(inspect(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0), true) :' +
12-
'false',
1310
(didSelectionChange, evalError) => {
1411
if (evalError) {
1512
console.error(evalError);
@@ -24,9 +21,6 @@ export function setReactSelectionFromBrowser(bridge) {
2421
evalInInspectedWindow(
2522
'setReactSelectionFromBrowser',
2623
[],
27-
'(window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' +
28-
'(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 = $0, true) :' +
29-
'false',
3024
(didSelectionChange, evalError) => {
3125
if (evalError) {
3226
console.error(evalError);

packages/react-devtools-extensions/src/main/evalInInspectedWindow.js

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,41 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import type {EvalScriptIds} from '../evalScripts';
11+
12+
import {evalScripts} from '../evalScripts';
13+
14+
type ExceptionInfo = {
15+
code: ?string,
16+
description: ?string,
17+
isError: boolean,
18+
isException: boolean,
19+
value: any,
20+
};
21+
122
const EVAL_TIMEOUT = 1000 * 10;
223

324
let evalRequestId = 0;
4-
const evalRequestCallbacks = new Map();
25+
const evalRequestCallbacks = new Map<
26+
number,
27+
(value: {result: any, error: any}) => void,
28+
>();
529

6-
function fallbackEvalInInspectedWindow(scriptId, args, code, callback) {
30+
function fallbackEvalInInspectedWindow(
31+
scriptId: EvalScriptIds,
32+
args: any[],
33+
callback: (value: any, exceptionInfo: ?ExceptionInfo) => void,
34+
) {
35+
if (!evalScripts[scriptId]) {
36+
throw new Error(`No eval script with id "${scriptId}" exists.`);
37+
}
38+
const code = evalScripts[scriptId].code.apply(null, args);
739
const tabId = chrome.devtools.inspectedWindow.tabId;
840
const requestId = evalRequestId++;
941
chrome.runtime.sendMessage({
@@ -48,19 +80,27 @@ function fallbackEvalInInspectedWindow(scriptId, args, code, callback) {
4880
});
4981
}
5082

51-
export function evalInInspectedWindow(scriptId, args, code, callback) {
83+
export function evalInInspectedWindow(
84+
scriptId: EvalScriptIds,
85+
args: any[],
86+
callback: (value: any, exceptionInfo: ?ExceptionInfo) => void,
87+
) {
88+
if (!evalScripts[scriptId]) {
89+
throw new Error(`No eval script with id "${scriptId}" exists.`);
90+
}
91+
const code = evalScripts[scriptId].code.apply(null, args);
5292
chrome.devtools.inspectedWindow.eval(code, (result, exceptionInfo) => {
5393
if (!exceptionInfo) {
5494
callback(result, exceptionInfo);
5595
return;
5696
}
5797
// If an exception (e.g. CSP Blocked) occurred,
5898
// fallback to the content script eval context
59-
fallbackEvalInInspectedWindow(scriptId, args, code, callback);
99+
fallbackEvalInInspectedWindow(scriptId, args, callback);
60100
});
61101
}
62102

63-
function handleEvalInInspectedWindow({payload, source}) {
103+
chrome.runtime.onMessage.addListener(({payload, source}) => {
64104
if (source === 'react-devtools-background') {
65105
switch (payload?.type) {
66106
case 'eval-in-inspected-window-response': {
@@ -73,6 +113,4 @@ function handleEvalInInspectedWindow({payload, source}) {
73113
}
74114
}
75115
}
76-
}
77-
78-
chrome.runtime.onMessage.addListener(handleEvalInInspectedWindow);
116+
});

packages/react-devtools-extensions/src/main/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ function createBridge() {
7171

7272
bridge.addListener('reloadAppForProfiling', () => {
7373
localStorageSetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY, 'true');
74-
evalInInspectedWindow('reload', [], 'window.location.reload();');
74+
evalInInspectedWindow('reload', []);
7575
});
7676

7777
bridge.addListener(

packages/react-devtools-extensions/src/main/reactPolling.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ export function startReactPolling(
2929
evalInInspectedWindow(
3030
'checkIfReactPresentInInspectedWindow',
3131
[],
32-
'window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.size > 0',
3332
(pageHasReact, exceptionInfo) => {
3433
if (status === 'aborted') {
3534
onError(

packages/react-devtools-extensions/src/main/sourceSelection.js

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,6 @@ export function viewAttributeSource(rendererID, elementID, path) {
44
evalInInspectedWindow(
55
'viewAttributeSource',
66
[{rendererID, elementID, path}],
7-
'{' + // The outer block is important because it means we can declare local variables.
8-
'const renderer = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces.get(' +
9-
JSON.stringify(rendererID) +
10-
');' +
11-
'if (renderer) {' +
12-
' const value = renderer.getElementAttributeByPath(' +
13-
JSON.stringify(elementID) +
14-
',' +
15-
JSON.stringify(path) +
16-
');' +
17-
' if (value) {' +
18-
' inspect(value);' +
19-
' true;' +
20-
' } else {' +
21-
' false;' +
22-
' }' +
23-
'} else {' +
24-
' false;' +
25-
'}' +
26-
'}',
277
(didInspect, evalError) => {
288
if (evalError) {
299
console.error(evalError);
@@ -36,24 +16,6 @@ export function viewElementSource(rendererID, elementID) {
3616
evalInInspectedWindow(
3717
'viewElementSource',
3818
[{rendererID, elementID}],
39-
'{' + // The outer block is important because it means we can declare local variables.
40-
'const renderer = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces.get(' +
41-
JSON.stringify(rendererID) +
42-
');' +
43-
'if (renderer) {' +
44-
' const value = renderer.getElementSourceFunctionById(' +
45-
JSON.stringify(elementID) +
46-
');' +
47-
' if (value) {' +
48-
' inspect(value);' +
49-
' true;' +
50-
' } else {' +
51-
' false;' +
52-
' }' +
53-
'} else {' +
54-
' false;' +
55-
'}' +
56-
'}',
5719
(didInspect, evalError) => {
5820
if (evalError) {
5921
console.error(evalError);

0 commit comments

Comments
 (0)