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. 🚧</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. 🚧</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’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. ⌛</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. ✅</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. 🚧</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 (