diff --git a/packages/react-devtools-shared/src/backend/agent.js b/packages/react-devtools-shared/src/backend/agent.js index 41993700252cf..86c35ae1b49bd 100644 --- a/packages/react-devtools-shared/src/backend/agent.js +++ b/packages/react-devtools-shared/src/backend/agent.js @@ -38,6 +38,7 @@ import type { RendererInterface, } from './types'; import type {ComponentFilter} from '../types'; +import {isSynchronousXHRSupported} from './utils'; const debug = (methodName, ...args) => { if (__DEBUG__) { @@ -221,6 +222,7 @@ export default class Agent extends EventEmitter<{| isBackendStorageAPISupported = true; } catch (error) {} bridge.send('isBackendStorageAPISupported', isBackendStorageAPISupported); + bridge.send('isSynchronousXHRSupported', isSynchronousXHRSupported()); setupHighlighter(bridge, this); setupTraceUpdates(this); diff --git a/packages/react-devtools-shared/src/backend/utils.js b/packages/react-devtools-shared/src/backend/utils.js index 06329eff72150..7a4ff6295ab99 100644 --- a/packages/react-devtools-shared/src/backend/utils.js +++ b/packages/react-devtools-shared/src/backend/utils.js @@ -183,3 +183,11 @@ export function format( return '' + formatted; } + +export function isSynchronousXHRSupported(): boolean { + return !!( + window.document && + window.document.featurePolicy && + window.document.featurePolicy.allowsFeature('sync-xhr') + ); +} diff --git a/packages/react-devtools-shared/src/bridge.js b/packages/react-devtools-shared/src/bridge.js index f69414ad47086..d3b669bd42871 100644 --- a/packages/react-devtools-shared/src/bridge.js +++ b/packages/react-devtools-shared/src/bridge.js @@ -122,6 +122,7 @@ export type BackendEvents = {| extensionBackendInitialized: [], inspectedElement: [InspectedElementPayload], isBackendStorageAPISupported: [boolean], + isSynchronousXHRSupported: [boolean], operations: [Array], ownersList: [OwnersList], overrideComponentFilters: [Array], diff --git a/packages/react-devtools-shared/src/devtools/store.js b/packages/react-devtools-shared/src/devtools/store.js index e5a6ac15fbde1..c0083f90a16e4 100644 --- a/packages/react-devtools-shared/src/devtools/store.js +++ b/packages/react-devtools-shared/src/devtools/store.js @@ -111,6 +111,12 @@ export default class Store extends EventEmitter<{| // If not, features like reload-and-profile will not work correctly and must be disabled. _isBackendStorageAPISupported: boolean = false; + // Can DevTools use sync XHR requests? + // If not, features like reload-and-profile will not work correctly and must be disabled. + // This current limitation applies only to web extension builds + // and will need to be reconsidered in the future if we add support for reload to React Native. + _isSynchronousXHRSupported: boolean = false; + _nativeStyleEditorValidAttributes: $ReadOnlyArray | null = null; // Map of element (id) to the set of elements (ids) it owns. @@ -195,12 +201,16 @@ export default class Store extends EventEmitter<{| bridge.addListener('shutdown', this.onBridgeShutdown); bridge.addListener( 'isBackendStorageAPISupported', - this.onBridgeStorageSupported, + this.onBackendStorageAPISupported, ); bridge.addListener( 'isNativeStyleEditorSupported', this.onBridgeNativeStyleEditorSupported, ); + bridge.addListener( + 'isSynchronousXHRSupported', + this.onBridgeSynchronousXHRSupported, + ); bridge.addListener( 'unsupportedRendererVersion', this.onBridgeUnsupportedRendererVersion, @@ -359,11 +369,16 @@ export default class Store extends EventEmitter<{| get supportsProfiling(): boolean { return this._supportsProfiling; } + get supportsReloadAndProfile(): boolean { // Does the DevTools shell support reloading and eagerly injecting the renderer interface? - // And if so, can the backend use the localStorage API? - // Both of these are required for the reload-and-profile feature to work. - return this._supportsReloadAndProfile && this._isBackendStorageAPISupported; + // And if so, can the backend use the localStorage API and sync XHR? + // All of these are currently required for the reload-and-profile feature to work. + return ( + this._supportsReloadAndProfile && + this._isBackendStorageAPISupported && + this._isSynchronousXHRSupported + ); } get supportsTraceUpdates(): boolean { @@ -1130,20 +1145,43 @@ export default class Store extends EventEmitter<{| debug('onBridgeShutdown', 'unsubscribing from Bridge'); } - this._bridge.removeListener('operations', this.onBridgeOperations); - this._bridge.removeListener('shutdown', this.onBridgeShutdown); - this._bridge.removeListener( + const bridge = this._bridge; + bridge.removeListener('operations', this.onBridgeOperations); + bridge.removeListener( + 'overrideComponentFilters', + this.onBridgeOverrideComponentFilters, + ); + bridge.removeListener('shutdown', this.onBridgeShutdown); + bridge.removeListener( 'isBackendStorageAPISupported', - this.onBridgeStorageSupported, + this.onBackendStorageAPISupported, + ); + bridge.removeListener( + 'isNativeStyleEditorSupported', + this.onBridgeNativeStyleEditorSupported, + ); + bridge.removeListener( + 'isSynchronousXHRSupported', + this.onBridgeSynchronousXHRSupported, + ); + bridge.removeListener( + 'unsupportedRendererVersion', + this.onBridgeUnsupportedRendererVersion, ); }; - onBridgeStorageSupported = (isBackendStorageAPISupported: boolean) => { + onBackendStorageAPISupported = (isBackendStorageAPISupported: boolean) => { this._isBackendStorageAPISupported = isBackendStorageAPISupported; this.emit('supportsReloadAndProfile'); }; + onBridgeSynchronousXHRSupported = (isSynchronousXHRSupported: boolean) => { + this._isSynchronousXHRSupported = isSynchronousXHRSupported; + + this.emit('supportsReloadAndProfile'); + }; + onBridgeUnsupportedRendererVersion = () => { this._unsupportedRendererVersionDetected = true;