@samitouri / QOS-React / commits / d57ec69561

Added initial Chrome/Firefox shells

Brian Vaughn committed Jan 23, 2019 at 18:06 UTC d57ec6956158037e7d69bf65175abcfcc9c7ee03
68 files changed +1032 -168
.eslintignore
+2
@@ -1,5 +1,7 @@
1 node_modules
2
3 +shells/browser/chrome/build
4 +shells/browser/firefox/build
5 shells/dev/build
6
7 package-lock.json
.flowconfig
+3
@@ -13,6 +13,9 @@
13 .*node_modules/webpack.*
14 .*node_modules/fbjs/flow.*
15 .*node_modules/web-ext.*
16 +shells/browser/chrome/build/*
17 +shells/browser/firefox/build/*
18 +shells/dev/build/*
19
20 [include]
21
.prettierignore
+2
@@ -1,5 +1,7 @@
1 node_modules
2
3 +shells/browser/chrome/build
4 +shells/browser/firefox/build
5 shells/dev/build
6
7 package-lock.json
package.json
+4 -5
@@ -12,18 +12,17 @@
12 },
13 "scripts": {
14 "build:extension": "yarn run build:extension:chrome && yarn run build:extension:firefox",
15 - "build:extension:chrome": "node ./shells/chrome/build",
16 - "build:extension:firefox": "node ./shells/firefox/build",
15 + "build:extension:chrome": "node ./shells/browser/chrome/build",
16 + "build:extension:firefox": "node ./shells/browser/firefox/build",
17 "build:standalone": "cd packages/react-devtools-core && yarn run build",
18 - "deploy": "cd ./shells/theme-preview && ./build.sh && gh-pages -d .",
18 "linc": "lint-staged",
19 "lint": "eslint '**/*.js'",
20 "precommit": "lint-staged",
21 "prettier": "prettier --write '**/*.{js,json,css}'",
22 "start": "cd ./shells/dev && open ./index.html && webpack --config webpack.config.js --watch",
23 "test": "jest",
25 - "test:chrome": "node ./shells/chrome/test",
26 - "test:firefox": "node ./shells/firefox/test",
24 + "test:chrome": "node ./shells/browser/chrome/test",
25 + "test:firefox": "node ./shells/browser/firefox/test",
26 "test:standalone": "cd packages/react-devtools && yarn start",
27 "typecheck": "flow check"
28 },
shells/browser/chrome/README.md new
+12
@@ -0,0 +1,12 @@
1 +# The Chrome extension
2 +
3 +The source code for this extension has moved to `shells/webextension`.
4 +
5 +Modify the source code there and then rebuild this extension by running `node build` from this directory or `yarn run build:extension:chrome` from the root directory.
6 +
7 +## Testing in Chrome
8 +
9 +You can test a local build of the web extension like so:
10 +
11 + 1. Build the extension: `node build`
12 + 1. Follow the on-screen instructions.
shells/browser/chrome/build.js new
+20
@@ -0,0 +1,20 @@
1 +#!/usr/bin/env node
2 +
3 +const chalk = require('chalk');
4 +const { join } = require('path');
5 +const build = require('../shared/build');
6 +
7 +const main = async () => {
8 + await build(
9 + 'chrome',
10 + join(__dirname, 'manifest.json'),
11 + join(__dirname, 'build')
12 + );
13 +
14 + console.log(chalk.green('\nThe Chrome extension has been built!'));
15 + console.log(chalk.green('You can test this build by running:'));
16 + console.log(chalk.gray('\n# From the react-devtools root directory:'));
17 + console.log('yarn run test:chrome');
18 +};
19 +
20 +main();
shells/browser/chrome/manifest.json
+44 -4
@@ -1,6 +1,46 @@
1 {
2 - "name": "React v16 DevTools",
3 - "version": "0.1",
4 - "description": "DevTools for React version 16.0+",
5 - "manifest_version": 2
2 + "manifest_version": 2,
3 + "name": "React Developer Tools",
4 + "description": "Adds React debugging tools to the Chrome Developer Tools.",
5 + "version": "4.0.0",
6 +
7 + "minimum_chrome_version": "49",
8 +
9 + "icons": {
10 + "16": "icons/16-production.png",
11 + "32": "icons/32-production.png",
12 + "48": "icons/48-production.png",
13 + "128": "icons/128-production.png"
14 + },
15 +
16 + "browser_action": {
17 + "default_icon": {
18 + "16": "icons/16-disabled.png",
19 + "32": "icons/32-disabled.png",
20 + "48": "icons/48-disabled.png",
21 + "128": "icons/128-disabled.png"
22 + },
23 +
24 + "default_popup": "popups/disabled.html"
25 + },
26 +
27 + "devtools_page": "main.html",
28 +
29 + "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
30 + "web_accessible_resources": ["main.html", "panel.html", "build/backend.js"],
31 +
32 + "background": {
33 + "scripts": ["build/background.js"],
34 + "persistent": false
35 + },
36 +
37 + "permissions": ["file:///*", "http://*/*", "https://*/*"],
38 +
39 + "content_scripts": [
40 + {
41 + "matches": ["<all_urls>"],
42 + "js": ["build/inject.js"],
43 + "run_at": "document_start"
44 + }
45 + ]
46 }
shells/browser/chrome/test.js new
+11
@@ -0,0 +1,11 @@
1 +#!/usr/bin/env node
2 +
3 +const chromeLaunch = require('chrome-launch'); // eslint-disable-line import/no-extraneous-dependencies
4 +const { resolve } = require('path');
5 +
6 +const EXTENSION_PATH = resolve('shells/chrome/build/unpacked');
7 +const START_URL = 'https://facebook.github.io/react/';
8 +
9 +chromeLaunch(START_URL, {
10 + args: [`--load-extension=${EXTENSION_PATH}`],
11 +});
shells/browser/firefox/README.md new
+12
@@ -0,0 +1,12 @@
1 +# The Firefox extension
2 +
3 +The source code for this extension has moved to `shells/webextension`.
4 +
5 +Modify the source code there and then rebuild this extension by running `node build` from this directory or `yarn run build:extension:firefox` from the root directory.
6 +
7 +## Testing in Firefox
8 +
9 + 1. Build the extension: `node build`
10 + 1. Follow the on-screen instructions.
11 +
12 +You can test upcoming releases of Firefox by downloading the Beta or Nightly build from the [Firefox releases](https://www.mozilla.org/en-US/firefox/channel/desktop/) page and then following the on-screen instructions after building.
shells/browser/firefox/build.js new
+38
@@ -0,0 +1,38 @@
1 +#!/usr/bin/env node
2 +
3 +const chalk = require('chalk');
4 +const { join } = require('path');
5 +const build = require('../shared/build');
6 +
7 +const main = async () => {
8 + await build(
9 + 'firefox',
10 + join(__dirname, 'manifest.json'),
11 + join(__dirname, 'build')
12 + );
13 +
14 + console.log(chalk.green('\nThe Firefox extension has been built!'));
15 + console.log(chalk.green('You can test this build by running:'));
16 + console.log(chalk.gray('\n# From the react-devtools root directory:'));
17 + console.log('yarn run test:firefox');
18 + console.log(
19 + chalk.gray('\n# You can also test against upcoming Firefox releases.')
20 + );
21 + console.log(
22 + chalk.gray(
23 + '# First download a release from https://www.mozilla.org/en-US/firefox/channel/desktop/'
24 + )
25 + );
26 + console.log(
27 + chalk.gray(
28 + '# And then tell web-ext which release to use (eg firefoxdeveloperedition, nightly, beta):'
29 + )
30 + );
31 + console.log('WEB_EXT_FIREFOX=nightly yarn run test:firefox');
32 + console.log(chalk.gray('\n# You can test against older versions too:'));
33 + console.log(
34 + 'WEB_EXT_FIREFOX=/Applications/Firefox52.app/Contents/MacOS/firefox-bin yarn run test:firefox'
35 + );
36 +};
37 +
38 +main();
shells/browser/firefox/manifest.json new
+52
@@ -0,0 +1,52 @@
1 +{
2 + "manifest_version": 2,
3 + "name": "React Developer Tools",
4 + "description": "Adds React debugging tools to the Firefox Developer Tools.",
5 + "version": "4.0.0",
6 +
7 + "applications": {
8 + "gecko": {
9 + "id": "@react-devtools",
10 + "strict_min_version": "54.0"
11 + }
12 + },
13 +
14 + "icons": {
15 + "16": "icons/16-production.png",
16 + "32": "icons/32-production.png",
17 + "48": "icons/48-production.png",
18 + "128": "icons/128-production.png"
19 + },
20 +
21 + "browser_action": {
22 + "default_icon": {
23 + "16": "icons/16-disabled.png",
24 + "32": "icons/32-disabled.png",
25 + "48": "icons/48-disabled.png",
26 + "128": "icons/128-disabled.png"
27 + },
28 +
29 + "default_popup": "popups/disabled.html",
30 + "browser_style": true
31 + },
32 +
33 + "devtools_page": "main.html",
34 +
35 + "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
36 + "web_accessible_resources": ["main.html", "panel.html", "build/backend.js"],
37 +
38 + "background": {
39 + "scripts": ["build/background.js"],
40 + "persistent": false
41 + },
42 +
43 + "permissions": ["file:///*", "http://*/*", "https://*/*"],
44 +
45 + "content_scripts": [
46 + {
47 + "matches": ["<all_urls>"],
48 + "js": ["build/inject.js"],
49 + "run_at": "document_start"
50 + }
51 + ]
52 +}
shells/browser/firefox/test.js new
+46
@@ -0,0 +1,46 @@
1 +#!/usr/bin/env node
2 +
3 +const { exec } = require('child-process-promise');
4 +const { Finder } = require('firefox-profile');
5 +const { resolve } = require('path');
6 +
7 +const EXTENSION_PATH = resolve('shells/firefox/build/unpacked');
8 +const START_URL = 'https://facebook.github.io/react/';
9 +
10 +const main = async () => {
11 + const finder = new Finder();
12 +
13 + // Use default Firefox profile for testing purposes.
14 + // This prevents users from having to re-login-to sites before testing.
15 + const findPathPromise = new Promise((resolvePromise, rejectPromise) => {
16 + finder.getPath('default', (error, profile) => {
17 + if (error) {
18 + rejectPromise(error);
19 + } else {
20 + resolvePromise(profile);
21 + }
22 + });
23 + });
24 +
25 + const options = [
26 + `--source-dir=${EXTENSION_PATH}`,
27 + `--start-url=${START_URL}`,
28 + '--browser-console',
29 + ];
30 +
31 + try {
32 + const path = await findPathPromise;
33 + const trimmedPath = path.replace(' ', '\\ ');
34 + options.push(`--firefox-profile=${trimmedPath}`);
35 + } catch (err) {
36 + console.warn('Could not find default profile, using temporary profile.');
37 + }
38 +
39 + try {
40 + await exec(`web-ext run ${options.join(' ')}`);
41 + } catch (err) {
42 + console.error('`web-ext run` failed', err.stdout, err.stderr);
43 + }
44 +};
45 +
46 +main();
shells/browser/shared/build.js new
+93
@@ -0,0 +1,93 @@
1 +#!/usr/bin/env node
2 +
3 +const AdmZip = require('adm-zip');
4 +const { execSync } = require('child_process');
5 +const { copy, ensureDir, move, remove } = require('fs-extra');
6 +const { join } = require('path');
7 +
8 +// These files are copied along with Webpack-bundled files
9 +// to produce the final web extension
10 +const STATIC_FILES = ['icons', 'popups', 'main.html', 'panel.html'];
11 +
12 +const preProcess = async (destinationPath, tempPath) => {
13 + await remove(destinationPath); // Clean up from previously completed builds
14 + await remove(tempPath); // Clean up from any previously failed builds
15 + await ensureDir(tempPath); // Create temp dir for this new build
16 +};
17 +
18 +const build = async (tempPath, manifestPath) => {
19 + const binPath = join(tempPath, 'bin');
20 + const zipPath = join(tempPath, 'zip');
21 +
22 + const webpackPath = join(
23 + __dirname,
24 + '..',
25 + '..',
26 + '..',
27 + 'node_modules',
28 + '.bin',
29 + 'webpack'
30 + );
31 + execSync(
32 + `${webpackPath} --config webpack.config.js --output-path ${binPath}`,
33 + {
34 + cwd: __dirname,
35 + env: Object.assign({}, process.env, { NODE_ENV: 'production' }),
36 + stdio: 'inherit',
37 + }
38 + );
39 + execSync(
40 + `${webpackPath} --config webpack.backend.js --output-path ${binPath}`,
41 + {
42 + cwd: __dirname,
43 + env: Object.assign({}, process.env, { NODE_ENV: 'production' }),
44 + stdio: 'inherit',
45 + }
46 + );
47 +
48 + // Make temp dir
49 + await ensureDir(zipPath);
50 +
51 + // Copy unbuilt source files to zip dir to be packaged:
52 + await copy(binPath, join(zipPath, 'build'));
53 + await copy(manifestPath, join(zipPath, 'manifest.json'));
54 + await Promise.all(
55 + STATIC_FILES.map(file => copy(join(__dirname, file), join(zipPath, file)))
56 + );
57 +
58 + // Pack the extension
59 + const zip = new AdmZip();
60 + zip.addLocalFolder(zipPath);
61 + zip.writeZip(join(tempPath, 'packed.zip'));
62 +};
63 +
64 +const postProcess = async (tempPath, destinationPath) => {
65 + const unpackedSourcePath = join(tempPath, 'zip');
66 + const packedSourcePath = join(tempPath, 'packed.zip');
67 + const packedDestPath = join(destinationPath, 'packed.zip');
68 + const unpackedDestPath = join(destinationPath, 'unpacked');
69 +
70 + await move(unpackedSourcePath, unpackedDestPath); // Copy built files to destination
71 + await move(packedSourcePath, packedDestPath); // Copy built files to destination
72 + await remove(tempPath); // Clean up temp directory and files
73 +};
74 +
75 +const main = async (buildId, manifestPath, destinationPath) => {
76 + try {
77 + const tempPath = join(__dirname, 'build', buildId);
78 + await preProcess(destinationPath, tempPath);
79 + await build(tempPath, manifestPath);
80 +
81 + const builtUnpackedPath = join(destinationPath, 'unpacked');
82 + await postProcess(tempPath, destinationPath);
83 +
84 + return builtUnpackedPath;
85 + } catch (error) {
86 + console.error(error);
87 + process.exit(1);
88 + }
89 +
90 + return null;
91 +};
92 +
93 +module.exports = main;
shells/browser/shared/icons/128-deadcode.png
Binary files /dev/null and b/shells/browser/shared/icons/128-deadcode.png differ
shells/browser/shared/icons/128-development.png
Binary files /dev/null and b/shells/browser/shared/icons/128-development.png differ
shells/browser/shared/icons/128-disabled.png
Binary files /dev/null and b/shells/browser/shared/icons/128-disabled.png differ
shells/browser/shared/icons/128-outdated.png
Binary files /dev/null and b/shells/browser/shared/icons/128-outdated.png differ
shells/browser/shared/icons/128-production.png
Binary files /dev/null and b/shells/browser/shared/icons/128-production.png differ
shells/browser/shared/icons/128-unminified.png
Binary files /dev/null and b/shells/browser/shared/icons/128-unminified.png differ
shells/browser/shared/icons/16-deadcode.png
Binary files /dev/null and b/shells/browser/shared/icons/16-deadcode.png differ
shells/browser/shared/icons/16-development.png
Binary files /dev/null and b/shells/browser/shared/icons/16-development.png differ
shells/browser/shared/icons/16-disabled.png
Binary files /dev/null and b/shells/browser/shared/icons/16-disabled.png differ
shells/browser/shared/icons/16-outdated.png
Binary files /dev/null and b/shells/browser/shared/icons/16-outdated.png differ
shells/browser/shared/icons/16-production.png
Binary files /dev/null and b/shells/browser/shared/icons/16-production.png differ
shells/browser/shared/icons/16-unminified.png
Binary files /dev/null and b/shells/browser/shared/icons/16-unminified.png differ
shells/browser/shared/icons/32-deadcode.png
Binary files /dev/null and b/shells/browser/shared/icons/32-deadcode.png differ
shells/browser/shared/icons/32-development.png
Binary files /dev/null and b/shells/browser/shared/icons/32-development.png differ
shells/browser/shared/icons/32-disabled.png
Binary files /dev/null and b/shells/browser/shared/icons/32-disabled.png differ
shells/browser/shared/icons/32-outdated.png
Binary files /dev/null and b/shells/browser/shared/icons/32-outdated.png differ
shells/browser/shared/icons/32-production.png
Binary files /dev/null and b/shells/browser/shared/icons/32-production.png differ
shells/browser/shared/icons/32-unminified.png
Binary files /dev/null and b/shells/browser/shared/icons/32-unminified.png differ
shells/browser/shared/icons/48-deadcode.png
Binary files /dev/null and b/shells/browser/shared/icons/48-deadcode.png differ
shells/browser/shared/icons/48-development.png
Binary files /dev/null and b/shells/browser/shared/icons/48-development.png differ
shells/browser/shared/icons/48-disabled.png
Binary files /dev/null and b/shells/browser/shared/icons/48-disabled.png differ
shells/browser/shared/icons/48-outdated.png
Binary files /dev/null and b/shells/browser/shared/icons/48-outdated.png differ
shells/browser/shared/icons/48-production.png
Binary files /dev/null and b/shells/browser/shared/icons/48-production.png differ
shells/browser/shared/icons/48-unminified.png
Binary files /dev/null and b/shells/browser/shared/icons/48-unminified.png differ
shells/browser/shared/icons/deadcode.svg new
+1
@@ -0,0 +1 @@
1 +<svg id="Development" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><defs><style>.cls-1{fill:#d94a38;}.cls-2{fill:#fff;}.cls-3{fill:#231f20;}.cls-4{font-size:12px;font-family:MyriadPro-Regular, Myriad Pro;}</style></defs><title>development</title><g id="Background"><rect class="cls-1" width="1024" height="1024" rx="96" ry="96"/></g><g id="Rings"><g id="Ring-2" data-name="Ring"><path class="cls-2" d="M959,509c0-62-74-117-189-150,27-115,17-207-37-238s-139,6-224,88c-86-81-171-118-224-87s-64,123-36,239C135,394,61,449,61,511s74,117,189,150c-27,115-17,207,37,238s139-6,224-88c86,81,171,118,224,87s64-123,36-239C885,626,959,571,959,509ZM713,157c40,23,45,97,21,193a900,900,0,0,0-121-19,900,900,0,0,0-78-94C606,166,673,133,713,157ZM635,583c-14,24-28,47-43,69-27,2-54,3-83,3l-81-3c-15-22-30-46-44-70s-27-48-38-72c12-24,24-49,39-73s28-47,43-69c27-2,54-3,83-3l81,3c15,22,30,46,44,70s27,48,38,72C662,534,649,558,635,583Zm60-27c11,26,21,52,29,77-25,6-52,10-81,14l26-44ZM511,757c-17-19-35-40-52-63H563C546,716,528,738,511,757ZM378,647c-29-3-56-8-81-13,8-25,17-50,28-77l25,45ZM325,464c-11-26-21-52-29-77,25-6,52-10,81-14l-26,44ZM509,263c17,19,35,40,52,63H457C474,304,492,282,509,263ZM670,418l-28-45c29,3,56,8,81,13-8,25-17,50-28,77ZM305,158c40-23,106,9,177,78a900,900,0,0,0-77,95,900,900,0,0,0-120,20C260,255,265,181,305,158ZM102,511c0-46,61-88,156-114a900,900,0,0,0,44,114,900,900,0,0,0-43,114C164,599,102,558,102,511ZM307,863c-40-23-45-97-21-193a900,900,0,0,0,121,19,900,900,0,0,0,78,94C414,854,347,887,307,863Zm408-1c-40,23-106-9-177-78a900,900,0,0,0,77-95,900,900,0,0,0,120-20C760,765,755,839,715,862Zm46-239a900,900,0,0,0-44-114,900,900,0,0,0,43-114c96,26,157,67,157,114S856,597,761,623Z"/></g></g><g id="Ring_Blockers" data-name="Ring Blockers"><circle class="cls-1" cx="417" cy="672" r="45"/><circle class="cls-1" cx="699" cy="513" r="45"/><circle class="cls-1" cx="797" cy="634" r="45"/><circle class="cls-1" cx="479" cy="818" r="45"/><g id="Layer_14" data-name="Layer 14"><rect class="cls-1" x="420" y="621" width="377" height="188"/><rect class="cls-1" x="500" y="530" width="312" height="405"/></g></g><g id="Bug"><g id="Legs"><path class="cls-3" d="M702,496a17,17,0,0,0-21,13l-19,78,34,8,19-78A17,17,0,0,0,702,496Z"/><text class="cls-4" transform="translate(512 512)">780</text><text class="cls-4" transform="translate(512 512)">780</text><path class="cls-3" d="M813,626a17,17,0,0,0-23-9l-73,32,14,32,73-32A17,17,0,0,0,813,626Z"/><path class="cls-3" d="M834,756l-77-20-9,34,77,20a18,18,0,0,0,9-34Z"/><path class="cls-3" d="M425,656a17,17,0,1,0-10,33l76,23,10-33Z"/><path class="cls-3" d="M532,756l-64,48a18,18,0,1,0,21,28l64-48Z"/><path class="cls-3" d="M584,836l-21,77a17,17,0,1,0,34,9l21-77Z"/></g><g id="Body"><path class="cls-3" d="M762,690h0l-51-92h0A125,125,0,0,0,492,721h0l51,92h0A125,125,0,0,0,762,690Z"/></g><g id="Line"><path class="cls-1" d="M613,649h0a17,17,0,0,0-30,18h0L711,887l30-18Z"/></g><g id="Head_Ring" data-name="Head Ring"><circle class="cls-1" cx="511" cy="509" r="113"/></g><g id="Head"><circle class="cls-3" cx="512" cy="512" r="80"/></g></g></svg>
\ No newline at end of file
shells/browser/shared/icons/development.svg new
+1
@@ -0,0 +1 @@
1 +<svg id="Development" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><defs><style>.cls-1{fill:#d94a38;}.cls-2{fill:#fff;}.cls-3{fill:#231f20;}.cls-4{font-size:12px;font-family:MyriadPro-Regular, Myriad Pro;}</style></defs><title>development</title><g id="Background"><rect class="cls-1" width="1024" height="1024" rx="96" ry="96"/></g><g id="Rings"><g id="Ring-2" data-name="Ring"><path class="cls-2" d="M959,509c0-62-74-117-189-150,27-115,17-207-37-238s-139,6-224,88c-86-81-171-118-224-87s-64,123-36,239C135,394,61,449,61,511s74,117,189,150c-27,115-17,207,37,238s139-6,224-88c86,81,171,118,224,87s64-123,36-239C885,626,959,571,959,509ZM713,157c40,23,45,97,21,193a900,900,0,0,0-121-19,900,900,0,0,0-78-94C606,166,673,133,713,157ZM635,583c-14,24-28,47-43,69-27,2-54,3-83,3l-81-3c-15-22-30-46-44-70s-27-48-38-72c12-24,24-49,39-73s28-47,43-69c27-2,54-3,83-3l81,3c15,22,30,46,44,70s27,48,38,72C662,534,649,558,635,583Zm60-27c11,26,21,52,29,77-25,6-52,10-81,14l26-44ZM511,757c-17-19-35-40-52-63H563C546,716,528,738,511,757ZM378,647c-29-3-56-8-81-13,8-25,17-50,28-77l25,45ZM325,464c-11-26-21-52-29-77,25-6,52-10,81-14l-26,44ZM509,263c17,19,35,40,52,63H457C474,304,492,282,509,263ZM670,418l-28-45c29,3,56,8,81,13-8,25-17,50-28,77ZM305,158c40-23,106,9,177,78a900,900,0,0,0-77,95,900,900,0,0,0-120,20C260,255,265,181,305,158ZM102,511c0-46,61-88,156-114a900,900,0,0,0,44,114,900,900,0,0,0-43,114C164,599,102,558,102,511ZM307,863c-40-23-45-97-21-193a900,900,0,0,0,121,19,900,900,0,0,0,78,94C414,854,347,887,307,863Zm408-1c-40,23-106-9-177-78a900,900,0,0,0,77-95,900,900,0,0,0,120-20C760,765,755,839,715,862Zm46-239a900,900,0,0,0-44-114,900,900,0,0,0,43-114c96,26,157,67,157,114S856,597,761,623Z"/></g></g><g id="Ring_Blockers" data-name="Ring Blockers"><circle class="cls-1" cx="417" cy="672" r="45"/><circle class="cls-1" cx="699" cy="513" r="45"/><circle class="cls-1" cx="797" cy="634" r="45"/><circle class="cls-1" cx="479" cy="818" r="45"/><g id="Layer_14" data-name="Layer 14"><rect class="cls-1" x="420" y="621" width="377" height="188"/><rect class="cls-1" x="500" y="530" width="312" height="405"/></g></g><g id="Bug"><g id="Legs"><path class="cls-3" d="M702,496a17,17,0,0,0-21,13l-19,78,34,8,19-78A17,17,0,0,0,702,496Z"/><text class="cls-4" transform="translate(512 512)">780</text><text class="cls-4" transform="translate(512 512)">780</text><path class="cls-3" d="M813,626a17,17,0,0,0-23-9l-73,32,14,32,73-32A17,17,0,0,0,813,626Z"/><path class="cls-3" d="M834,756l-77-20-9,34,77,20a18,18,0,0,0,9-34Z"/><path class="cls-3" d="M425,656a17,17,0,1,0-10,33l76,23,10-33Z"/><path class="cls-3" d="M532,756l-64,48a18,18,0,1,0,21,28l64-48Z"/><path class="cls-3" d="M584,836l-21,77a17,17,0,1,0,34,9l21-77Z"/></g><g id="Body"><path class="cls-3" d="M762,690h0l-51-92h0A125,125,0,0,0,492,721h0l51,92h0A125,125,0,0,0,762,690Z"/></g><g id="Line"><path class="cls-1" d="M613,649h0a17,17,0,0,0-30,18h0L711,887l30-18Z"/></g><g id="Head_Ring" data-name="Head Ring"><circle class="cls-1" cx="511" cy="509" r="113"/></g><g id="Head"><circle class="cls-3" cx="512" cy="512" r="80"/></g></g></svg>
\ No newline at end of file
shells/browser/shared/icons/disabled.svg new
+1
@@ -0,0 +1 @@
1 +<svg id="Disabled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><defs><style>.cls-1{fill:#aaa;}.cls-2{fill:#fff;}</style></defs><title>disabled</title><g id="Background"><rect class="cls-1" width="1024" height="1024" rx="96" ry="96"/></g><g id="Rings"><path class="cls-2" d="M959,509c0-62-74-117-189-150,27-115,17-207-37-238s-139,6-224,88c-86-81-171-118-224-87s-64,123-36,239C135,394,61,449,61,511s74,117,189,150c-27,115-17,207,37,238s139-6,224-88c86,81,171,118,224,87s64-123,36-239C885,626,959,571,959,509ZM713,157c40,23,45,97,21,193a900,900,0,0,0-121-19,900,900,0,0,0-78-94C606,166,673,133,713,157ZM635,583c-14,24-28,47-43,69-27,2-54,3-83,3l-81-3c-15-22-30-46-44-70s-27-48-38-72c12-24,24-49,39-73s28-47,43-69c27-2,54-3,83-3l81,3c15,22,30,46,44,70s27,48,38,72C662,534,649,558,635,583Zm60-27c11,26,21,52,29,77-25,6-52,10-81,14l26-44ZM511,757c-17-19-35-40-52-63H563C546,716,528,738,511,757ZM378,647c-29-3-56-8-81-13,8-25,17-50,28-77l25,45ZM325,464c-11-26-21-52-29-77,25-6,52-10,81-14l-26,44ZM509,263c17,19,35,40,52,63H457C474,304,492,282,509,263ZM670,418l-28-45c29,3,56,8,81,13-8,25-17,50-28,77ZM305,158c40-23,106,9,177,78a900,900,0,0,0-77,95,900,900,0,0,0-120,20C260,255,265,181,305,158ZM102,511c0-46,61-88,156-114a900,900,0,0,0,44,114,900,900,0,0,0-43,114C164,599,102,558,102,511ZM307,863c-40-23-45-97-21-193a900,900,0,0,0,121,19,900,900,0,0,0,78,94C414,854,347,887,307,863Zm408-1c-40,23-106-9-177-78a900,900,0,0,0,77-95,900,900,0,0,0,120-20C760,765,755,839,715,862Zm46-239a900,900,0,0,0-44-114,900,900,0,0,0,43-114c96,26,157,67,157,114S856,597,761,623Z"/></g><g id="Circle"><circle class="cls-2" cx="510" cy="510" r="80"/></g></svg>
\ No newline at end of file
shells/browser/shared/icons/outdated.svg new
+1
@@ -0,0 +1 @@
1 +<svg id="Outdated" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><defs><style>.cls-1{fill:#202020;}.cls-2{fill:#fff;}.cls-3{fill:#f9f453;}</style></defs><title>outdated</title><g id="Background"><rect class="cls-1" width="1024" height="1024" rx="96" ry="96"/></g><g id="Rings"><path class="cls-2" d="M510,325C261,325,60,408,60,510S261,695,510,695s450-83,450-185S759,325,510,325Zm0,330c-225,0-407-65-407-145S285,365,510,365s408,65,408,145S735,655,510,655Z"/><path class="cls-2" d="M670,417C546,202,373,69,285,120s-59,267,65,482S647,951,735,900,794,633,670,417ZM384,583C272,388,237,197,306,157s217,86,329,280,148,385,78,425S497,777,384,583Z"/><g id="BLOCKER"><rect class="cls-1" x="564" y="572" width="315" height="397.12" transform="translate(652 -283) rotate(39)"/><rect class="cls-1" x="685" y="369" width="283" height="360.81" transform="translate(255 -262) rotate(21)"/></g><path class="cls-2" d="M670,603c124-215,153-431,65-482S474,202,350,417,197,849,285,900,546,818,670,603ZM384,437C497,243,644,117,714,157s34,231-78,425S376,903,306,863,272,632,384,437Z"/></g><g id="Circle"><circle class="cls-2" cx="512" cy="512" r="80"/></g><g id="Shield"><path class="cls-3" d="M960,887a24,24,0,0,0-5-15h0L807,605h0v-2h0a25,25,0,0,0-43,3h0L613,876h0a24,24,0,0,0-3,11,25,25,0,0,0,24,25H936A25,25,0,0,0,960,887ZM810,848a15,15,0,0,1-15,15H775a15,15,0,0,1-15-15V828a15,15,0,0,1,15-15h20a15,15,0,0,1,15,15Zm0-74a15,15,0,0,1-15,15H775a15,15,0,0,1-15-15V705a15,15,0,0,1,15-15h20a15,15,0,0,1,15,15Z"/></g></svg>
\ No newline at end of file
shells/browser/shared/icons/production.svg new
+1
@@ -0,0 +1 @@
1 +<svg id="Production" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><defs><style>.cls-1{fill:#202020;}.cls-2{fill:#59c9f1;}</style></defs><title>production</title><g id="Background"><rect class="cls-1" width="1024" height="1024" rx="96" ry="96"/></g><g id="Rings"><path class="cls-2" d="M959,509c0-62-74-117-189-150,27-115,17-207-37-238s-139,6-224,88c-86-81-171-118-224-87s-64,123-36,239C135,394,61,449,61,511s74,117,189,150c-27,115-17,207,37,238s139-6,224-88c86,81,171,118,224,87s64-123,36-239C885,626,959,571,959,509ZM713,157c40,23,45,97,21,193a900,900,0,0,0-121-19,900,900,0,0,0-78-94C606,166,673,133,713,157ZM635,583c-14,24-28,47-43,69-27,2-54,3-83,3l-81-3c-15-22-30-46-44-70s-27-48-38-72c12-24,24-49,39-73s28-47,43-69c27-2,54-3,83-3l81,3c15,22,30,46,44,70s27,48,38,72C662,534,649,558,635,583Zm60-27c11,26,21,52,29,77-25,6-52,10-81,14l26-44ZM511,757c-17-19-35-40-52-63H563C546,716,528,738,511,757ZM378,647c-29-3-56-8-81-13,8-25,17-50,28-77l25,45ZM325,464c-11-26-21-52-29-77,25-6,52-10,81-14l-26,44ZM509,263c17,19,35,40,52,63H457C474,304,492,282,509,263ZM670,418l-28-45c29,3,56,8,81,13-8,25-17,50-28,77ZM305,158c40-23,106,9,177,78a900,900,0,0,0-77,95,900,900,0,0,0-120,20C260,255,265,181,305,158ZM102,511c0-46,61-88,156-114a900,900,0,0,0,44,114,900,900,0,0,0-43,114C164,599,102,558,102,511ZM307,863c-40-23-45-97-21-193a900,900,0,0,0,121,19,900,900,0,0,0,78,94C414,854,347,887,307,863Zm408-1c-40,23-106-9-177-78a900,900,0,0,0,77-95,900,900,0,0,0,120-20C760,765,755,839,715,862Zm46-239a900,900,0,0,0-44-114,900,900,0,0,0,43-114c96,26,157,67,157,114S856,597,761,623Z"/></g><g id="Circle"><circle class="cls-2" cx="510" cy="510" r="80"/></g></svg>
\ No newline at end of file
shells/browser/shared/main.html new
+9
@@ -0,0 +1,9 @@
1 +<!doctype html>
2 +<html>
3 + <head>
4 + <meta charset="utf-8">
5 + <script src="./build/main.js"></script>
6 + </head>
7 + <body>
8 + </body>
9 +</html>
shells/browser/shared/panel.html new
+32
@@ -0,0 +1,32 @@
1 +<!doctype html>
2 +<html style="display: flex">
3 + <head>
4 + <meta charset="utf8">
5 + <style>
6 + html {
7 + display: flex;
8 + }
9 + body {
10 + margin: 0;
11 + padding: 0;
12 + flex: 1;
13 + display: flex;
14 + }
15 + #container {
16 + display: flex;
17 + flex: 1;
18 + width: 100%;
19 + position: fixed;
20 + top: 0;
21 + left: 0;
22 + right: 0;
23 + bottom: 0;
24 + }
25 + </style>
26 + </head>
27 + <body>
28 + <!-- main react mount point -->
29 + <div id="container">Unable to find React on the page.</div>
30 + <script src="./build/panel.js"></script>
31 + </body>
32 +</html>
shells/browser/shared/popups/deadcode.html new
+32
@@ -0,0 +1,32 @@
1 +<script src="shared.js"></script>
2 +<style>
3 + html, body {
4 + font-size: 14px;
5 + min-width: 460px;
6 + min-height: 133px;
7 + }
8 +
9 + body {
10 + margin: 8px;
11 + }
12 +
13 + hr {
14 + width: 100%;
15 + }
16 +</style>
17 +<p>
18 + <b>This page includes an extra development build of React. &#x1f6a7;</b>
19 +</p>
20 +<p>
21 + The React build on this page includes both development and production versions because dead code elimination has not been applied correctly.
22 + <br />
23 + <br />
24 + This makes its size larger, and causes React to run slower.
25 + <br />
26 + <br />
27 + Make sure to <a href="https://facebook.github.io/react/docs/optimizing-performance.html#use-the-production-build">set up dead code elimination</a> before deployment.
28 +</p>
29 +<hr />
30 +<p>
31 + Open the developer tools, and the React tab will appear to the right.
32 +</p>
shells/browser/shared/popups/development.html new
+28
@@ -0,0 +1,28 @@
1 +<script src="shared.js"></script>
2 +<style>
3 + html, body {
4 + font-size: 14px;
5 + min-width: 460px;
6 + min-height: 101px;
7 + }
8 +
9 + body {
10 + margin: 8px;
11 + }
12 +
13 + hr {
14 + width: 100%;
15 + }
16 +</style>
17 +<p>
18 + <b>This page is using the development build of React. &#x1f6a7;</b>
19 +</p>
20 +<p>
21 + Note that the development build is not suitable for production.
22 + <br />
23 + Make sure to <a href="https://facebook.github.io/react/docs/optimizing-performance.html#use-the-production-build">use the production build</a> before deployment.
24 +</p>
25 +<hr />
26 +<p>
27 + Open the developer tools, and the React tab will appear to the right.
28 +</p>
shells/browser/shared/popups/disabled.html new
+21
@@ -0,0 +1,21 @@
1 +<script src="shared.js"></script>
2 +<style>
3 + html, body {
4 + font-size: 14px;
5 + min-width: 410px;
6 + min-height: 33px;
7 + }
8 +
9 + body {
10 + margin: 8px;
11 + }
12 +
13 + hr {
14 + width: 100%;
15 + }
16 +</style>
17 +<p>
18 + <b>This page doesn&rsquo;t appear to be using React.</b>
19 + <br />
20 + If this seems wrong, follow the <a href="https://github.com/facebook/react-devtools/blob/master/README.md#the-react-tab-doesnt-show-up">troubleshooting instructions</a>.
21 +</p>
shells/browser/shared/popups/outdated.html new
+29
@@ -0,0 +1,29 @@
1 +<script src="shared.js"></script>
2 +<style>
3 + html, body {
4 + font-size: 14px;
5 + min-width: 460px;
6 + min-height: 117px;
7 + }
8 +
9 + body {
10 + margin: 8px;
11 + }
12 +
13 + hr {
14 + width: 100%;
15 + }
16 +</style>
17 +<p>
18 + <b>This page is using an outdated version of React. &#8987;</b>
19 +</p>
20 +<p>
21 + We recommend updating React to ensure that you receive important bugfixes and performance improvements.
22 + <br />
23 + <br />
24 + You can find the upgrade instructions on the <a href="https://facebook.github.io/react/blog/">React blog</a>.
25 +</p>
26 +<hr />
27 +<p>
28 + Open the developer tools, and the React tab will appear to the right.
29 +</p>
shells/browser/shared/popups/production.html new
+21
@@ -0,0 +1,21 @@
1 +<script src="shared.js"></script>
2 +<style>
3 + html, body {
4 + font-size: 14px;
5 + min-width: 460px;
6 + min-height: 39px;
7 + }
8 +
9 + body {
10 + margin: 8px;
11 + }
12 +
13 + hr {
14 + width: 100%;
15 + }
16 +</style>
17 +<p>
18 + <b>This page is using the production build of React. &#x2705;</b>
19 + <br />
20 + Open the developer tools, and the React tab will appear to the right.
21 +</p>
shells/browser/shared/popups/shared.js new
+22
@@ -0,0 +1,22 @@
1 +/* globals chrome */
2 +
3 +document.addEventListener('DOMContentLoaded', function() {
4 + // Make links work
5 + const links = document.getElementsByTagName('a');
6 + for (let i = 0; i < links.length; i++) {
7 + (function() {
8 + const ln = links[i];
9 + const location = ln.href;
10 + ln.onclick = function() {
11 + chrome.tabs.create({ active: true, url: location });
12 + };
13 + })();
14 + }
15 +
16 + // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=428044
17 + document.body.style.opacity = 0;
18 + document.body.style.transition = 'opacity ease-out .4s';
19 + requestAnimationFrame(function() {
20 + document.body.style.opacity = 1;
21 + });
22 +});
shells/browser/shared/popups/unminified.html new
+31
@@ -0,0 +1,31 @@
1 +<script src="shared.js"></script>
2 +<style>
3 + html, body {
4 + font-size: 14px;
5 + min-width: 460px;
6 + min-height: 133px;
7 + }
8 +
9 + body {
10 + margin: 8px;
11 + }
12 +
13 + hr {
14 + width: 100%;
15 + }
16 +</style>
17 +<p>
18 + <b>This page is using an unminified build of React. &#x1f6a7;</b>
19 +</p>
20 +<p>
21 + The React build on this page appears to be unminified.
22 + <br />
23 + This makes its size larger, and causes React to run slower.
24 + <br />
25 + <br />
26 + Make sure to <a href="https://facebook.github.io/react/docs/optimizing-performance.html#use-the-production-build">set up minification</a> before deployment.
27 +</p>
28 +<hr />
29 +<p>
30 + Open the developer tools, and the React tab will appear to the right.
31 +</p>
shells/browser/shared/src/GlobalHook.js new
+64
@@ -0,0 +1,64 @@
1 +/* global chrome */
2 +
3 +import nullthrows from 'nullthrows';
4 +import { installHook } from 'src/hook';
5 +
6 +let lastDetectionResult;
7 +
8 +// We want to detect when a renderer attaches, and notify the "background
9 +// page" (which is shared between tabs and can highlight the React icon).
10 +// Currently we are in "content script" context, so we can't listen
11 +// to the hook directly (it will be injected directly into the page).
12 +// So instead, the hook will use postMessage() to pass message to us here.
13 +// And when this happens, we'll send a message to the "background page".
14 +window.addEventListener('message', function(evt) {
15 + if (
16 + evt.source === window &&
17 + evt.data &&
18 + evt.data.source === 'react-devtools-detector'
19 + ) {
20 + lastDetectionResult = {
21 + hasDetectedReact: true,
22 + reactBuildType: evt.data.reactBuildType,
23 + };
24 + chrome.runtime.sendMessage(lastDetectionResult);
25 + }
26 +});
27 +
28 +// NOTE: Firefox WebExtensions content scripts are still alive and not re-injected
29 +// while navigating the history to a document that has not been destroyed yet,
30 +// replay the last detection result if the content script is active and the
31 +// document has been hidden and shown again.
32 +window.addEventListener('pageshow', function(evt) {
33 + if (!lastDetectionResult || evt.target !== window.document) {
34 + return;
35 + }
36 + chrome.runtime.sendMessage(lastDetectionResult);
37 +});
38 +
39 +const detectReact = `
40 +window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('renderer', function(evt) {
41 + window.postMessage({
42 + source: 'react-devtools-detector',
43 + reactBuildType: evt.reactBuildType,
44 + }, '*');
45 +});
46 +`;
47 +const saveNativeValues = `
48 +window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeObjectCreate = Object.create;
49 +window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeMap = Map;
50 +window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeWeakMap = WeakMap;
51 +window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeSet = Set;
52 +`;
53 +
54 +// Inject a `__REACT_DEVTOOLS_GLOBAL_HOOK__` global so that React can detect that the
55 +// devtools are installed (and skip its suggestion to install the devtools).
56 +const js =
57 + ';(' + installHook.toString() + '(window))' + saveNativeValues + detectReact;
58 +
59 +// This script runs before the <head> element is created, so we add the script
60 +// to <html> instead.
61 +const script = document.createElement('script');
62 +script.textContent = js;
63 +nullthrows(document.documentElement).appendChild(script);
64 +nullthrows(script.parentNode).removeChild(script);
shells/browser/shared/src/backend.js new
+67
@@ -0,0 +1,67 @@
1 +// Do not use imports or top-level requires here!
2 +// Running module factories is intentionally delayed until we know the hook exists.
3 +// This is to avoid issues like: https://github.com/facebook/react-devtools/issues/1039
4 +
5 +/** @flow */
6 +
7 +function welcome(event) {
8 + if (
9 + event.source !== window ||
10 + event.data.source !== 'react-devtools-content-script'
11 + ) {
12 + return;
13 + }
14 +
15 + window.removeEventListener('message', welcome);
16 +
17 + setup(window.__REACT_DEVTOOLS_GLOBAL_HOOK__);
18 +}
19 +
20 +window.addEventListener('message', welcome);
21 +
22 +function setup(hook) {
23 + const Agent = require('src/backend/agent').default;
24 + const Bridge = require('src/bridge').default;
25 + const { initBackend } = require('src/backend');
26 +
27 + const listeners = [];
28 +
29 + const bridge = new Bridge({
30 + listen(fn) {
31 + const listener = event => {
32 + if (
33 + event.source !== window ||
34 + !event.data ||
35 + event.data.source !== 'react-devtools-content-script' ||
36 + !event.data.payload
37 + ) {
38 + return;
39 + }
40 + fn(event.data.payload);
41 + };
42 + listeners.push(listener);
43 + window.addEventListener('message', listener);
44 + },
45 + send(data) {
46 + window.postMessage(
47 + {
48 + source: 'react-devtools-bridge',
49 + payload: data,
50 + },
51 + '*'
52 + );
53 + },
54 + });
55 +
56 + const agent = new Agent();
57 + agent.addBridge(bridge);
58 + agent.addListener('shutdown', () => {
59 + hook.emit('shutdown');
60 + listeners.forEach(fn => {
61 + window.removeEventListener('message', fn);
62 + });
63 + listeners.splice(0);
64 + });
65 +
66 + initBackend(window.__REACT_DEVTOOLS_GLOBAL_HOOK__, agent);
67 +}
shells/browser/shared/src/background.js new
+111
@@ -0,0 +1,111 @@
1 +/* global chrome */
2 +
3 +const ports = {};
4 +
5 +const IS_FIREFOX = navigator.userAgent.indexOf('Firefox') >= 0;
6 +
7 +chrome.runtime.onConnect.addListener(function(port) {
8 + let tab = null;
9 + let name = null;
10 + if (isNumeric(port.name)) {
11 + tab = port.name;
12 + name = 'devtools';
13 + installContentScript(+port.name);
14 + } else {
15 + tab = port.sender.tab.id;
16 + name = 'content-script';
17 + }
18 +
19 + if (!ports[tab]) {
20 + ports[tab] = {
21 + devtools: null,
22 + 'content-script': null,
23 + };
24 + }
25 + ports[tab][name] = port;
26 +
27 + if (ports[tab].devtools && ports[tab]['content-script']) {
28 + doublePipe(ports[tab].devtools, ports[tab]['content-script']);
29 + }
30 +});
31 +
32 +function isNumeric(str: string): boolean {
33 + return +str + '' === str;
34 +}
35 +
36 +function installContentScript(tabId: number) {
37 + chrome.tabs.executeScript(
38 + tabId,
39 + { file: '/build/contentScript.js' },
40 + function() {}
41 + );
42 +}
43 +
44 +function doublePipe(one, two) {
45 + one.onMessage.addListener(lOne);
46 + function lOne(message) {
47 + two.postMessage(message);
48 + }
49 + two.onMessage.addListener(lTwo);
50 + function lTwo(message) {
51 + one.postMessage(message);
52 + }
53 + function shutdown() {
54 + one.onMessage.removeListener(lOne);
55 + two.onMessage.removeListener(lTwo);
56 + one.disconnect();
57 + two.disconnect();
58 + }
59 + one.onDisconnect.addListener(shutdown);
60 + two.onDisconnect.addListener(shutdown);
61 +}
62 +
63 +function setIconAndPopup(reactBuildType, tabId) {
64 + chrome.browserAction.setIcon({
65 + tabId: tabId,
66 + path: {
67 + '16': 'icons/16-' + reactBuildType + '.png',
68 + '32': 'icons/32-' + reactBuildType + '.png',
69 + '48': 'icons/48-' + reactBuildType + '.png',
70 + '128': 'icons/128-' + reactBuildType + '.png',
71 + },
72 + });
73 + chrome.browserAction.setPopup({
74 + tabId: tabId,
75 + popup: 'popups/' + reactBuildType + '.html',
76 + });
77 +}
78 +
79 +// Listen to URL changes on the active tab and reset the DevTools icon.
80 +// This prevents non-disabled icons from sticking in Firefox.
81 +// Don't listen to this event in Chrome though.
82 +// It fires more frequently, often after onMessage() has been called.
83 +if (IS_FIREFOX) {
84 + chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
85 + if (tab.active && changeInfo.status === 'loading') {
86 + setIconAndPopup('disabled', tabId);
87 + }
88 + });
89 +}
90 +
91 +chrome.runtime.onMessage.addListener((req, sender) => {
92 + // This is sent from the hook content script.
93 + // It tells us a renderer has attached.
94 + if (req.hasDetectedReact && sender.tab) {
95 + // We use browserAction instead of pageAction because this lets us
96 + // display a custom default popup when React is *not* detected.
97 + // It is specified in the manifest.
98 + let reactBuildType = req.reactBuildType;
99 + if (sender.url.indexOf('facebook.github.io/react') !== -1) {
100 + // Cheat: We use the development version on the website because
101 + // it is better for interactive examples. However we're going
102 + // to get misguided bug reports if the extension highlights it
103 + // as using the dev version. We're just going to special case
104 + // our own documentation and cheat. It is acceptable to use dev
105 + // version of React in React docs, but not in any other case.
106 + reactBuildType = 'production';
107 + }
108 +
109 + setIconAndPopup(reactBuildType, sender.tab.id);
110 + }
111 +});
shells/browser/shared/src/checkForReact.js new
+12
@@ -0,0 +1,12 @@
1 +/* global chrome */
2 +
3 +export default function checkForReact(done: (pageHasReact: boolean) => void) {
4 + chrome.devtools.inspectedWindow.eval(
5 + `!!(
6 + (window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && Object.keys(window.__REACT_DEVTOOLS_GLOBAL_HOOK__._renderers).length) || window.React || (window.require && (require('react') || require('React')))
7 + )`,
8 + function(pageHasReact, err) {
9 + done(pageHasReact);
10 + }
11 + );
12 +}
shells/browser/shared/src/contentScript.js new
+52
@@ -0,0 +1,52 @@
1 +/* global chrome */
2 +
3 +// proxy from main page to devtools (via the background page)
4 +var port = chrome.runtime.connect({
5 + name: 'content-script',
6 +});
7 +
8 +port.onMessage.addListener(handleMessageFromDevtools);
9 +port.onDisconnect.addListener(handleDisconnect);
10 +window.addEventListener('message', handleMessageFromPage);
11 +
12 +window.postMessage(
13 + {
14 + source: 'react-devtools-content-script',
15 + hello: true,
16 + },
17 + '*'
18 +);
19 +
20 +function handleMessageFromDevtools(message) {
21 + window.postMessage(
22 + {
23 + source: 'react-devtools-content-script',
24 + payload: message,
25 + },
26 + '*'
27 + );
28 +}
29 +
30 +function handleMessageFromPage(evt) {
31 + if (
32 + evt.source === window &&
33 + evt.data &&
34 + evt.data.source === 'react-devtools-bridge'
35 + ) {
36 + port.postMessage(evt.data.payload);
37 + }
38 +}
39 +
40 +function handleDisconnect() {
41 + window.removeEventListener('message', handleMessageFromPage);
42 + window.postMessage(
43 + {
44 + source: 'react-devtools-content-script',
45 + payload: {
46 + type: 'event',
47 + evt: 'shutdown',
48 + },
49 + },
50 + '*'
51 + );
52 +}
shells/browser/shared/src/inject.js new
+21
@@ -0,0 +1,21 @@
1 +/* global chrome */
2 +
3 +export default function inject(scriptName: string, done: () => void) {
4 + const src = `
5 + // the prototype stuff is in case document.createElement has been modified
6 + (function () {
7 + var script = document.constructor.prototype.createElement.call(document, 'script');
8 + script.src = "${scriptName}";
9 + script.charset = "utf-8";
10 + document.documentElement.appendChild(script);
11 + script.parentNode.removeChild(script);
12 + })()
13 + `;
14 +
15 + chrome.devtools.inspectedWindow.eval(src, function(res, err) {
16 + if (err) {
17 + console.log(err);
18 + }
19 + done();
20 + });
21 +}
shells/browser/shared/src/main.js new
+40
@@ -0,0 +1,40 @@
1 +/* global chrome */
2 +
3 +let panelCreated = false;
4 +
5 +function createPanelIfReactLoaded() {
6 + if (panelCreated) {
7 + return;
8 + }
9 + chrome.devtools.inspectedWindow.eval(
10 + 'window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && Object.keys(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers).length > 0',
11 + function(pageHasReact, err) {
12 + if (!pageHasReact || panelCreated) {
13 + return;
14 + }
15 +
16 + clearInterval(loadCheckInterval);
17 + panelCreated = true;
18 + chrome.devtools.panels.create('React', '', 'panel.html', function(panel) {
19 + panel.onShown.addListener(function(window) {
20 + // TODO: When the user switches to the panel, check for an Elements tab selection.
21 + });
22 + panel.onHidden.addListener(function() {
23 + // TODO: Stop highlighting and stuff.
24 + });
25 + });
26 + }
27 + );
28 +}
29 +
30 +chrome.devtools.network.onNavigated.addListener(function() {
31 + createPanelIfReactLoaded();
32 +});
33 +
34 +// Check to see if React has loaded once per second in case React is added
35 +// after page load
36 +const loadCheckInterval = setInterval(function() {
37 + createPanelIfReactLoaded();
38 +}, 1000);
39 +
40 +createPanelIfReactLoaded();
shells/browser/shared/src/panel.js new
+62
@@ -0,0 +1,62 @@
1 +/* global chrome */
2 +
3 +import { createElement } from 'react';
4 +import { render } from 'react-dom';
5 +import Bridge from 'src/bridge';
6 +import App from 'src/devtools/views/App';
7 +import inject from './inject';
8 +
9 +const IS_CHROME = navigator.userAgent.indexOf('Firefox') < 0;
10 +
11 +let browserName;
12 +let themeName;
13 +
14 +if (IS_CHROME) {
15 + browserName = 'Chrome';
16 +
17 + // chrome.devtools.panels added in Chrome 18.
18 + // chrome.devtools.panels.themeName added in Chrome 54.
19 + themeName = chrome.devtools.panels.themeName === 'dark' ? 'Dark' : 'Default';
20 +} else {
21 + browserName = 'Firefox';
22 +
23 + // chrome.devtools.panels.themeName added in Firefox 55.
24 + // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/devtools.panels/themeName
25 + if (chrome.devtools && chrome.devtools.panels) {
26 + switch (chrome.devtools.panels.themeName) {
27 + case 'dark':
28 + themeName = 'Dark';
29 + break;
30 + default:
31 + themeName = 'Light';
32 + break;
33 + }
34 + }
35 +}
36 +
37 +const node = ((document.getElementById('container'): any): HTMLElement);
38 +
39 +inject(chrome.runtime.getURL('build/backend.js'), () => {
40 + let disconnected = false;
41 +
42 + const port = chrome.runtime.connect({
43 + name: '' + chrome.devtools.inspectedWindow.tabId,
44 + });
45 + port.onDisconnect.addListener(() => {
46 + disconnected = true;
47 + });
48 +
49 + const bridge = new Bridge({
50 + listen(fn) {
51 + port.onMessage.addListener(message => fn(message));
52 + },
53 + send(data) {
54 + if (disconnected) {
55 + return;
56 + }
57 + port.postMessage(data);
58 + },
59 + });
60 +
61 + render(createElement(App, { bridge, browserName, themeName }), node);
62 +});
shells/browser/shared/webpack.backend.js renamed
+10 -2
@@ -1,7 +1,8 @@
1 const { readFileSync } = require('fs');
2 const { resolve } = require('path');
3
4 -const __DEV__ = process.env.NODE_ENV !== 'production';
4 +//const __DEV__ = process.env.NODE_ENV !== 'production';
5 +const __DEV__ = true; // TODO
6
7 module.exports = {
8 mode: __DEV__ ? 'development' : 'production',
@@ -13,12 +14,19 @@ module.exports = {
14 path: __dirname + '/build',
15 filename: '[name].js',
16 },
17 + resolve: {
18 + alias: {
19 + src: resolve(__dirname, '../../../src'),
20 + },
21 + },
22 module: {
23 rules: [
24 {
25 test: /\.js$/,
26 loader: 'babel-loader',
21 - options: JSON.parse(readFileSync(resolve(__dirname, '../../.babelrc'))),
27 + options: JSON.parse(
28 + readFileSync(resolve(__dirname, '../../../.babelrc'))
29 + ),
30 },
31 ],
32 },
shells/browser/shared/webpack.config.js renamed
+10 -2
@@ -2,7 +2,8 @@ const { readFileSync } = require('fs');
2 const { resolve } = require('path');
3 const webpack = require('webpack');
4
5 -const __DEV__ = process.env.NODE_ENV !== 'production';
5 +//const __DEV__ = process.env.NODE_ENV !== 'production';
6 +const __DEV__ = true; // TODO
7
8 module.exports = {
9 mode: __DEV__ ? 'development' : 'production',
@@ -18,6 +19,11 @@ module.exports = {
19 path: __dirname + '/build',
20 filename: '[name].js',
21 },
22 + resolve: {
23 + alias: {
24 + src: resolve(__dirname, '../../../src'),
25 + },
26 + },
27 plugins: __DEV__
28 ? []
29 : [
@@ -31,7 +37,9 @@ module.exports = {
37 {
38 test: /\.js$/,
39 loader: 'babel-loader',
34 - options: JSON.parse(readFileSync(resolve(__dirname, '../../.babelrc'))),
40 + options: JSON.parse(
41 + readFileSync(resolve(__dirname, '../../../.babelrc'))
42 + ),
43 },
44 {
45 test: /\.css$/,
shells/createConfig.js deleted
-148
@@ -1,148 +0,0 @@
1 -const path = require('path');
2 -const webpack = require('webpack');
3 -const merge = require('webpack-merge');
4 -const { VueLoaderPlugin } = require('vue-loader');
5 -const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
6 -
7 -module.exports = (config, target = { chrome: 52, firefox: 48 }) => {
8 - const bubleOptions = {
9 - target,
10 - objectAssign: 'Object.assign',
11 - transforms: {
12 - forOf: false,
13 - modules: false,
14 - },
15 - };
16 -
17 - const baseConfig = {
18 - mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
19 - resolve: {
20 - alias: {
21 - src: path.resolve(__dirname, '../src'),
22 - views: path.resolve(__dirname, '../src/devtools/views'),
23 - components: path.resolve(__dirname, '../src/devtools/components'),
24 - filters: path.resolve(__dirname, '../src/devtools/filters'),
25 - },
26 - },
27 - module: {
28 - rules: [
29 - {
30 - test: /\.js$/,
31 - loader: 'buble-loader',
32 - exclude: /node_modules|vue\/dist|vuex\/dist/,
33 - options: bubleOptions,
34 - },
35 - {
36 - test: /\.vue$/,
37 - loader: 'vue-loader',
38 - options: {
39 - compilerOptions: {
40 - preserveWhitespace: false,
41 - },
42 - transpileOptions: bubleOptions,
43 - },
44 - },
45 - {
46 - test: /\.css$/,
47 - use: ['vue-style-loader', 'css-loader', 'postcss-loader'],
48 - },
49 - {
50 - test: /\.styl(us)?$/,
51 - use: [
52 - 'vue-style-loader',
53 - 'css-loader',
54 - 'postcss-loader',
55 - 'stylus-loader',
56 - {
57 - loader: 'style-resources-loader',
58 - options: {
59 - patterns: [
60 - path.resolve(__dirname, '../src/devtools/style/imports.styl'),
61 - ],
62 - },
63 - },
64 - ],
65 - },
66 - {
67 - test: /\.(png|woff2)$/,
68 - loader: 'url-loader?limit=0',
69 - },
70 - ],
71 - },
72 - performance: {
73 - hints: false,
74 - },
75 - plugins: [
76 - new VueLoaderPlugin(),
77 - ...(process.env.VUE_DEVTOOL_TEST ? [] : [new FriendlyErrorsPlugin()]),
78 - new webpack.DefinePlugin({
79 - 'process.env.RELEASE_CHANNEL': JSON.stringify(
80 - process.env.RELEASE_CHANNEL || 'stable'
81 - ),
82 - }),
83 - ],
84 - devServer: {
85 - port: process.env.PORT,
86 - },
87 - stats: {
88 - colors: true,
89 - },
90 - };
91 -
92 - if (process.env.NODE_ENV === 'production') {
93 - const UglifyPlugin = require('uglifyjs-webpack-plugin');
94 - baseConfig.plugins.push(
95 - new webpack.DefinePlugin({
96 - 'process.env.NODE_ENV': '"production"',
97 - })
98 - );
99 - baseConfig.optimization = {
100 - minimizer: [
101 - new UglifyPlugin({
102 - exclude: /backend/,
103 - uglifyOptions: {
104 - compress: {
105 - // turn off flags with small gains to speed up minification
106 - arrows: false,
107 - collapse_vars: false, // 0.3kb
108 - comparisons: false,
109 - computed_props: false,
110 - hoist_funs: false,
111 - hoist_props: false,
112 - hoist_vars: false,
113 - inline: false,
114 - loops: false,
115 - negate_iife: false,
116 - properties: false,
117 - reduce_funcs: false,
118 - reduce_vars: false,
119 - switches: false,
120 - toplevel: false,
121 - typeofs: false,
122 -
123 - // a few flags with noticable gains/speed ratio
124 - // numbers based on out of the box vendor bundle
125 - booleans: true, // 0.7kb
126 - if_return: true, // 0.4kb
127 - sequences: true, // 0.7kb
128 - unused: true, // 2.3kb
129 -
130 - // required features to drop conditional branches
131 - conditionals: true,
132 - dead_code: true,
133 - evaluate: true,
134 - },
135 - mangle: {
136 - safari10: true,
137 - },
138 - },
139 - sourceMap: false,
140 - cache: true,
141 - parallel: true,
142 - }),
143 - ],
144 - };
145 - }
146 -
147 - return merge(baseConfig, config);
148 -};
shells/dev/app/ElementTypes/index.js
+2 -1
@@ -15,7 +15,8 @@ import React, {
15 } from 'react';
16
17 const Context = createContext('abc');
18 -Context.displayName = 'testContext';
18 +// $FlowFixMe Flow doesn't support this property yet.
19 +Context.displayName = 'ExampleContext';
20
21 class ClassComponent extends Component<any> {
22 render() {
shells/dev/src/devtools.js
+5 -1
@@ -30,7 +30,11 @@ initDevTools({
30 cb(bridge);
31
32 render(
33 - createElement(App, { bridge }),
33 + createElement(App, {
34 + bridge,
35 + browserName: 'Chrome',
36 + themeName: 'light',
37 + }),
38 ((document.getElementById('devtools'): any): HTMLElement)
39 );
40 });
src/backend/agent.js
+1 -1
@@ -15,7 +15,7 @@ import type { Bridge } from '../types';
15 import type { Element } from 'src/devtools/types';
16
17 const debug = (methodName, ...args) => {
18 - //console.log(`%cAgent %c${methodName}`, 'color: blue; font-weight: bold;', 'font-weight: bold;', ...args);
18 + // debug(`%cAgent %c${methodName}`, 'color: blue; font-weight: bold;', 'font-weight: bold;', ...args);
19 };
20
21 const THROTTLE_BY_MS = 350;
src/backend/renderer.js
+1 -1
@@ -263,8 +263,8 @@ export function attach(
263 const symbolOrNumber =
264 typeof type === 'object' && type !== null ? type.$$typeof : type;
265
266 - // $FlowFixMe facebook/flow/issues/2362
266 const switchValue =
267 + // $FlowFixMe facebook/flow/issues/2362
268 typeof symbolOrNumber === 'symbol'
269 ? symbolOrNumber.toString()
270 : symbolOrNumber;
src/devtools/store.js
+1 -1
@@ -6,7 +6,7 @@ import type { Element } from './types';
6 import type { Bridge } from '../types';
7
8 const debug = (methodName, ...args) => {
9 - //console.log(`%cAgent %c${methodName}`, 'color: red; font-weight: bold;', 'font-weight: bold;', ...args);
9 + // console.log(`%cAgent %c${methodName}`, 'color: red; font-weight: bold;', 'font-weight: bold;', ...args);
10 };
11
12 /**
src/devtools/views/App.js
+4 -2
@@ -8,11 +8,13 @@ import styles from './App.css';
8
9 import type { Bridge } from '../../types';
10
11 -type Props = {|
11 +export type Props = {|
12 bridge: Bridge,
13 + browserName: string,
14 + themeName: string,
15 |};
16
15 -export default function App({ bridge }: Props) {
17 +export default function App({ bridge, browserName, themeName }: Props) {
18 const store = useMemo(() => new Store(bridge), []);
19
20 return (