mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
feat[react-devtools/extension]: use chrome.storage to persist settings across sessions (#30636)
Stacked on https://github.com/facebook/react/pull/30610 and whats under it. See [last commit](https://github.com/facebook/react/pull/30636/commits/248ddba18608e1bb5ef14c823085a7ff9d7a54a3). Now, we are using [`chrome.storage`](https://developer.chrome.com/docs/extensions/reference/api/storage) to persist settings for the browser extension across different sessions. Once settings are updated from the UI, the `Store` will emit `settingsUpdated` event, and we are going to persist them via `chrome.storage.local.set` in `main/index.js`. When hook is being injected, we are going to pass a `Promise`, which is going to be resolved after the settings are read from the storage via `chrome.storage.local.get` in `hookSettingsInjector.js`.
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
},
|
||||
"permissions": [
|
||||
"scripting",
|
||||
"storage",
|
||||
"tabs"
|
||||
],
|
||||
"host_permissions": [
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
},
|
||||
"permissions": [
|
||||
"scripting",
|
||||
"storage",
|
||||
"tabs"
|
||||
],
|
||||
"host_permissions": [
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
},
|
||||
"permissions": [
|
||||
"scripting",
|
||||
"storage",
|
||||
"tabs"
|
||||
],
|
||||
"host_permissions": [
|
||||
|
||||
+7
@@ -25,6 +25,13 @@ const contentScriptsToInject = [
|
||||
runAt: 'document_start',
|
||||
world: chrome.scripting.ExecutionWorld.MAIN,
|
||||
},
|
||||
{
|
||||
id: '@react-devtools/hook-settings-injector',
|
||||
js: ['build/hookSettingsInjector.js'],
|
||||
matches: ['<all_urls>'],
|
||||
persistAcrossSessions: true,
|
||||
runAt: 'document_start',
|
||||
},
|
||||
];
|
||||
|
||||
async function dynamicallyInjectContentScripts() {
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/* global chrome */
|
||||
|
||||
// We can't use chrome.storage domain from scripts which are injected in ExecutionWorld.MAIN
|
||||
// This is the only purpose of this script - to send persisted settings to installHook.js content script
|
||||
|
||||
async function messageListener(event: MessageEvent) {
|
||||
if (event.source !== window) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data.source === 'react-devtools-hook-installer') {
|
||||
if (event.data.payload.handshake) {
|
||||
const settings = await chrome.storage.local.get();
|
||||
// If storage was empty (first installation), define default settings
|
||||
if (typeof settings.appendComponentStack !== 'boolean') {
|
||||
settings.appendComponentStack = true;
|
||||
}
|
||||
if (typeof settings.breakOnConsoleErrors !== 'boolean') {
|
||||
settings.breakOnConsoleErrors = false;
|
||||
}
|
||||
if (typeof settings.showInlineWarningsAndErrors !== 'boolean') {
|
||||
settings.showInlineWarningsAndErrors = true;
|
||||
}
|
||||
if (typeof settings.hideConsoleLogsInStrictMode !== 'boolean') {
|
||||
settings.hideConsoleLogsInStrictMode = false;
|
||||
}
|
||||
|
||||
window.postMessage({
|
||||
source: 'react-devtools-hook-settings-injector',
|
||||
payload: {settings},
|
||||
});
|
||||
|
||||
window.removeEventListener('message', messageListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', messageListener);
|
||||
window.postMessage({
|
||||
source: 'react-devtools-hook-settings-injector',
|
||||
payload: {handshake: true},
|
||||
});
|
||||
@@ -1,10 +1,43 @@
|
||||
import {installHook} from 'react-devtools-shared/src/hook';
|
||||
|
||||
// avoid double execution
|
||||
if (!window.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) {
|
||||
installHook(window);
|
||||
let resolveHookSettingsInjection;
|
||||
|
||||
// detect react
|
||||
function messageListener(event: MessageEvent) {
|
||||
if (event.source !== window) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data.source === 'react-devtools-hook-settings-injector') {
|
||||
// In case handshake message was sent prior to hookSettingsInjector execution
|
||||
// We can't guarantee order
|
||||
if (event.data.payload.handshake) {
|
||||
window.postMessage({
|
||||
source: 'react-devtools-hook-installer',
|
||||
payload: {handshake: true},
|
||||
});
|
||||
} else if (event.data.payload.settings) {
|
||||
window.removeEventListener('message', messageListener);
|
||||
resolveHookSettingsInjection(event.data.payload.settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid double execution
|
||||
if (!window.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) {
|
||||
const hookSettingsPromise = new Promise(resolve => {
|
||||
resolveHookSettingsInjection = resolve;
|
||||
});
|
||||
|
||||
window.addEventListener('message', messageListener);
|
||||
window.postMessage({
|
||||
source: 'react-devtools-hook-installer',
|
||||
payload: {handshake: true},
|
||||
});
|
||||
|
||||
// Can't delay hook installation, inject settings lazily
|
||||
installHook(window, hookSettingsPromise);
|
||||
|
||||
// Detect React
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on(
|
||||
'renderer',
|
||||
function ({reactBuildType}) {
|
||||
|
||||
+4
-6
@@ -27,7 +27,6 @@ import {startReactPolling} from './reactPolling';
|
||||
import cloneStyleTags from './cloneStyleTags';
|
||||
import fetchFileWithCaching from './fetchFileWithCaching';
|
||||
import injectBackendManager from './injectBackendManager';
|
||||
import syncSavedPreferences from './syncSavedPreferences';
|
||||
import registerEventsLogger from './registerEventsLogger';
|
||||
import getProfilingFlags from './getProfilingFlags';
|
||||
import debounce from './debounce';
|
||||
@@ -103,6 +102,10 @@ function createBridgeAndStore() {
|
||||
supportsClickToInspect: true,
|
||||
});
|
||||
|
||||
store.addListener('settingsUpdated', settings => {
|
||||
chrome.storage.local.set(settings);
|
||||
});
|
||||
|
||||
if (!isProfiling) {
|
||||
// We previously stored this in performCleanup function
|
||||
store.profilerStore.profilingData = profilingData;
|
||||
@@ -393,10 +396,6 @@ let root = null;
|
||||
|
||||
let port = null;
|
||||
|
||||
// Re-initialize saved filters on navigation,
|
||||
// since global values stored on window get reset in this case.
|
||||
chrome.devtools.network.onNavigated.addListener(syncSavedPreferences);
|
||||
|
||||
// In case when multiple navigation events emitted in a short period of time
|
||||
// This debounced callback primarily used to avoid mounting React DevTools multiple times, which results
|
||||
// into subscribing to the same events from Bridge and window multiple times
|
||||
@@ -426,5 +425,4 @@ if (__IS_FIREFOX__) {
|
||||
|
||||
connectExtensionPort();
|
||||
|
||||
syncSavedPreferences();
|
||||
mountReactDevToolsWhenReactHasLoaded();
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/* global chrome */
|
||||
|
||||
import {
|
||||
getAppendComponentStack,
|
||||
getBreakOnConsoleErrors,
|
||||
getSavedComponentFilters,
|
||||
getShowInlineWarningsAndErrors,
|
||||
getHideConsoleLogsInStrictMode,
|
||||
} from 'react-devtools-shared/src/utils';
|
||||
|
||||
// The renderer interface can't read saved component filters directly,
|
||||
// because they are stored in localStorage within the context of the extension.
|
||||
// Instead it relies on the extension to pass filters through.
|
||||
function syncSavedPreferences() {
|
||||
chrome.devtools.inspectedWindow.eval(
|
||||
`window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = ${JSON.stringify(
|
||||
getAppendComponentStack(),
|
||||
)};
|
||||
window.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ = ${JSON.stringify(
|
||||
getBreakOnConsoleErrors(),
|
||||
)};
|
||||
window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = ${JSON.stringify(
|
||||
getSavedComponentFilters(),
|
||||
)};
|
||||
window.__REACT_DEVTOOLS_SHOW_INLINE_WARNINGS_AND_ERRORS__ = ${JSON.stringify(
|
||||
getShowInlineWarningsAndErrors(),
|
||||
)};
|
||||
window.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = ${JSON.stringify(
|
||||
getHideConsoleLogsInStrictMode(),
|
||||
)};`,
|
||||
);
|
||||
}
|
||||
|
||||
export default syncSavedPreferences;
|
||||
@@ -56,6 +56,7 @@ module.exports = {
|
||||
proxy: './src/contentScripts/proxy.js',
|
||||
prepareInjection: './src/contentScripts/prepareInjection.js',
|
||||
installHook: './src/contentScripts/installHook.js',
|
||||
hookSettingsInjector: './src/contentScripts/hookSettingsInjector.js',
|
||||
},
|
||||
output: {
|
||||
path: __dirname + '/build',
|
||||
|
||||
+2
-7
@@ -149,7 +149,7 @@ export default class Agent extends EventEmitter<{
|
||||
drawTraceUpdates: [Array<HostInstance>],
|
||||
disableTraceUpdates: [],
|
||||
getIfHasUnsupportedRendererVersion: [],
|
||||
updateHookSettings: [DevToolsHookSettings],
|
||||
updateHookSettings: [$ReadOnly<DevToolsHookSettings>],
|
||||
getHookSettings: [],
|
||||
}> {
|
||||
_bridge: BackendBridge;
|
||||
@@ -806,12 +806,7 @@ export default class Agent extends EventEmitter<{
|
||||
updateHookSettings: (settings: $ReadOnly<DevToolsHookSettings>) => void =
|
||||
settings => {
|
||||
// Propagate the settings, so Backend can subscribe to it and modify hook
|
||||
this.emit('updateHookSettings', {
|
||||
appendComponentStack: settings.appendComponentStack,
|
||||
breakOnConsoleErrors: settings.breakOnConsoleErrors,
|
||||
showInlineWarningsAndErrors: settings.showInlineWarningsAndErrors,
|
||||
hideConsoleLogsInStrictMode: settings.hideConsoleLogsInStrictMode,
|
||||
});
|
||||
this.emit('updateHookSettings', settings);
|
||||
};
|
||||
|
||||
getHookSettings: () => void = () => {
|
||||
|
||||
+1
-1
@@ -524,7 +524,7 @@ export type DevToolsHook = {
|
||||
// Testing
|
||||
dangerous_setTargetConsoleForTesting?: (fakeConsole: Object) => void,
|
||||
|
||||
settings?: DevToolsHookSettings,
|
||||
settings?: $ReadOnly<DevToolsHookSettings>,
|
||||
...
|
||||
};
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ export default class Store extends EventEmitter<{
|
||||
componentFilters: [],
|
||||
error: [Error],
|
||||
hookSettings: [$ReadOnly<DevToolsHookSettings>],
|
||||
settingsUpdated: [$ReadOnly<DevToolsHookSettings>],
|
||||
mutated: [[Array<number>, Map<number, number>]],
|
||||
recordChangeDescriptions: [],
|
||||
roots: [],
|
||||
@@ -1519,7 +1520,9 @@ export default class Store extends EventEmitter<{
|
||||
updateHookSettings: (settings: $ReadOnly<DevToolsHookSettings>) => void =
|
||||
settings => {
|
||||
this._hookSettings = settings;
|
||||
|
||||
this._bridge.send('updateHookSettings', settings);
|
||||
this.emit('settingsUpdated', settings);
|
||||
};
|
||||
|
||||
onHookSettings: (settings: $ReadOnly<DevToolsHookSettings>) => void =
|
||||
|
||||
Reference in New Issue
Block a user