mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Added option to disable screenshot capturing while profiling
This commit is contained in:
@@ -56,6 +56,7 @@
|
||||
"cli-spinners": "^1.0.0",
|
||||
"clipboard-js": "^0.3.6",
|
||||
"css-loader": "^1.0.1",
|
||||
"html2canvas": "^1.0.0-alpha.12",
|
||||
"error-stack-parser": "^2.0.2",
|
||||
"es6-symbol": "3.0.2",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import nullthrows from 'nullthrows';
|
||||
import { installHook } from 'src/hook';
|
||||
import { RELOAD_AND_PROFILE_KEY } from 'src/constants';
|
||||
import { LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY } from 'src/constants';
|
||||
|
||||
function injectCode(code) {
|
||||
const script = document.createElement('script');
|
||||
@@ -63,7 +63,7 @@ window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeSet = Set;
|
||||
`;
|
||||
|
||||
// If we have just reloaded to profile, we need to inject the renderer interface before the app loads.
|
||||
if (localStorage.getItem(RELOAD_AND_PROFILE_KEY) === 'true') {
|
||||
if (localStorage.getItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') {
|
||||
const rendererURL = chrome.runtime.getURL('build/renderer.js');
|
||||
let rendererCode;
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ import {
|
||||
} from './utils';
|
||||
import DevTools from 'src/devtools/views/DevTools';
|
||||
|
||||
const SUPPORTS_PROFILING_KEY = 'React::DevTools::supportsProfiling';
|
||||
const LOCAL_STORAGE_SUPPORTS_PROFILING_KEY =
|
||||
'React::DevTools::supportsProfiling';
|
||||
|
||||
let panelCreated = false;
|
||||
|
||||
@@ -64,7 +65,7 @@ function createPanelIfReactLoaded() {
|
||||
},
|
||||
});
|
||||
bridge.addListener('reloadAppForProfiling', () => {
|
||||
localStorage.setItem(SUPPORTS_PROFILING_KEY, 'true');
|
||||
localStorage.setItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY, 'true');
|
||||
chrome.devtools.inspectedWindow.eval('window.location.reload();');
|
||||
});
|
||||
bridge.addListener('exportFile', ({ contents, filename }) => {
|
||||
@@ -89,16 +90,19 @@ function createPanelIfReactLoaded() {
|
||||
// after a user has clicked the "reload and profile" button.
|
||||
let isProfiling = false;
|
||||
let supportsProfiling = false;
|
||||
if (localStorage.getItem(SUPPORTS_PROFILING_KEY) === 'true') {
|
||||
if (
|
||||
localStorage.getItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY) === 'true'
|
||||
) {
|
||||
supportsProfiling = true;
|
||||
isProfiling = true;
|
||||
localStorage.removeItem(SUPPORTS_PROFILING_KEY);
|
||||
localStorage.removeItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY);
|
||||
}
|
||||
|
||||
const browserName = getBrowserName();
|
||||
|
||||
store = new Store(bridge, {
|
||||
isProfiling,
|
||||
supportsCaptureScreenshots: true,
|
||||
supportsFileDownloads: browserName === 'Chrome',
|
||||
supportsReloadAndProfile: true,
|
||||
supportsProfiling,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/** @flow */
|
||||
|
||||
import html2canvas from 'html2canvas';
|
||||
import Agent from 'src/backend/agent';
|
||||
import Bridge from 'src/bridge';
|
||||
import { initBackend } from 'src/backend';
|
||||
@@ -15,6 +16,15 @@ const bridge = new Bridge({
|
||||
},
|
||||
});
|
||||
|
||||
bridge.addListener('captureScreenshot', ({ commitIndex }) => {
|
||||
html2canvas(document.body, { logging: false }).then(canvas => {
|
||||
bridge.send('screenshotCaptured', {
|
||||
commitIndex,
|
||||
dataURL: canvas.toDataURL(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const agent = new Agent();
|
||||
agent.addBridge(bridge);
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ inject('./build/app.js', () => {
|
||||
|
||||
cb(bridge);
|
||||
|
||||
const store = new Store(bridge);
|
||||
const store = new Store(bridge, { supportsCaptureScreenshots: true });
|
||||
|
||||
const root = createRoot(container);
|
||||
const batch = root.createBatch();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import EventEmitter from 'events';
|
||||
import { RELOAD_AND_PROFILE_KEY, __DEBUG__ } from '../constants';
|
||||
import { LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY, __DEBUG__ } from '../constants';
|
||||
import { hideOverlay, showOverlay } from './views/Highlighter';
|
||||
|
||||
import type { RendererID, RendererInterface } from './types';
|
||||
@@ -46,10 +46,10 @@ export default class Agent extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (localStorage.getItem(RELOAD_AND_PROFILE_KEY) === 'true') {
|
||||
if (localStorage.getItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') {
|
||||
this._isProfiling = true;
|
||||
|
||||
localStorage.removeItem(RELOAD_AND_PROFILE_KEY);
|
||||
localStorage.removeItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ export default class Agent extends EventEmitter {
|
||||
};
|
||||
|
||||
reloadAndProfile = () => {
|
||||
localStorage.setItem(RELOAD_AND_PROFILE_KEY, 'true');
|
||||
localStorage.setItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY, 'true');
|
||||
|
||||
// This code path should only be hit if the shell has explicitly told the Store that it supports profiling.
|
||||
// In that case, the shell must also listen for this specific message to know when it needs to reload the app.
|
||||
|
||||
@@ -18,7 +18,7 @@ import { getDisplayName, utfEncodeString } from '../utils';
|
||||
import { cleanForBridge, copyWithSet, setInObject } from './utils';
|
||||
import {
|
||||
__DEBUG__,
|
||||
RELOAD_AND_PROFILE_KEY,
|
||||
LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY,
|
||||
TREE_OPERATION_ADD,
|
||||
TREE_OPERATION_REMOVE,
|
||||
TREE_OPERATION_RESET_CHILDREN,
|
||||
@@ -1642,7 +1642,7 @@ export function attach(
|
||||
}
|
||||
|
||||
// Automatically start profiling so that we don't miss timing info from initial "mount".
|
||||
if (localStorage.getItem(RELOAD_AND_PROFILE_KEY) === 'true') {
|
||||
if (localStorage.getItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') {
|
||||
startProfiling();
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -5,6 +5,7 @@ export const TREE_OPERATION_REMOVE = 2;
|
||||
export const TREE_OPERATION_RESET_CHILDREN = 3;
|
||||
export const TREE_OPERATION_UPDATE_TREE_BASE_DURATION = 4;
|
||||
|
||||
export const RELOAD_AND_PROFILE_KEY = 'React::DevTools::reloadAndProfile';
|
||||
export const LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY =
|
||||
'React::DevTools::reloadAndProfile';
|
||||
|
||||
export const __DEBUG__ = false;
|
||||
|
||||
+35
-1
@@ -31,8 +31,12 @@ const debug = (methodName, ...args) => {
|
||||
}
|
||||
};
|
||||
|
||||
const LOCAL_STORAGE_CAPTURE_SCREENSHOTS_KEY =
|
||||
'React::DevTools::captureScreenshots';
|
||||
|
||||
type Config = {|
|
||||
isProfiling?: boolean,
|
||||
supportsCaptureScreenshots?: boolean,
|
||||
supportsFileDownloads?: boolean,
|
||||
supportsReloadAndProfile?: boolean,
|
||||
supportsProfiling?: boolean,
|
||||
@@ -49,6 +53,8 @@ export type Capabilities = {|
|
||||
export default class Store extends EventEmitter {
|
||||
_bridge: Bridge;
|
||||
|
||||
_captureScreenshots: boolean = false;
|
||||
|
||||
// Map of ID to Element.
|
||||
// Elements are mutable (for now) to avoid excessive cloning during tree updates.
|
||||
_idToElement: Map<number, Element> = new Map();
|
||||
@@ -96,6 +102,7 @@ export default class Store extends EventEmitter {
|
||||
|
||||
// These options may be initially set by a confiugraiton option when constructing the Store.
|
||||
// In the case of "supportsProfiling", the option may be updated based on the injected renderers.
|
||||
_supportsCaptureScreenshots: boolean = false;
|
||||
_supportsFileDownloads: boolean = false;
|
||||
_supportsProfiling: boolean = false;
|
||||
_supportsReloadAndProfile: boolean = false;
|
||||
@@ -108,6 +115,7 @@ export default class Store extends EventEmitter {
|
||||
if (config != null) {
|
||||
const {
|
||||
isProfiling,
|
||||
supportsCaptureScreenshots,
|
||||
supportsFileDownloads,
|
||||
supportsProfiling,
|
||||
supportsReloadAndProfile,
|
||||
@@ -115,6 +123,12 @@ export default class Store extends EventEmitter {
|
||||
if (isProfiling) {
|
||||
this._isProfiling = true;
|
||||
}
|
||||
if (supportsCaptureScreenshots) {
|
||||
this._supportsCaptureScreenshots = true;
|
||||
this._captureScreenshots =
|
||||
localStorage.getItem(LOCAL_STORAGE_CAPTURE_SCREENSHOTS_KEY) !==
|
||||
'false';
|
||||
}
|
||||
if (supportsFileDownloads) {
|
||||
this._supportsFileDownloads = true;
|
||||
}
|
||||
@@ -139,6 +153,20 @@ export default class Store extends EventEmitter {
|
||||
this._profilingCache = new ProfilingCache(bridge, this);
|
||||
}
|
||||
|
||||
get captureScreenshots(): boolean {
|
||||
return this._captureScreenshots;
|
||||
}
|
||||
set captureScreenshots(value: boolean): void {
|
||||
this._captureScreenshots = value;
|
||||
|
||||
localStorage.setItem(
|
||||
LOCAL_STORAGE_CAPTURE_SCREENSHOTS_KEY,
|
||||
value ? 'true' : 'false'
|
||||
);
|
||||
|
||||
this.emit('captureScreenshots');
|
||||
}
|
||||
|
||||
// Profiling data has been recorded for at least one root.
|
||||
get hasProfilingData(): boolean {
|
||||
return (
|
||||
@@ -190,6 +218,10 @@ export default class Store extends EventEmitter {
|
||||
return this._roots;
|
||||
}
|
||||
|
||||
get supportsCaptureScreenshots(): boolean {
|
||||
return this._supportsCaptureScreenshots;
|
||||
}
|
||||
|
||||
get supportsFileDownloads(): boolean {
|
||||
return this._supportsFileDownloads;
|
||||
}
|
||||
@@ -419,7 +451,9 @@ export default class Store extends EventEmitter {
|
||||
|
||||
const commitIndex = profilingOperations.length - 1;
|
||||
|
||||
this._bridge.send('captureScreenshot', { commitIndex });
|
||||
if (this._captureScreenshots) {
|
||||
this._bridge.send('captureScreenshot', { commitIndex });
|
||||
}
|
||||
}
|
||||
|
||||
let addedElementIDs: Uint32Array = new Uint32Array(0);
|
||||
|
||||
@@ -55,6 +55,9 @@
|
||||
|
||||
.Screenshot {
|
||||
width: 100%;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Modal {
|
||||
@@ -73,4 +76,6 @@
|
||||
.ModalImage {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback, useContext } from 'react';
|
||||
import React, { useCallback, useContext, useMemo } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useSubscription } from '../hooks';
|
||||
import { StoreContext } from '../context';
|
||||
import { SettingsContext } from './SettingsContext';
|
||||
import Store from 'src/devtools/store';
|
||||
|
||||
import styles from './Settings.css';
|
||||
|
||||
@@ -11,10 +14,23 @@ export type Props = {|
|
||||
|};
|
||||
|
||||
export default function Settings({ portalContainer }: Props) {
|
||||
const store = useContext(StoreContext);
|
||||
const { displayDensity, setDisplayDensity, theme, setTheme } = useContext(
|
||||
SettingsContext
|
||||
);
|
||||
|
||||
const subscription = useMemo(
|
||||
() => ({
|
||||
getCurrentValue: () => store.captureScreenshots,
|
||||
subscribe: (callback: Function) => {
|
||||
store.addListener('captureScreenshots', callback);
|
||||
return () => store.removeListener('captureScreenshots', callback);
|
||||
},
|
||||
}),
|
||||
[store]
|
||||
);
|
||||
const captureScreenshots = useSubscription<boolean, Store>(subscription);
|
||||
|
||||
const updateDisplayDensity = useCallback(
|
||||
({ currentTarget }) => {
|
||||
setDisplayDensity(currentTarget.value);
|
||||
@@ -29,6 +45,13 @@ export default function Settings({ portalContainer }: Props) {
|
||||
[setTheme]
|
||||
);
|
||||
|
||||
const updateCaptureScreenshotsWhileProfiling = useCallback(
|
||||
({ currentTarget }) => {
|
||||
store.captureScreenshots = currentTarget.checked;
|
||||
},
|
||||
[store]
|
||||
);
|
||||
|
||||
const children = (
|
||||
<div className={styles.Settings}>
|
||||
<div className={styles.Section}>
|
||||
@@ -91,6 +114,21 @@ export default function Settings({ portalContainer }: Props) {
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{store.supportsCaptureScreenshots && (
|
||||
<div className={styles.Section}>
|
||||
<div className={styles.Header}>Profiler</div>
|
||||
<div className={styles.OptionGroup}>
|
||||
<label className={styles.Option}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={captureScreenshots}
|
||||
onChange={updateCaptureScreenshotsWhileProfiling}
|
||||
/>{' '}
|
||||
Capture screenshots while profiling
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -2038,6 +2038,11 @@ balanced-match@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||
|
||||
base64-arraybuffer@^0.1.5:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
|
||||
integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg=
|
||||
|
||||
base64-js@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
|
||||
@@ -3259,6 +3264,13 @@ crypto-random-string@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
|
||||
|
||||
css-line-break@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-1.0.1.tgz#19f2063a33e95fb2831b86446c0b80c188af450a"
|
||||
integrity sha1-GfIGOjPpX7KDG4ZEbAuAwYivRQo=
|
||||
dependencies:
|
||||
base64-arraybuffer "^0.1.5"
|
||||
|
||||
css-loader@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-1.0.1.tgz#6885bb5233b35ec47b006057da01cc640b6b79fe"
|
||||
@@ -5392,6 +5404,13 @@ html-encoding-sniffer@^1.0.2:
|
||||
dependencies:
|
||||
whatwg-encoding "^1.0.1"
|
||||
|
||||
html2canvas@^1.0.0-alpha.12:
|
||||
version "1.0.0-alpha.12"
|
||||
resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.0.0-alpha.12.tgz#3b1992e3c9b3f56063c35fd620494f37eba88513"
|
||||
integrity sha1-OxmS48mz9WBjw1/WIElPN+uohRM=
|
||||
dependencies:
|
||||
css-line-break "1.0.1"
|
||||
|
||||
htmlparser2@^3.9.1:
|
||||
version "3.9.2"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
|
||||
|
||||
Reference in New Issue
Block a user