Inject early on when reloading-and-profiling

This commit is contained in:
Brian Vaughn
2019-04-01 07:48:04 -07:00
parent 37ed5a7175
commit 2c14f3e88e
10 changed files with 107 additions and 20 deletions

View File

@@ -27,7 +27,12 @@
"devtools_page": "main.html",
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"web_accessible_resources": ["main.html", "panel.html", "build/backend.js"],
"web_accessible_resources": [
"main.html",
"panel.html",
"build/backend.js",
"build/renderer.js"
],
"background": {
"scripts": ["build/background.js"],

View File

@@ -33,7 +33,12 @@
"devtools_page": "main.html",
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"web_accessible_resources": ["main.html", "panel.html", "build/backend.js"],
"web_accessible_resources": [
"main.html",
"panel.html",
"build/backend.js",
"build/renderer.js"
],
"background": {
"scripts": ["build/background.js"],

View File

@@ -2,13 +2,24 @@
import nullthrows from 'nullthrows';
import { installHook } from 'src/hook';
import { RELOAD_AND_PROFILE_KEY } from 'src/constants';
function injectCode(code) {
const script = document.createElement('script');
script.textContent = code;
// This script runs before the <head> element is created,
// so we add the script to <html> instead.
nullthrows(document.documentElement).appendChild(script);
nullthrows(script.parentNode).removeChild(script);
}
let lastDetectionResult;
// We want to detect when a renderer attaches, and notify the "background
// page" (which is shared between tabs and can highlight the React icon).
// Currently we are in "content script" context, so we can't listen
// to the hook directly (it will be injected directly into the page).
// We want to detect when a renderer attaches, and notify the "background page"
// (which is shared between tabs and can highlight the React icon).
// Currently we are in "content script" context, so we can't listen to the hook directly
// (it will be injected directly into the page).
// So instead, the hook will use postMessage() to pass message to us here.
// And when this happens, we'll send a message to the "background page".
window.addEventListener('message', function(evt) {
@@ -51,14 +62,27 @@ window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeWeakMap = WeakMap;
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') {
const rendererURL = chrome.runtime.getURL('build/renderer.js');
let rendererCode;
// We need to inject in time to catch the initial mount.
// This means we need to synchronously read the renderer code itself,
// and synchronously inject it into the page.
// There are very few ways to actually do this.
// This seems to be the best approach.
const request = new XMLHttpRequest();
request.addEventListener('load', function() {
rendererCode = this.responseText;
});
request.open('GET', rendererURL, false);
request.send();
injectCode(rendererCode);
}
// Inject a `__REACT_DEVTOOLS_GLOBAL_HOOK__` global so that React can detect that the
// devtools are installed (and skip its suggestion to install the devtools).
const js =
';(' + installHook.toString() + '(window))' + saveNativeValues + detectReact;
// This script runs before the <head> element is created, so we add the script
// to <html> instead.
const script = document.createElement('script');
script.textContent = js;
nullthrows(document.documentElement).appendChild(script);
nullthrows(script.parentNode).removeChild(script);
injectCode(
';(' + installHook.toString() + '(window))' + saveNativeValues + detectReact
);

View File

@@ -0,0 +1,21 @@
/**
* Install the hook on window, which is an event emitter.
* Note because Chrome content scripts cannot directly modify the window object,
* we are evaling this function by inserting a script tag.
* That's why we have to inline the whole event emitter implementation here.
*
* @flow
*/
import { attach } from 'src/backend/renderer';
Object.defineProperty(
window,
'__REACT_DEVTOOLS_ATTACH__',
({
enumerable: false,
get() {
return attach;
},
}: Object)
);

View File

@@ -18,6 +18,7 @@ module.exports = {
inject: './src/GlobalHook.js',
main: './src/main.js',
panel: './src/panel.js',
renderer: './src/renderer.js',
},
output: {
path: __dirname + '/build',

View File

@@ -1,7 +1,7 @@
// @flow
import EventEmitter from 'events';
import { __DEBUG__ } from '../constants';
import { RELOAD_AND_PROFILE_KEY, __DEBUG__ } from '../constants';
import { hideOverlay, showOverlay } from './views/Highlighter';
import type { RendererID, RendererInterface } from './types';
@@ -38,8 +38,6 @@ type SetInParams = {|
value: any,
|};
const RELOAD_AND_PROFILE_KEY = 'React::DevTools::reloadAndProfile';
export default class Agent extends EventEmitter {
_bridge: Bridge = ((null: any): Bridge);
_isProfiling: boolean = false;

View File

@@ -33,8 +33,16 @@ export function initBackend(
];
const attachRenderer = (id: number, renderer: ReactRenderer) => {
const rendererInterface = attach(hook, id, renderer, global);
hook.rendererInterfaces.set(id, rendererInterface);
let rendererInterface = hook.rendererInterfaces.get(id);
// Inject any not-yet-injected renderers (if we didn't reload-and-profile)
if (!rendererInterface) {
rendererInterface = attach(hook, id, renderer, global);
hook.rendererInterfaces.set(id, rendererInterface);
}
// Notify the DevTools frontend about any renderers that were attached early.
hook.emit('renderer-attached', {
id,
renderer,

View File

@@ -1595,6 +1595,10 @@ export function attach(
}
function startProfiling() {
if (isProfiling) {
return;
}
// Capture initial values as of the time profiling starts.
// It's important we snapshot both the durations and the id-to-root map,
// since either of these may change during the profiling session
@@ -1611,6 +1615,11 @@ export function attach(
isProfiling = false;
}
// Automatically start profiling so that we don't miss timing info from initial "mount".
if (localStorage.getItem('React::DevTools::reloadAndProfile') === 'true') {
startProfiling();
}
return {
cleanup,
getCommitDetails,

View File

@@ -5,4 +5,6 @@ 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 __DEBUG__ = false;

View File

@@ -79,6 +79,20 @@ export function installHook(target: any): DevToolsHook | null {
hook.emit('renderer', { id, renderer, reactBuildType });
// If we have just reloaded to profile, we need to inject the renderer interface before the app loads.
// Otherwise the renderer won't yet exist and we can skip this step.
const attach = target.__REACT_DEVTOOLS_ATTACH__;
if (typeof attach === 'function') {
const rendererInterface = attach(hook, id, renderer, target);
hook.rendererInterfaces.set(id, rendererInterface);
/*hook.emit('renderer-attached', {
id,
renderer,
rendererInterface,
});*/
}
return id;
}