diff --git a/packages/react-devtools-core/src/backend.js b/packages/react-devtools-core/src/backend.js index 48a1348ee05cc..a3f2e772d8fda 100644 --- a/packages/react-devtools-core/src/backend.js +++ b/packages/react-devtools-core/src/backend.js @@ -14,6 +14,11 @@ import {initBackend} from 'react-devtools-shared/src/backend'; import {__DEBUG__} from 'react-devtools-shared/src/constants'; import setupNativeStyleEditor from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor'; import {getDefaultComponentFilters} from 'react-devtools-shared/src/utils'; +import { + initializeUsingCachedSettings, + cacheConsolePatchSettings, + type DevToolsSettingsManager, +} from './cachedSettings'; import type {BackendBridge} from 'react-devtools-shared/src/bridge'; import type {ComponentFilter} from 'react-devtools-shared/src/types'; @@ -29,6 +34,7 @@ type ConnectOptions = { retryConnectionDelay?: number, isAppActive?: () => boolean, websocket?: ?WebSocket, + devToolsSettingsManager: ?DevToolsSettingsManager, ... }; @@ -63,6 +69,7 @@ export function connectToDevTools(options: ?ConnectOptions) { resolveRNStyle = null, retryConnectionDelay = 2000, isAppActive = () => true, + devToolsSettingsManager, } = options || {}; const protocol = useHttps ? 'wss' : 'ws'; @@ -78,6 +85,16 @@ export function connectToDevTools(options: ?ConnectOptions) { } } + if (devToolsSettingsManager != null) { + try { + initializeUsingCachedSettings(devToolsSettingsManager); + } catch (e) { + // If we call a method on devToolsSettingsManager that throws, or if + // is invalid data read out, don't throw and don't interrupt initialization + console.error(e); + } + } + if (!isAppActive()) { // If the app is in background, maybe retry later. // Don't actually attempt to connect until we're in foreground. @@ -157,6 +174,12 @@ export function connectToDevTools(options: ?ConnectOptions) { }, ); + if (devToolsSettingsManager != null && bridge != null) { + bridge.addListener('updateConsolePatchSettings', consolePatchSettings => + cacheConsolePatchSettings(devToolsSettingsManager, consolePatchSettings), + ); + } + // The renderer interface doesn't read saved component filters directly, // because they are generally stored in localStorage within the context of the extension. // Because of this it relies on the extension to pass filters. diff --git a/packages/react-devtools-core/src/cachedSettings.js b/packages/react-devtools-core/src/cachedSettings.js new file mode 100644 index 0000000000000..cb6dddfec313b --- /dev/null +++ b/packages/react-devtools-core/src/cachedSettings.js @@ -0,0 +1,77 @@ +/** + * Copyright (c) Facebook, Inc. and its 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 ConsolePatchSettings, + writeConsolePatchSettingsToWindow, +} from 'react-devtools-shared/src/backend/console'; +import {castBool, castBrowserTheme} from 'react-devtools-shared/src/utils'; + +// Note: all keys should be optional in this type, because users can use newer +// versions of React DevTools with older versions of React Native, and the object +// provided by React Native may not include all of this type's fields. +export type DevToolsSettingsManager = { + getConsolePatchSettings: ?() => string, + setConsolePatchSettings: ?(key: string) => void, +}; + +export function initializeUsingCachedSettings( + devToolsSettingsManager: DevToolsSettingsManager, +) { + initializeConsolePatchSettings(devToolsSettingsManager); +} + +function initializeConsolePatchSettings( + devToolsSettingsManager: DevToolsSettingsManager, +) { + if (devToolsSettingsManager.getConsolePatchSettings == null) { + return; + } + const consolePatchSettingsString = devToolsSettingsManager.getConsolePatchSettings(); + if (consolePatchSettingsString == null) { + return; + } + const parsedConsolePatchSettings = parseConsolePatchSettings( + consolePatchSettingsString, + ); + if (parsedConsolePatchSettings == null) { + return; + } + writeConsolePatchSettingsToWindow(parsedConsolePatchSettings); +} + +function parseConsolePatchSettings( + consolePatchSettingsString: string, +): ?ConsolePatchSettings { + const parsedValue = JSON.parse(consolePatchSettingsString ?? '{}'); + const { + appendComponentStack, + breakOnConsoleErrors, + showInlineWarningsAndErrors, + hideConsoleLogsInStrictMode, + browserTheme, + } = parsedValue; + return { + appendComponentStack: castBool(appendComponentStack) ?? true, + breakOnConsoleErrors: castBool(breakOnConsoleErrors) ?? false, + showInlineWarningsAndErrors: castBool(showInlineWarningsAndErrors) ?? true, + hideConsoleLogsInStrictMode: castBool(hideConsoleLogsInStrictMode) ?? false, + browserTheme: castBrowserTheme(browserTheme) ?? 'dark', + }; +} + +export function cacheConsolePatchSettings( + devToolsSettingsManager: DevToolsSettingsManager, + value: ConsolePatchSettings, +): void { + if (devToolsSettingsManager.setConsolePatchSettings == null) { + return; + } + devToolsSettingsManager.setConsolePatchSettings(JSON.stringify(value)); +} diff --git a/packages/react-devtools-shared/src/backend/agent.js b/packages/react-devtools-shared/src/backend/agent.js index 13394da2aea85..46dc844e35520 100644 --- a/packages/react-devtools-shared/src/backend/agent.js +++ b/packages/react-devtools-shared/src/backend/agent.js @@ -25,7 +25,7 @@ import { initialize as setupTraceUpdates, toggleEnabled as setTraceUpdatesEnabled, } from './views/TraceUpdates'; -import {patch as patchConsole} from './console'; +import {patch as patchConsole, type ConsolePatchSettings} from './console'; import {currentBridgeProtocol} from 'react-devtools-shared/src/bridge'; import type {BackendBridge} from 'react-devtools-shared/src/bridge'; @@ -712,11 +712,11 @@ export default class Agent extends EventEmitter<{ showInlineWarningsAndErrors, hideConsoleLogsInStrictMode, browserTheme, - }) => { - // If the frontend preference has change, - // or in the case of React Native- if the backend is just finding out the preference- + }: ConsolePatchSettings) => { + // If the frontend preferences have changed, + // or in the case of React Native- if the backend is just finding out the preferences- // then reinstall the console overrides. - // It's safe to call these methods multiple times, so we don't need to worry about that. + // It's safe to call `patchConsole` multiple times. patchConsole({ appendComponentStack, breakOnConsoleErrors, diff --git a/packages/react-devtools-shared/src/backend/console.js b/packages/react-devtools-shared/src/backend/console.js index 025180b872dae..cdf7799bbcdf2 100644 --- a/packages/react-devtools-shared/src/backend/console.js +++ b/packages/react-devtools-shared/src/backend/console.js @@ -15,6 +15,7 @@ import {format, formatWithStyles} from './utils'; import {getInternalReactConstants} from './renderer'; import {getStackByFiberInDevAndProd} from './DevToolsFiberComponentStack'; import {consoleManagedByDevToolsDuringStrictMode} from 'react-devtools-feature-flags'; +import {castBool, castBrowserTheme} from '../utils'; const OVERRIDE_CONSOLE_METHODS = ['error', 'trace', 'warn']; const DIMMED_NODE_CONSOLE_COLOR = '\x1b[2m%s\x1b[0m'; @@ -143,6 +144,14 @@ const consoleSettingsRef = { browserTheme: 'dark', }; +export type ConsolePatchSettings = { + appendComponentStack: boolean, + breakOnConsoleErrors: boolean, + showInlineWarningsAndErrors: boolean, + hideConsoleLogsInStrictMode: boolean, + browserTheme: BrowserTheme, +}; + // Patches console methods to append component stack for the current fiber. // Call unpatch() to remove the injected behavior. export function patch({ @@ -151,13 +160,7 @@ export function patch({ showInlineWarningsAndErrors, hideConsoleLogsInStrictMode, browserTheme, -}: { - appendComponentStack: boolean, - breakOnConsoleErrors: boolean, - showInlineWarningsAndErrors: boolean, - hideConsoleLogsInStrictMode: boolean, - browserTheme: BrowserTheme, -}): void { +}: ConsolePatchSettings): void { // Settings may change after we've patched the console. // Using a shared ref allows the patch function to read the latest values. consoleSettingsRef.appendComponentStack = appendComponentStack; @@ -390,14 +393,19 @@ export function patchConsoleUsingWindowValues() { }); } -function castBool(v: any): ?boolean { - if (v === true || v === false) { - return v; - } -} - -function castBrowserTheme(v: any): ?BrowserTheme { - if (v === 'light' || v === 'dark' || v === 'auto') { - return v; - } +// After receiving cached console patch settings from React Native, we set them on window. +// When the console is initially patched (in renderer.js and hook.js), these values are read. +// The browser extension (etc.) sets these values on window, but through another method. +export function writeConsolePatchSettingsToWindow( + settings: ConsolePatchSettings, +): void { + window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = + settings.appendComponentStack; + window.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ = + settings.breakOnConsoleErrors; + window.__REACT_DEVTOOLS_SHOW_INLINE_WARNINGS_AND_ERRORS__ = + settings.showInlineWarningsAndErrors; + window.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = + settings.hideConsoleLogsInStrictMode; + window.__REACT_DEVTOOLS_BROWSER_THEME__ = settings.browserTheme; } diff --git a/packages/react-devtools-shared/src/bridge.js b/packages/react-devtools-shared/src/bridge.js index b5dd931ef0a3b..999d6508ca363 100644 --- a/packages/react-devtools-shared/src/bridge.js +++ b/packages/react-devtools-shared/src/bridge.js @@ -17,7 +17,7 @@ import type { RendererID, } from 'react-devtools-shared/src/backend/types'; import type {StyleAndLayout as StyleAndLayoutPayload} from 'react-devtools-shared/src/backend/NativeStyleEditor/types'; -import type {BrowserTheme} from 'react-devtools-shared/src/devtools/views/DevTools'; +import type {ConsolePatchSettings} from 'react-devtools-shared/src/backend/console'; const BATCH_DURATION = 100; @@ -171,14 +171,6 @@ type NativeStyleEditor_SetValueParams = { value: string, }; -type UpdateConsolePatchSettingsParams = { - appendComponentStack: boolean, - breakOnConsoleErrors: boolean, - showInlineWarningsAndErrors: boolean, - hideConsoleLogsInStrictMode: boolean, - browserTheme: BrowserTheme, -}; - type SavedPreferencesParams = { appendComponentStack: boolean, breakOnConsoleErrors: boolean, @@ -247,7 +239,7 @@ type FrontendEvents = { stopProfiling: [], storeAsGlobal: [StoreAsGlobalParams], updateComponentFilters: [Array], - updateConsolePatchSettings: [UpdateConsolePatchSettingsParams], + updateConsolePatchSettings: [ConsolePatchSettings], viewAttributeSource: [ViewAttributeSourceParams], viewElementSource: [ElementAndRendererID], diff --git a/packages/react-devtools-shared/src/utils.js b/packages/react-devtools-shared/src/utils.js index 064a57d3f3458..0a36b2b3c1dae 100644 --- a/packages/react-devtools-shared/src/utils.js +++ b/packages/react-devtools-shared/src/utils.js @@ -55,6 +55,7 @@ import isArray from './isArray'; import type {ComponentFilter, ElementType} from './types'; import type {LRUCache} from 'react-devtools-shared/src/types'; +import type {BrowserTheme} from 'react-devtools-shared/src/devtools/views/DevTools'; // $FlowFixMe[method-unbinding] const hasOwnProperty = Object.prototype.hasOwnProperty; @@ -353,6 +354,18 @@ function parseBool(s: ?string): ?boolean { } } +export function castBool(v: any): ?boolean { + if (v === true || v === false) { + return v; + } +} + +export function castBrowserTheme(v: any): ?BrowserTheme { + if (v === 'light' || v === 'dark' || v === 'auto') { + return v; + } +} + export function getAppendComponentStack(): boolean { const raw = localStorageGetItem( LOCAL_STORAGE_SHOULD_APPEND_COMPONENT_STACK_KEY,