Added option to disable screenshot capturing while profiling

This commit is contained in:
Brian Vaughn
2019-04-03 14:32:16 -07:00
parent 07bf8e53c1
commit ed6e34da8e
12 changed files with 128 additions and 16 deletions
+1
View File
@@ -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;
+8 -4
View File
@@ -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,
+10
View File
@@ -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);
+1 -1
View File
@@ -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();
+4 -4
View File
@@ -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.
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+39 -1
View File
@@ -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>
);
+19
View File
@@ -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"