From d57ec6956158037e7d69bf65175abcfcc9c7ee03 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 23 Jan 2019 18:06:21 -0800 Subject: [PATCH] Added initial Chrome/Firefox shells --- .eslintignore | 2 + .flowconfig | 3 + .prettierignore | 2 + package.json | 9 +- shells/browser/chrome/README.md | 12 ++ shells/browser/chrome/build.js | 20 +++ shells/browser/chrome/manifest.json | 48 +++++- shells/browser/chrome/test.js | 11 ++ shells/browser/firefox/README.md | 12 ++ shells/browser/firefox/build.js | 38 +++++ shells/browser/firefox/manifest.json | 52 ++++++ shells/browser/firefox/test.js | 46 ++++++ shells/browser/shared/build.js | 93 +++++++++++ shells/browser/shared/icons/128-deadcode.png | Bin 0 -> 5201 bytes .../browser/shared/icons/128-development.png | Bin 0 -> 5201 bytes shells/browser/shared/icons/128-disabled.png | Bin 0 -> 4792 bytes shells/browser/shared/icons/128-outdated.png | Bin 0 -> 4162 bytes .../browser/shared/icons/128-production.png | Bin 0 -> 4577 bytes .../browser/shared/icons/128-unminified.png | Bin 0 -> 5201 bytes shells/browser/shared/icons/16-deadcode.png | Bin 0 -> 638 bytes .../browser/shared/icons/16-development.png | Bin 0 -> 638 bytes shells/browser/shared/icons/16-disabled.png | Bin 0 -> 494 bytes shells/browser/shared/icons/16-outdated.png | Bin 0 -> 558 bytes shells/browser/shared/icons/16-production.png | Bin 0 -> 546 bytes shells/browser/shared/icons/16-unminified.png | Bin 0 -> 638 bytes shells/browser/shared/icons/32-deadcode.png | Bin 0 -> 1318 bytes .../browser/shared/icons/32-development.png | Bin 0 -> 1318 bytes shells/browser/shared/icons/32-disabled.png | Bin 0 -> 1092 bytes shells/browser/shared/icons/32-outdated.png | Bin 0 -> 1095 bytes shells/browser/shared/icons/32-production.png | Bin 0 -> 1068 bytes shells/browser/shared/icons/32-unminified.png | Bin 0 -> 1318 bytes shells/browser/shared/icons/48-deadcode.png | Bin 0 -> 2001 bytes .../browser/shared/icons/48-development.png | Bin 0 -> 2001 bytes shells/browser/shared/icons/48-disabled.png | Bin 0 -> 1564 bytes shells/browser/shared/icons/48-outdated.png | Bin 0 -> 1570 bytes shells/browser/shared/icons/48-production.png | Bin 0 -> 1674 bytes shells/browser/shared/icons/48-unminified.png | Bin 0 -> 2001 bytes shells/browser/shared/icons/deadcode.svg | 1 + shells/browser/shared/icons/development.svg | 1 + shells/browser/shared/icons/disabled.svg | 1 + shells/browser/shared/icons/outdated.svg | 1 + shells/browser/shared/icons/production.svg | 1 + shells/browser/shared/main.html | 9 ++ shells/browser/shared/panel.html | 32 ++++ shells/browser/shared/popups/deadcode.html | 32 ++++ shells/browser/shared/popups/development.html | 28 ++++ shells/browser/shared/popups/disabled.html | 21 +++ shells/browser/shared/popups/outdated.html | 29 ++++ shells/browser/shared/popups/production.html | 21 +++ shells/browser/shared/popups/shared.js | 22 +++ shells/browser/shared/popups/unminified.html | 31 ++++ shells/browser/shared/src/GlobalHook.js | 64 ++++++++ shells/browser/shared/src/backend.js | 67 ++++++++ shells/browser/shared/src/background.js | 111 +++++++++++++ shells/browser/shared/src/checkForReact.js | 12 ++ shells/browser/shared/src/contentScript.js | 52 ++++++ shells/browser/shared/src/inject.js | 21 +++ shells/browser/shared/src/main.js | 40 +++++ shells/browser/shared/src/panel.js | 62 ++++++++ .../browser/{ => shared}/webpack.backend.js | 12 +- shells/browser/{ => shared}/webpack.config.js | 12 +- shells/createConfig.js | 148 ------------------ shells/dev/app/ElementTypes/index.js | 3 +- shells/dev/src/devtools.js | 6 +- src/backend/agent.js | 2 +- src/backend/renderer.js | 2 +- src/devtools/store.js | 2 +- src/devtools/views/App.js | 6 +- 68 files changed, 1032 insertions(+), 168 deletions(-) create mode 100644 shells/browser/chrome/README.md create mode 100644 shells/browser/chrome/build.js create mode 100644 shells/browser/chrome/test.js create mode 100644 shells/browser/firefox/README.md create mode 100644 shells/browser/firefox/build.js create mode 100644 shells/browser/firefox/manifest.json create mode 100644 shells/browser/firefox/test.js create mode 100644 shells/browser/shared/build.js create mode 100644 shells/browser/shared/icons/128-deadcode.png create mode 100644 shells/browser/shared/icons/128-development.png create mode 100644 shells/browser/shared/icons/128-disabled.png create mode 100644 shells/browser/shared/icons/128-outdated.png create mode 100644 shells/browser/shared/icons/128-production.png create mode 100644 shells/browser/shared/icons/128-unminified.png create mode 100644 shells/browser/shared/icons/16-deadcode.png create mode 100644 shells/browser/shared/icons/16-development.png create mode 100644 shells/browser/shared/icons/16-disabled.png create mode 100644 shells/browser/shared/icons/16-outdated.png create mode 100644 shells/browser/shared/icons/16-production.png create mode 100644 shells/browser/shared/icons/16-unminified.png create mode 100644 shells/browser/shared/icons/32-deadcode.png create mode 100644 shells/browser/shared/icons/32-development.png create mode 100644 shells/browser/shared/icons/32-disabled.png create mode 100644 shells/browser/shared/icons/32-outdated.png create mode 100644 shells/browser/shared/icons/32-production.png create mode 100644 shells/browser/shared/icons/32-unminified.png create mode 100644 shells/browser/shared/icons/48-deadcode.png create mode 100644 shells/browser/shared/icons/48-development.png create mode 100644 shells/browser/shared/icons/48-disabled.png create mode 100644 shells/browser/shared/icons/48-outdated.png create mode 100644 shells/browser/shared/icons/48-production.png create mode 100644 shells/browser/shared/icons/48-unminified.png create mode 100644 shells/browser/shared/icons/deadcode.svg create mode 100644 shells/browser/shared/icons/development.svg create mode 100644 shells/browser/shared/icons/disabled.svg create mode 100644 shells/browser/shared/icons/outdated.svg create mode 100644 shells/browser/shared/icons/production.svg create mode 100644 shells/browser/shared/main.html create mode 100644 shells/browser/shared/panel.html create mode 100644 shells/browser/shared/popups/deadcode.html create mode 100644 shells/browser/shared/popups/development.html create mode 100644 shells/browser/shared/popups/disabled.html create mode 100644 shells/browser/shared/popups/outdated.html create mode 100644 shells/browser/shared/popups/production.html create mode 100644 shells/browser/shared/popups/shared.js create mode 100644 shells/browser/shared/popups/unminified.html create mode 100644 shells/browser/shared/src/GlobalHook.js create mode 100644 shells/browser/shared/src/backend.js create mode 100644 shells/browser/shared/src/background.js create mode 100644 shells/browser/shared/src/checkForReact.js create mode 100644 shells/browser/shared/src/contentScript.js create mode 100644 shells/browser/shared/src/inject.js create mode 100644 shells/browser/shared/src/main.js create mode 100644 shells/browser/shared/src/panel.js rename shells/browser/{ => shared}/webpack.backend.js (61%) rename shells/browser/{ => shared}/webpack.config.js (80%) delete mode 100644 shells/createConfig.js diff --git a/.eslintignore b/.eslintignore index 2b7e221a11..c21ee38faa 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,7 @@ node_modules +shells/browser/chrome/build +shells/browser/firefox/build shells/dev/build package-lock.json diff --git a/.flowconfig b/.flowconfig index 784a049997..35306bd54a 100644 --- a/.flowconfig +++ b/.flowconfig @@ -13,6 +13,9 @@ .*node_modules/webpack.* .*node_modules/fbjs/flow.* .*node_modules/web-ext.* +shells/browser/chrome/build/* +shells/browser/firefox/build/* +shells/dev/build/* [include] diff --git a/.prettierignore b/.prettierignore index 2b7e221a11..c21ee38faa 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,7 @@ node_modules +shells/browser/chrome/build +shells/browser/firefox/build shells/dev/build package-lock.json diff --git a/package.json b/package.json index 8dc27444c4..043c75ee8c 100644 --- a/package.json +++ b/package.json @@ -12,18 +12,17 @@ }, "scripts": { "build:extension": "yarn run build:extension:chrome && yarn run build:extension:firefox", - "build:extension:chrome": "node ./shells/chrome/build", - "build:extension:firefox": "node ./shells/firefox/build", + "build:extension:chrome": "node ./shells/browser/chrome/build", + "build:extension:firefox": "node ./shells/browser/firefox/build", "build:standalone": "cd packages/react-devtools-core && yarn run build", - "deploy": "cd ./shells/theme-preview && ./build.sh && gh-pages -d .", "linc": "lint-staged", "lint": "eslint '**/*.js'", "precommit": "lint-staged", "prettier": "prettier --write '**/*.{js,json,css}'", "start": "cd ./shells/dev && open ./index.html && webpack --config webpack.config.js --watch", "test": "jest", - "test:chrome": "node ./shells/chrome/test", - "test:firefox": "node ./shells/firefox/test", + "test:chrome": "node ./shells/browser/chrome/test", + "test:firefox": "node ./shells/browser/firefox/test", "test:standalone": "cd packages/react-devtools && yarn start", "typecheck": "flow check" }, diff --git a/shells/browser/chrome/README.md b/shells/browser/chrome/README.md new file mode 100644 index 0000000000..dd77d072c8 --- /dev/null +++ b/shells/browser/chrome/README.md @@ -0,0 +1,12 @@ +# The Chrome extension + +The source code for this extension has moved to `shells/webextension`. + +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. + +## Testing in Chrome + +You can test a local build of the web extension like so: + + 1. Build the extension: `node build` + 1. Follow the on-screen instructions. diff --git a/shells/browser/chrome/build.js b/shells/browser/chrome/build.js new file mode 100644 index 0000000000..1311527d59 --- /dev/null +++ b/shells/browser/chrome/build.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const chalk = require('chalk'); +const { join } = require('path'); +const build = require('../shared/build'); + +const main = async () => { + await build( + 'chrome', + join(__dirname, 'manifest.json'), + join(__dirname, 'build') + ); + + console.log(chalk.green('\nThe Chrome extension has been built!')); + console.log(chalk.green('You can test this build by running:')); + console.log(chalk.gray('\n# From the react-devtools root directory:')); + console.log('yarn run test:chrome'); +}; + +main(); diff --git a/shells/browser/chrome/manifest.json b/shells/browser/chrome/manifest.json index 9d6543254f..c22e4b3849 100644 --- a/shells/browser/chrome/manifest.json +++ b/shells/browser/chrome/manifest.json @@ -1,6 +1,46 @@ { - "name": "React v16 DevTools", - "version": "0.1", - "description": "DevTools for React version 16.0+", - "manifest_version": 2 + "manifest_version": 2, + "name": "React Developer Tools", + "description": "Adds React debugging tools to the Chrome Developer Tools.", + "version": "4.0.0", + + "minimum_chrome_version": "49", + + "icons": { + "16": "icons/16-production.png", + "32": "icons/32-production.png", + "48": "icons/48-production.png", + "128": "icons/128-production.png" + }, + + "browser_action": { + "default_icon": { + "16": "icons/16-disabled.png", + "32": "icons/32-disabled.png", + "48": "icons/48-disabled.png", + "128": "icons/128-disabled.png" + }, + + "default_popup": "popups/disabled.html" + }, + + "devtools_page": "main.html", + + "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", + "web_accessible_resources": ["main.html", "panel.html", "build/backend.js"], + + "background": { + "scripts": ["build/background.js"], + "persistent": false + }, + + "permissions": ["file:///*", "http://*/*", "https://*/*"], + + "content_scripts": [ + { + "matches": [""], + "js": ["build/inject.js"], + "run_at": "document_start" + } + ] } diff --git a/shells/browser/chrome/test.js b/shells/browser/chrome/test.js new file mode 100644 index 0000000000..9b9bbbf98f --- /dev/null +++ b/shells/browser/chrome/test.js @@ -0,0 +1,11 @@ +#!/usr/bin/env node + +const chromeLaunch = require('chrome-launch'); // eslint-disable-line import/no-extraneous-dependencies +const { resolve } = require('path'); + +const EXTENSION_PATH = resolve('shells/chrome/build/unpacked'); +const START_URL = 'https://facebook.github.io/react/'; + +chromeLaunch(START_URL, { + args: [`--load-extension=${EXTENSION_PATH}`], +}); diff --git a/shells/browser/firefox/README.md b/shells/browser/firefox/README.md new file mode 100644 index 0000000000..c4bbc8d2de --- /dev/null +++ b/shells/browser/firefox/README.md @@ -0,0 +1,12 @@ +# The Firefox extension + +The source code for this extension has moved to `shells/webextension`. + +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. + +## Testing in Firefox + + 1. Build the extension: `node build` + 1. Follow the on-screen instructions. + +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. diff --git a/shells/browser/firefox/build.js b/shells/browser/firefox/build.js new file mode 100644 index 0000000000..993399e278 --- /dev/null +++ b/shells/browser/firefox/build.js @@ -0,0 +1,38 @@ +#!/usr/bin/env node + +const chalk = require('chalk'); +const { join } = require('path'); +const build = require('../shared/build'); + +const main = async () => { + await build( + 'firefox', + join(__dirname, 'manifest.json'), + join(__dirname, 'build') + ); + + console.log(chalk.green('\nThe Firefox extension has been built!')); + console.log(chalk.green('You can test this build by running:')); + console.log(chalk.gray('\n# From the react-devtools root directory:')); + console.log('yarn run test:firefox'); + console.log( + chalk.gray('\n# You can also test against upcoming Firefox releases.') + ); + console.log( + chalk.gray( + '# First download a release from https://www.mozilla.org/en-US/firefox/channel/desktop/' + ) + ); + console.log( + chalk.gray( + '# And then tell web-ext which release to use (eg firefoxdeveloperedition, nightly, beta):' + ) + ); + console.log('WEB_EXT_FIREFOX=nightly yarn run test:firefox'); + console.log(chalk.gray('\n# You can test against older versions too:')); + console.log( + 'WEB_EXT_FIREFOX=/Applications/Firefox52.app/Contents/MacOS/firefox-bin yarn run test:firefox' + ); +}; + +main(); diff --git a/shells/browser/firefox/manifest.json b/shells/browser/firefox/manifest.json new file mode 100644 index 0000000000..e0015a59ff --- /dev/null +++ b/shells/browser/firefox/manifest.json @@ -0,0 +1,52 @@ +{ + "manifest_version": 2, + "name": "React Developer Tools", + "description": "Adds React debugging tools to the Firefox Developer Tools.", + "version": "4.0.0", + + "applications": { + "gecko": { + "id": "@react-devtools", + "strict_min_version": "54.0" + } + }, + + "icons": { + "16": "icons/16-production.png", + "32": "icons/32-production.png", + "48": "icons/48-production.png", + "128": "icons/128-production.png" + }, + + "browser_action": { + "default_icon": { + "16": "icons/16-disabled.png", + "32": "icons/32-disabled.png", + "48": "icons/48-disabled.png", + "128": "icons/128-disabled.png" + }, + + "default_popup": "popups/disabled.html", + "browser_style": true + }, + + "devtools_page": "main.html", + + "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", + "web_accessible_resources": ["main.html", "panel.html", "build/backend.js"], + + "background": { + "scripts": ["build/background.js"], + "persistent": false + }, + + "permissions": ["file:///*", "http://*/*", "https://*/*"], + + "content_scripts": [ + { + "matches": [""], + "js": ["build/inject.js"], + "run_at": "document_start" + } + ] +} diff --git a/shells/browser/firefox/test.js b/shells/browser/firefox/test.js new file mode 100644 index 0000000000..850d87c0f7 --- /dev/null +++ b/shells/browser/firefox/test.js @@ -0,0 +1,46 @@ +#!/usr/bin/env node + +const { exec } = require('child-process-promise'); +const { Finder } = require('firefox-profile'); +const { resolve } = require('path'); + +const EXTENSION_PATH = resolve('shells/firefox/build/unpacked'); +const START_URL = 'https://facebook.github.io/react/'; + +const main = async () => { + const finder = new Finder(); + + // Use default Firefox profile for testing purposes. + // This prevents users from having to re-login-to sites before testing. + const findPathPromise = new Promise((resolvePromise, rejectPromise) => { + finder.getPath('default', (error, profile) => { + if (error) { + rejectPromise(error); + } else { + resolvePromise(profile); + } + }); + }); + + const options = [ + `--source-dir=${EXTENSION_PATH}`, + `--start-url=${START_URL}`, + '--browser-console', + ]; + + try { + const path = await findPathPromise; + const trimmedPath = path.replace(' ', '\\ '); + options.push(`--firefox-profile=${trimmedPath}`); + } catch (err) { + console.warn('Could not find default profile, using temporary profile.'); + } + + try { + await exec(`web-ext run ${options.join(' ')}`); + } catch (err) { + console.error('`web-ext run` failed', err.stdout, err.stderr); + } +}; + +main(); diff --git a/shells/browser/shared/build.js b/shells/browser/shared/build.js new file mode 100644 index 0000000000..5f6747af54 --- /dev/null +++ b/shells/browser/shared/build.js @@ -0,0 +1,93 @@ +#!/usr/bin/env node + +const AdmZip = require('adm-zip'); +const { execSync } = require('child_process'); +const { copy, ensureDir, move, remove } = require('fs-extra'); +const { join } = require('path'); + +// These files are copied along with Webpack-bundled files +// to produce the final web extension +const STATIC_FILES = ['icons', 'popups', 'main.html', 'panel.html']; + +const preProcess = async (destinationPath, tempPath) => { + await remove(destinationPath); // Clean up from previously completed builds + await remove(tempPath); // Clean up from any previously failed builds + await ensureDir(tempPath); // Create temp dir for this new build +}; + +const build = async (tempPath, manifestPath) => { + const binPath = join(tempPath, 'bin'); + const zipPath = join(tempPath, 'zip'); + + const webpackPath = join( + __dirname, + '..', + '..', + '..', + 'node_modules', + '.bin', + 'webpack' + ); + execSync( + `${webpackPath} --config webpack.config.js --output-path ${binPath}`, + { + cwd: __dirname, + env: Object.assign({}, process.env, { NODE_ENV: 'production' }), + stdio: 'inherit', + } + ); + execSync( + `${webpackPath} --config webpack.backend.js --output-path ${binPath}`, + { + cwd: __dirname, + env: Object.assign({}, process.env, { NODE_ENV: 'production' }), + stdio: 'inherit', + } + ); + + // Make temp dir + await ensureDir(zipPath); + + // Copy unbuilt source files to zip dir to be packaged: + await copy(binPath, join(zipPath, 'build')); + await copy(manifestPath, join(zipPath, 'manifest.json')); + await Promise.all( + STATIC_FILES.map(file => copy(join(__dirname, file), join(zipPath, file))) + ); + + // Pack the extension + const zip = new AdmZip(); + zip.addLocalFolder(zipPath); + zip.writeZip(join(tempPath, 'packed.zip')); +}; + +const postProcess = async (tempPath, destinationPath) => { + const unpackedSourcePath = join(tempPath, 'zip'); + const packedSourcePath = join(tempPath, 'packed.zip'); + const packedDestPath = join(destinationPath, 'packed.zip'); + const unpackedDestPath = join(destinationPath, 'unpacked'); + + await move(unpackedSourcePath, unpackedDestPath); // Copy built files to destination + await move(packedSourcePath, packedDestPath); // Copy built files to destination + await remove(tempPath); // Clean up temp directory and files +}; + +const main = async (buildId, manifestPath, destinationPath) => { + try { + const tempPath = join(__dirname, 'build', buildId); + await preProcess(destinationPath, tempPath); + await build(tempPath, manifestPath); + + const builtUnpackedPath = join(destinationPath, 'unpacked'); + await postProcess(tempPath, destinationPath); + + return builtUnpackedPath; + } catch (error) { + console.error(error); + process.exit(1); + } + + return null; +}; + +module.exports = main; diff --git a/shells/browser/shared/icons/128-deadcode.png b/shells/browser/shared/icons/128-deadcode.png new file mode 100644 index 0000000000000000000000000000000000000000..b6ecc88e13ac082da3b82b48179b8a665fef6214 GIT binary patch literal 5201 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 LNkvXXu0mjfgeJQI literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/128-development.png b/shells/browser/shared/icons/128-development.png new file mode 100644 index 0000000000000000000000000000000000000000..b6ecc88e13ac082da3b82b48179b8a665fef6214 GIT binary patch literal 5201 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 LNkvXXu0mjfgeJQI literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/128-disabled.png b/shells/browser/shared/icons/128-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..67b1c9a31a6dfe22567ce26e385b0be06022e3c2 GIT binary patch literal 4792 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 Sp@}{K0000W>yK& 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 M07*qoM6N<$f@qJob^rhX literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/128-production.png b/shells/browser/shared/icons/128-production.png new file mode 100644 index 0000000000000000000000000000000000000000..b9327946441f529cd5510f8ee14b788f0aaea1df GIT binary patch literal 4577 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 LNkvXXu0mjfgeJQI literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/16-deadcode.png b/shells/browser/shared/icons/16-deadcode.png new file mode 100644 index 0000000000000000000000000000000000000000..33d99798e07b27bd48862e9d5779206bc96f9e4b GIT binary patch literal 638 zcmV-^0)hRBP)0 z5Ee=iwzklA@y)=lrIw46Om=qOn|a^&y?M58v8zh}>hXUTmx_zswGHq$HAC`vt(+rt z5oOnJ@LG}8OU1>(+Z|9M)wuj(gd0&I#gf;DaNhv06`H;9$5q#WMK8*HAt+4rwLM`cMUdq%=7LO-CU}b*ZqMO%`w2<$H1O2(= zHYz%M8Gazvii01k@Fg^yOKPa~D40&Bxk>1Z3@w{S^nMQ#!>^GQRBd7s56!M+EjQtB zj6whM8Ql*bVQz09sl0)AH9gg-kg%(aRvV9NQnZ~o9 zzZk{fm2!Aw+ld1$4NRk1mpwjY)bC77&E&8_!ugbvH$({Hg)g#j&dDN9JNs$R`Huht Y0J(_O53sM9y8r+H07*qoM6N<$g5#(xe*gdg literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/16-development.png b/shells/browser/shared/icons/16-development.png new file mode 100644 index 0000000000000000000000000000000000000000..33d99798e07b27bd48862e9d5779206bc96f9e4b GIT binary patch literal 638 zcmV-^0)hRBP)0 z5Ee=iwzklA@y)=lrIw46Om=qOn|a^&y?M58v8zh}>hXUTmx_zswGHq$HAC`vt(+rt z5oOnJ@LG}8OU1>(+Z|9M)wuj(gd0&I#gf;DaNhv06`H;9$5q#WMK8*HAt+4rwLM`cMUdq%=7LO-CU}b*ZqMO%`w2<$H1O2(= zHYz%M8Gazvii01k@Fg^yOKPa~D40&Bxk>1Z3@w{S^nMQ#!>^GQRBd7s56!M+EjQtB zj6whM8Ql*bVQz09sl0)AH9gg-kg%(aRvV9NQnZ~o9 zzZk{fm2!Aw+ld1$4NRk1mpwjY)bC77&E&8_!ugbvH$({Hg)g#j&dDN9JNs$R`Huht Y0J(_O53sM9y8r+H07*qoM6N<$g5#(xe*gdg literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/16-disabled.png b/shells/browser/shared/icons/16-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..2f0317ea3fd6b7933988fb03624d7fa0d1981d36 GIT binary patch literal 494 zcmVQ9n z-goZ3=iI!SBuOXCkEFj@Dp$d->$WD7$#u8eRVij!*4cDAy+f{2fMZX|*@dg!Zuc5R zk)5XL8!L3rfc-qrSM&M&0bj>)3=wls0G9y5nRUHD5jdOXyqYJMtE3b~;iDUd(JG2! z4e#}Omz0m=cwNQtO6YuFh($ literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/16-outdated.png b/shells/browser/shared/icons/16-outdated.png new file mode 100644 index 0000000000000000000000000000000000000000..aa42bfe0b4e0c791fa1bb7f48e989729028bf481 GIT binary patch literal 558 zcmV+}0@3}6P)KQz112mgW=yWf z7&`QNy@ewN4F-c$iJeRVkbvYwB0*U&Gjn7;z&lx%DS;ClE0<|!-%WkN#ya8VIPON% zG^kdqq*an6(mtEbpj<9PtJQ*7EJoGo?+O&I{qXSkwTysG#1#&Qtwy6^>AG(9`+duf z$z($J+M8sV-?DXiabz(bXMY}iXPkpnDh1hWmMX!?!~MzeHi!ovxT}4_`Pl&oJMPVM zz<^}bj=I&?Ar!9sWIs*sQlLzFZ+#N6%k6fX#ulHhR4S1zGbgc(hVest40K!jSPl{c wHj)hy8y->g%(L=qRXz<33h=Mr`B#7e0LAj*R5Z^J3jhEB07*qoM6N<$f@F>Jr2qf` literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/16-production.png b/shells/browser/shared/icons/16-production.png new file mode 100644 index 0000000000000000000000000000000000000000..1c253dca5d56d6a309c5854b8a82266dfce01aba GIT binary patch literal 546 zcmV+-0^R+IP)Kjq`cRJc0luV*{T{`ATe)vFcW8~a^U z&1IW8S~VsC9cYk*rG)-jtNe~YB-#QCyfAn9CdEELe=9ukUS~A3Um(&+k`%_QL#q+K zB>6_EcAw=DDz_ObB@^^s%=6`TyOU$!2?em%qZ1rrP9PER2bOm@F|#qpgubK_lsk%W zaS#ixnj8R{(gft+VrU|`(3#tG;g8oMcQaTW)H4}mXt?ljx=+s+G3xS-F((~^UNSnI z4cOH>7UfQo=xG$0N|06!l;X?+F3D&~5O9a6V{f_i5|To~O@pf4C| z(Q=Cp>eVuTAr|qpNX$Kp30 z5Ee=iwzklA@y)=lrIw46Om=qOn|a^&y?M58v8zh}>hXUTmx_zswGHq$HAC`vt(+rt z5oOnJ@LG}8OU1>(+Z|9M)wuj(gd0&I#gf;DaNhv06`H;9$5q#WMK8*HAt+4rwLM`cMUdq%=7LO-CU}b*ZqMO%`w2<$H1O2(= zHYz%M8Gazvii01k@Fg^yOKPa~D40&Bxk>1Z3@w{S^nMQ#!>^GQRBd7s56!M+EjQtB zj6whM8Ql*bVQz09sl0)AH9gg-kg%(aRvV9NQnZ~o9 zzZk{fm2!Aw+ld1$4NRk1mpwjY)bC77&E&8_!ugbvH$({Hg)g#j&dDN9JNs$R`Huht Y0J(_O53sM9y8r+H07*qoM6N<$g5#(xe*gdg literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/32-deadcode.png b/shells/browser/shared/icons/32-deadcode.png new file mode 100644 index 0000000000000000000000000000000000000000..c4a6bda3e6a6d0df9388f3beff37e254a91df25c GIT binary patch literal 1318 zcmV+>1=;$EP)_r`D3!8(9ZZPOzH)}VTUMvIY zO?R^j>(!KY%-^(KY@6hyO=1$i=S{zOPI^pY9P@?4NxppN{dwN!eZCXhUxyAf5z(Z$ zUZ?-pu8V6xc|pNxGv=i7f`XTluvgpRXltjYH;<5`wVl@IW-0mmG-VebSnl=g*+<@R zFS)zAX=C9Q#U?IOUQbr^Zxb9SR0{sm7vGWM)hI0-x`boc%1=L-oMT_;<5Sci62KY~ z#9rM0Ir(nIBq}0+$`Kq+kN`dh2ai!etfSiK*jZA&KH7fz9PeMgG|tJv8V?@kq_l7! z#UsDemjIvy>lJ2rV> zPPB)zOVNt{?bYB1;WRN?aOUdl=IUi%JHPp{v=T9U!GuGNuHKV+KnKwxfDHyd;Q*3Sm~WEj2^Ve#huV zvJ3G-0`Os_A29V~_s%oY!OqXdO9AhX4Aa`RNrv9N`&Ck0F8*zCe1x6O=^2Wwt#+F`A#dP{)ho9k}ilu0mNM?`MxkW$Lgg@Ehn#QnJYjdi!n9cEZ zDH^AFfs&P}WqP4VrUG86@kT=nB&%Bl60oq(y~Q!s#_p}rdpDQ)``)tZVek)s^1LvS zu*mp6YJTe-=0f`KqRIu29wVF6Ne@MP@GykMb9BKA|44D7E`Gi>0kE>j&Ma#H5MJ3dSWcSOWGjXQ zLYR=6r5MlyDg;Locg4tHO5}vF2CU{x&qP+&slVAvk(G5OBFrlgINcEtcUygj5wyQ3 zyR%$9?qQ#czU^t*oO?%U@7K`;-v?Wtuw2jO>CaTA{?ky^G|DBdU#9@+@sQ6H3@td= z)L@!`3t}?P-d<(Y>N<{bx>=c?D{#PaF#`O9Et+9GU8LvprM5g%0;nEmffY%o9_lPM zAehmh1yDdB(dJND9Y@~ literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/32-development.png b/shells/browser/shared/icons/32-development.png new file mode 100644 index 0000000000000000000000000000000000000000..c4a6bda3e6a6d0df9388f3beff37e254a91df25c GIT binary patch literal 1318 zcmV+>1=;$EP)_r`D3!8(9ZZPOzH)}VTUMvIY zO?R^j>(!KY%-^(KY@6hyO=1$i=S{zOPI^pY9P@?4NxppN{dwN!eZCXhUxyAf5z(Z$ zUZ?-pu8V6xc|pNxGv=i7f`XTluvgpRXltjYH;<5`wVl@IW-0mmG-VebSnl=g*+<@R zFS)zAX=C9Q#U?IOUQbr^Zxb9SR0{sm7vGWM)hI0-x`boc%1=L-oMT_;<5Sci62KY~ z#9rM0Ir(nIBq}0+$`Kq+kN`dh2ai!etfSiK*jZA&KH7fz9PeMgG|tJv8V?@kq_l7! z#UsDemjIvy>lJ2rV> zPPB)zOVNt{?bYB1;WRN?aOUdl=IUi%JHPp{v=T9U!GuGNuHKV+KnKwxfDHyd;Q*3Sm~WEj2^Ve#huV zvJ3G-0`Os_A29V~_s%oY!OqXdO9AhX4Aa`RNrv9N`&Ck0F8*zCe1x6O=^2Wwt#+F`A#dP{)ho9k}ilu0mNM?`MxkW$Lgg@Ehn#QnJYjdi!n9cEZ zDH^AFfs&P}WqP4VrUG86@kT=nB&%Bl60oq(y~Q!s#_p}rdpDQ)``)tZVek)s^1LvS zu*mp6YJTe-=0f`KqRIu29wVF6Ne@MP@GykMb9BKA|44D7E`Gi>0kE>j&Ma#H5MJ3dSWcSOWGjXQ zLYR=6r5MlyDg;Locg4tHO5}vF2CU{x&qP+&slVAvk(G5OBFrlgINcEtcUygj5wyQ3 zyR%$9?qQ#czU^t*oO?%U@7K`;-v?Wtuw2jO>CaTA{?ky^G|DBdU#9@+@sQ6H3@td= z)L@!`3t}?P-d<(Y>N<{bx>=c?D{#PaF#`O9Et+9GU8LvprM5g%0;nEmffY%o9_lPM zAehmh1yDdB(dJND9Y@~ literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/32-disabled.png b/shells/browser/shared/icons/32-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..7c750453238885d1cba45da238abc82ae0fcfa06 GIT binary patch literal 1092 zcmV-K1iSl*P)({HVg{3dAJt$@ z#3X9$`B{(LT;F@q28zzC44$pwO_=FdH71k~AWD^z$SK%5RKNa;O|+ zRx)u+O-(&LI5-%O#bPtt+uL3Euf@BvveE$F)6>&=JiEKQOYUNYO>2;{=NA_je=ms= ziNw##Ya-!Z67CTBNa;7MfuDKL&d!1&pT^K$gdzx9TwHvC{}9G=P=a~#`ZbhdP=v;K z@8;&_JMiI|wE^%7L!K9vvN4E)%!df<@>-9z{{Ea=f;- z=I-t74at1x=jY+Fva$v}`56t!&(FVANECXf-EJRDCX>HXsnl^}W8F** zxY#NFhoaWj))~C}e7?6!OH1vD#N_1U?5!R%o1K)xu%3KVrtp`Smv?P!Y_wEVR5&db z%d>)l0;kDjx@yIqo}TZ_TUc1wK;C{qM6fR!jfP+to_ad+xDsVAM@L6HY&KgLD+56| zgvR4>zg!zutMwH;W}X|UqclY#kvbHT+0xLkM61fkJL82;g?Y8l~e6=JCWDc+r-a0}g!Zu%TD~(9GEp=JD=RApR##WstE#G;gmk4M;sNCt^F@RN{O;}Tg_@e0 z=7YhYySlpCm(`63p;v1sP7OlMm;hciKB?gDcONhsjitz=Jm1&X*NzCZwYAOH)zwWP z68%InA9&=`^xnrY<@#8j^Lo99i>EJ6m71>06ef z?tK~c)XoeMKV;<88%FxEXj!(tzMk0K-JK(k|4=vlpo_)w@bGYdXJ_YEnb+g-_{oU( z4u>N^M*jxy$jFErGVXmNdQ4KTzfyBWNu(ft{ef!C#pn4y0R{m73@I68j+emz0000< KMNUMnLSTXgoDP}* literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/32-outdated.png b/shells/browser/shared/icons/32-outdated.png new file mode 100644 index 0000000000000000000000000000000000000000..6eae901bf058833312c3ccd677300e26f08bc3fb GIT binary patch literal 1095 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 literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/48-deadcode.png b/shells/browser/shared/icons/48-deadcode.png new file mode 100644 index 0000000000000000000000000000000000000000..f3224021a560c7d543178c3159c05cee9eebac9f GIT binary patch literal 2001 zcmV;?2QK)DP)So#v3TzJq(Wsh2i& zG*EU}yE;NUI=Ua^#KHHdqnNVGCe=}=#Ur0>;3YW+duL9bgXdyAqwo>N50zKe81@MAWi+;L1Q<+bnF0{c*V&D zTR-`lJXO`EYcpM)H2TG524rS|`h8UP&KY*x%qDJqN26CSXD$OJhu_e_kA3qMh40=W zEfC;na#ZVMQ5{hrkP&0nDnl*24ugY}3M?`w_r>*ib&^S#>%HpDAEkS8#p!0sEeSGs zq&qq?NRh{X(M)$Imq1?mMs^TD5oLUi*UjfIvt#1;-xr%{zVA0$ni%H;=V2Y^u0$g= z_e*zXG9b~PX#X~84N(O!M6v9|8IGu@YCD5M29{@M=+W6zmeED|T&K#RwhLU>phYkb49)Gc2mt6fpxWMm!iyGC0vSUYNiY^i22FbkYZz{r)>g74r2_yPJbH|q&ln}C ze>A9+5%duZItFvV&X76u3GRj2fLHVmzDu%T>@n#fD@Qad%>z&Z9ybBtKVCn;pkvY8 z&adV1h@y^DftTi%Kmd4*f$H=ji0{C6iIxHeV6kGbBrOEKE`9AS%5l3H?2`{e>Si6R zc{pWmPfY8mi5XF#1;kM62l@b;U0R_lgHv>Ce2#8UhUxBXglhdBa%(xPbygw~^6%M8 z`8#*16713MF{~&T&Y9^eoG<#y(gEB^JOzLMbN#cV!Gcu6L06za&k!6P|KWxX{_Xxz z>I+9{IJ(5g!2BY0OoZuBiI1Itki)gOho&Yxg0(m@MC0GzNNViq&fEM72axrJKl}Ay zu3{l0JB%jC;l!I_&Da~*1BiV-G^LzaPPz`CVeS3riu7nNDOn;K$YUeoP+GYv`YaR` z14H%+@N#p`MGp@rKV}p&Th+CO)4U_$0-|y6PZ&2gsyAXK7EO7s)%R$|h)BI-&uhP@J-BK}8RbEFp3Cw&Sv!4yj6Qzyl)9!C`0rZ3 zJEa4`qsR5RW0?WjRD+YPXbahq`j^rPHXhU0PgWL^-<6zLe1v3F7M^K!IIq!~L z>^yiLR9Ro>W#BmQzH@4xS{_cAGSuV9BA+MDfPyRBE*_G=0MHn2VH2TeDJ5i57AXhJ zni^CKXP1=tLX?m_?3JaDt`1G>&3RwV0W;u61HEG4bxkkuxvkqu>E**mRx!|;#2moG ziP$FHoU26xfeukJRo+}W`(T^_8SUEkG74xK4>~)C>I=N=5Xy1gavBlk=&A#Kf#yaj z$);U&5SsI<8dTD-rgI>&L=}D)HI@bRbB$Kz&7+p*i>bnf$=0WY8@pAeIy#` znLCgw`o`hZrfS~I+Ex8SVsoYvN)4akKo+{wC@06i4|vjeb_sCJibfG_VWdRb1PfhK zT;EXY=Sa)?r58$d2hwS1Z7tw^^-YXo2fbR>ssVrm;{cUelQa zjvlW=F1WXB+*OE=I<_kU&ZC=bXAyqBGs(fa11bfDNrJAHXHySze8 z6$O006LaJMp5R}k4W1oMAPBsj)AWAx#?M&<6kNNZw4A|&tm-Ta3Dmnf1JoR1Lly1e_2s) j>ar{oz*-0F{|YbwKA#E$9zpcP00000NkvXXu0mjf*nPMD literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/48-development.png b/shells/browser/shared/icons/48-development.png new file mode 100644 index 0000000000000000000000000000000000000000..f3224021a560c7d543178c3159c05cee9eebac9f GIT binary patch literal 2001 zcmV;?2QK)DP)So#v3TzJq(Wsh2i& zG*EU}yE;NUI=Ua^#KHHdqnNVGCe=}=#Ur0>;3YW+duL9bgXdyAqwo>N50zKe81@MAWi+;L1Q<+bnF0{c*V&D zTR-`lJXO`EYcpM)H2TG524rS|`h8UP&KY*x%qDJqN26CSXD$OJhu_e_kA3qMh40=W zEfC;na#ZVMQ5{hrkP&0nDnl*24ugY}3M?`w_r>*ib&^S#>%HpDAEkS8#p!0sEeSGs zq&qq?NRh{X(M)$Imq1?mMs^TD5oLUi*UjfIvt#1;-xr%{zVA0$ni%H;=V2Y^u0$g= z_e*zXG9b~PX#X~84N(O!M6v9|8IGu@YCD5M29{@M=+W6zmeED|T&K#RwhLU>phYkb49)Gc2mt6fpxWMm!iyGC0vSUYNiY^i22FbkYZz{r)>g74r2_yPJbH|q&ln}C ze>A9+5%duZItFvV&X76u3GRj2fLHVmzDu%T>@n#fD@Qad%>z&Z9ybBtKVCn;pkvY8 z&adV1h@y^DftTi%Kmd4*f$H=ji0{C6iIxHeV6kGbBrOEKE`9AS%5l3H?2`{e>Si6R zc{pWmPfY8mi5XF#1;kM62l@b;U0R_lgHv>Ce2#8UhUxBXglhdBa%(xPbygw~^6%M8 z`8#*16713MF{~&T&Y9^eoG<#y(gEB^JOzLMbN#cV!Gcu6L06za&k!6P|KWxX{_Xxz z>I+9{IJ(5g!2BY0OoZuBiI1Itki)gOho&Yxg0(m@MC0GzNNViq&fEM72axrJKl}Ay zu3{l0JB%jC;l!I_&Da~*1BiV-G^LzaPPz`CVeS3riu7nNDOn;K$YUeoP+GYv`YaR` z14H%+@N#p`MGp@rKV}p&Th+CO)4U_$0-|y6PZ&2gsyAXK7EO7s)%R$|h)BI-&uhP@J-BK}8RbEFp3Cw&Sv!4yj6Qzyl)9!C`0rZ3 zJEa4`qsR5RW0?WjRD+YPXbahq`j^rPHXhU0PgWL^-<6zLe1v3F7M^K!IIq!~L z>^yiLR9Ro>W#BmQzH@4xS{_cAGSuV9BA+MDfPyRBE*_G=0MHn2VH2TeDJ5i57AXhJ zni^CKXP1=tLX?m_?3JaDt`1G>&3RwV0W;u61HEG4bxkkuxvkqu>E**mRx!|;#2moG ziP$FHoU26xfeukJRo+}W`(T^_8SUEkG74xK4>~)C>I=N=5Xy1gavBlk=&A#Kf#yaj z$);U&5SsI<8dTD-rgI>&L=}D)HI@bRbB$Kz&7+p*i>bnf$=0WY8@pAeIy#` znLCgw`o`hZrfS~I+Ex8SVsoYvN)4akKo+{wC@06i4|vjeb_sCJibfG_VWdRb1PfhK zT;EXY=Sa)?r58$d2hwS1Z7tw^^-YXo2fbR>ssVrm;{cUelQa zjvlW=F1WXB+*OE=I<_kU&ZC=bXAyqBGs(fa11bfDNrJAHXHySze8 z6$O006LaJMp5R}k4W1oMAPBsj)AWAx#?M&<6kNNZw4A|&tm-Ta3Dmnf1JoR1Lly1e_2s) j>ar{oz*-0F{|YbwKA#E$9zpcP00000NkvXXu0mjf*nPMD literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/48-disabled.png b/shells/browser/shared/icons/48-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..372b6e00e88a85c08ccfa020e8db75b31b429a5b GIT binary patch literal 1564 zcmV+%2IKjOP)X|Jd>e?ZEK3(Fq>>IDf=r&l24$Pz0Ohy##{rBE=7 zFpKTHpX(RBOlH2t#%bM=MtsaS^S-yg-y6vD{4?@t`e_z>a$`hkMrg+JQp|r%)9cBt z5vTbVnE#4qfhYF{l<)KAklW}+vDcI1!?gf?eSPJ-ySqQ#+}ylLBocpKU0wZgczF0? zW@e_T9j5PLget!NLm^wK*nc{WQYJ@cWgFHu8djR2h4}<_Mvf8(p@;rXe zngFbQFt5IY<|UJ0ir1ypbZ0RyFE3xGX$9w-nF(mriIu=OEW(zt(k?8CeGtSAPmY6_ zgY^3PIy^o;ernp+*w{F+u(0r5JRUzSEG%sGdcEa1)8F6!sJpv6Y&y5NxR`wB^ZD*) zB@0znR+cBrA2DXPcXf63y}D*4G{(Tdz)nhlKvvVJlQbI)1}CBU+uPeDQ?LZZ#E2t& z;6OoX3DDWuxs)t@#l;;~`$lMhVK6c`H}`yFV`C8Oe!stm;v;A}M;WbyT{Pq4Gk}tQFWA+mHE>JME9I-rVQ~LEU>b&^1Qvh zJ%qrfpbga4*0vTD6qHw0RR!4%ph*tmXf(RSmczT_Epl6ln)RF}bISj~H_EGPwL zPxwp#X<9p9R%1hIPHM~w?@P^FDU2u1vgNe`NP>2LECW|HYr;C4^O#VE^l@3Z zq`+#1!M9{br^!J2LM9KgAXt;yU<W6s zO3%*DLex)8a}#xEaGEhY&>}bOcLAf(J3s-T=N3-;+tt7+YaQGKAG<;iuy3o|r z^th&`Cdifm%>LvSHU*o(EVR#GMryAqW6%heDx2-SrA-K1>39Rv7Qpg>Vs@Jd~IBjcE`1Vv3g^@yrcC&N%J^1-?|Q68MMcFR_A&Dp7Z)Kr z=D*nA2b^Rc-2cn|nbPNDijSxNx&Vwct^SyS7+{~L#(Wyt2$n!_TU~6ifNn3gT-{D{ zvm!e%iPyQxs#RIVS2|emMXuK+c!LlCS19$e3p0T(tVvjcGk_zQQgE9ah|C1A!~1w= z1?ce3>bxE9Nec^^f;12ae8e;d2M3|q+1Xc4E}{6ctgEZ*3Bi&MO38jgn>$XiPeyg% zfAMG5|DpiV9Ogd?0q|cR@f>6xJV)@)9=K<@Od(TWu4ta--!x+%1Q-Bvr`E+MSw)!u O0000dJ!R1d+^W%y?8J|FG5IB57H743Q}6|5TzyHMQHZ+uhmOv|Z>AhFxdgy!n2A@B91nX00@VT@EbxxECDP zO?lX2a18$(*bB$te+Z70DKC2x98DF+o-a4><{0xmR^V?_##Uj<++q``uC6xY+|#TW@KdK`4Q;v@3#Q3$;n9z7;A5Dw_b&yu1!o#oFLA9 zjKTKy_RPY*?vqo-a9prKKh7{V+gRS^~1Xyqxu>yK3A!Jw0s>4-Ze= zkEFOa?kdD0fL^i2#>P_sTzx|XLrTa6a*Q7s81Q-*@Q~o1&d$!#2eCZJ^83I`#{ggq z_W)j?a!EtieDRf-fbCzPY!zw@XWa zhpSQwlnahkQMv>K((z>!R!YH)=e3INoN=mfP6B{J25yta1Ep1p68dHw6HX-%p$2!W^orH}Wu!Ca@m z4;MUuvx15b#z0W30ZFbGUC5YB;PF-zcmWAtQ=XR-qN=r4MdlL5W@cuRRbsa|<}PH+{}?hG>Ni<&!`JBf;gONedb;xQHVgX z@;O03*+E-JU*p9?l{Ey&C9G@{6+6mQZag5@xVhgeAtsX+0p->LcjqMV3Pi@}ys{1R zNauWaGY}3^<#rPQcJ<16Q(yODvMnWGs0Y$I@*+o5Xy}2czIYSDsv8i}PAs49Nug1j z|95X+G`>d;5Me-+q@V-CGG)F4`&Z3xUw>*G;2}}6 z{eaRQ!Rx+7V3i1DRw=42=A64>2?y@o_nysrnEA_m{@_?^t zhrpNaqu!gM{alv5c-GwNeL1;=`stgzg+y?O6(u` zeE^vd=mHOLH)w5KoIew2|57MRRUH?ybEn4M>U|~I3aH!{l6mmH+o(motOXFj!g6^q z^IGJbHv<`c@zrmqid#io0(do72a5K!arc1-_{|7i?>|1WY4g+jzeL_crA{Ov>EGR$ zxG_TSq)id2()XI2%o6s#C9wae&HWpm;zgkg_erq|nfvOTj6;9KYVZSxf?TQ6w?U_6 zc=+|3-e*97SnvgRxVXjsmD1hxN4;A2wbM}zTe0@~8{ZdKiIrh!;JUiHP82OXUPxb0 zRlR!UrJ})yQ|bS|6aNLP!L!9w zhV%u`lOuu5n^FEm@MKJ7$$0Rz%+F*p`-3-YP36f#l7|RfH5DvXro!dA)Dvp!%<|10 zrhdZ%rn>%SGchu3E)E?x=RQ4R-zO2~E@|Cp7B#K2>s>iF8qDuEKOO2#Z9wW2TC?{9 zQ`^uO`Q3%VV`k*Nm#jijvZVEX)9}bHYj~8LJ$lg2FPj0&H*d2FAZHH0V}3t5Xg!EG z@hbB7#3ic)9(AL3d)FRo0D^&Mj~=j*n_jmgod5OihZ08;0fnARI{3Cnd(48&QmZ`S zMv*^9N37>jxVk=Tqk};Umu2nec>AuWd(GnJb)n(IuXLL~PJLsh#?RY_af_Qcwlsb4XUoZy66eT=bv$D}DMFfb<&&W;9GM}QHUs@01wcIr^(schSj!4LCaDpnG#&%*h4pb%f{5uPXADnNO7S@n z=4|k-Rn!2KC8u@7yIw*lP^A(42ub2SQV3_T7H0#m?6YF)ju3&|dJxI;8B*orJSZjQ zSp|S5?i}2QH{JQfUYmY3*<0;c3Ky<(b?S=AtX*&F zR;{rqs5O9G#zgTe#*1HhQ%IrIVlJa};jZ=F&kATXUX$`o&+IFJMK@)TyWbkHPEa&3 z5GGoT1uJz6Qiw%vYTFj(a+LB?B>y{?!;=8)A2AkufCSQd2kG$DVI5ok83@s>9S?3g6iBBTQ~gQ8zP)@I-1q^J~P z3@Pb@^h9J%IZv2<4Y$jYFv8b%;(cGPyK4aiW1k#JM6FZNX$p_x#xrW#=YmF)`@ksc zrKQs)U8^X4^ckB|C!FMpM85X~wbP{UKnM;32pm(}uqz~qWif!$+J!#%JqpudsX+N# zcb#hJg(OkeE2Unmq%uI>^>K5eLjTJ-!t>YI&p8-Rnc`i z#x3qRG)a4%Qu3`tu@+BElW|gBx7OFh4;)F5C!gn#7I7@~e=|T~jxz#Yq@pEdC}!Q( z#a(R4z%*CusSOY`x{Zk&8BSSsbmTm##6_=ipPCRcE9|;D%6~6JDxiS!nkIuY0CFb} z3M%t^SH9Opm*!s_8l3rVV~9S`izIbck^%1JR&>|YZ;RE3D5BB6`My4UP7P3zx(x`8 zidbyYPK=1acy)`Nl>5wy0oRgL{O`mV&{x6w_f`BqdsOi+E4-<&NT6#Ay8kD@0DMx% U2JL{>NdN!<07*qoM6N<$f|%kP{{R30 literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/48-unminified.png b/shells/browser/shared/icons/48-unminified.png new file mode 100644 index 0000000000000000000000000000000000000000..f3224021a560c7d543178c3159c05cee9eebac9f GIT binary patch literal 2001 zcmV;?2QK)DP)So#v3TzJq(Wsh2i& zG*EU}yE;NUI=Ua^#KHHdqnNVGCe=}=#Ur0>;3YW+duL9bgXdyAqwo>N50zKe81@MAWi+;L1Q<+bnF0{c*V&D zTR-`lJXO`EYcpM)H2TG524rS|`h8UP&KY*x%qDJqN26CSXD$OJhu_e_kA3qMh40=W zEfC;na#ZVMQ5{hrkP&0nDnl*24ugY}3M?`w_r>*ib&^S#>%HpDAEkS8#p!0sEeSGs zq&qq?NRh{X(M)$Imq1?mMs^TD5oLUi*UjfIvt#1;-xr%{zVA0$ni%H;=V2Y^u0$g= z_e*zXG9b~PX#X~84N(O!M6v9|8IGu@YCD5M29{@M=+W6zmeED|T&K#RwhLU>phYkb49)Gc2mt6fpxWMm!iyGC0vSUYNiY^i22FbkYZz{r)>g74r2_yPJbH|q&ln}C ze>A9+5%duZItFvV&X76u3GRj2fLHVmzDu%T>@n#fD@Qad%>z&Z9ybBtKVCn;pkvY8 z&adV1h@y^DftTi%Kmd4*f$H=ji0{C6iIxHeV6kGbBrOEKE`9AS%5l3H?2`{e>Si6R zc{pWmPfY8mi5XF#1;kM62l@b;U0R_lgHv>Ce2#8UhUxBXglhdBa%(xPbygw~^6%M8 z`8#*16713MF{~&T&Y9^eoG<#y(gEB^JOzLMbN#cV!Gcu6L06za&k!6P|KWxX{_Xxz z>I+9{IJ(5g!2BY0OoZuBiI1Itki)gOho&Yxg0(m@MC0GzNNViq&fEM72axrJKl}Ay zu3{l0JB%jC;l!I_&Da~*1BiV-G^LzaPPz`CVeS3riu7nNDOn;K$YUeoP+GYv`YaR` z14H%+@N#p`MGp@rKV}p&Th+CO)4U_$0-|y6PZ&2gsyAXK7EO7s)%R$|h)BI-&uhP@J-BK}8RbEFp3Cw&Sv!4yj6Qzyl)9!C`0rZ3 zJEa4`qsR5RW0?WjRD+YPXbahq`j^rPHXhU0PgWL^-<6zLe1v3F7M^K!IIq!~L z>^yiLR9Ro>W#BmQzH@4xS{_cAGSuV9BA+MDfPyRBE*_G=0MHn2VH2TeDJ5i57AXhJ zni^CKXP1=tLX?m_?3JaDt`1G>&3RwV0W;u61HEG4bxkkuxvkqu>E**mRx!|;#2moG ziP$FHoU26xfeukJRo+}W`(T^_8SUEkG74xK4>~)C>I=N=5Xy1gavBlk=&A#Kf#yaj z$);U&5SsI<8dTD-rgI>&L=}D)HI@bRbB$Kz&7+p*i>bnf$=0WY8@pAeIy#` znLCgw`o`hZrfS~I+Ex8SVsoYvN)4akKo+{wC@06i4|vjeb_sCJibfG_VWdRb1PfhK zT;EXY=Sa)?r58$d2hwS1Z7tw^^-YXo2fbR>ssVrm;{cUelQa zjvlW=F1WXB+*OE=I<_kU&ZC=bXAyqBGs(fa11bfDNrJAHXHySze8 z6$O006LaJMp5R}k4W1oMAPBsj)AWAx#?M&<6kNNZw4A|&tm-Ta3Dmnf1JoR1Lly1e_2s) j>ar{oz*-0F{|YbwKA#E$9zpcP00000NkvXXu0mjf*nPMD literal 0 HcmV?d00001 diff --git a/shells/browser/shared/icons/deadcode.svg b/shells/browser/shared/icons/deadcode.svg new file mode 100644 index 0000000000..ccd6e66906 --- /dev/null +++ b/shells/browser/shared/icons/deadcode.svg @@ -0,0 +1 @@ +development780780 \ No newline at end of file diff --git a/shells/browser/shared/icons/development.svg b/shells/browser/shared/icons/development.svg new file mode 100644 index 0000000000..ccd6e66906 --- /dev/null +++ b/shells/browser/shared/icons/development.svg @@ -0,0 +1 @@ +development780780 \ No newline at end of file diff --git a/shells/browser/shared/icons/disabled.svg b/shells/browser/shared/icons/disabled.svg new file mode 100644 index 0000000000..73c2bb51cd --- /dev/null +++ b/shells/browser/shared/icons/disabled.svg @@ -0,0 +1 @@ +disabled \ No newline at end of file diff --git a/shells/browser/shared/icons/outdated.svg b/shells/browser/shared/icons/outdated.svg new file mode 100644 index 0000000000..03b83c1eb5 --- /dev/null +++ b/shells/browser/shared/icons/outdated.svg @@ -0,0 +1 @@ +outdated \ No newline at end of file diff --git a/shells/browser/shared/icons/production.svg b/shells/browser/shared/icons/production.svg new file mode 100644 index 0000000000..1e974f5131 --- /dev/null +++ b/shells/browser/shared/icons/production.svg @@ -0,0 +1 @@ +production \ No newline at end of file diff --git a/shells/browser/shared/main.html b/shells/browser/shared/main.html new file mode 100644 index 0000000000..f1c96d4a7a --- /dev/null +++ b/shells/browser/shared/main.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/shells/browser/shared/panel.html b/shells/browser/shared/panel.html new file mode 100644 index 0000000000..60fd1bdf13 --- /dev/null +++ b/shells/browser/shared/panel.html @@ -0,0 +1,32 @@ + + + + + + + + +
Unable to find React on the page.
+ + + diff --git a/shells/browser/shared/popups/deadcode.html b/shells/browser/shared/popups/deadcode.html new file mode 100644 index 0000000000..5e74dc06c4 --- /dev/null +++ b/shells/browser/shared/popups/deadcode.html @@ -0,0 +1,32 @@ + + +

+ This page includes an extra development build of React. 🚧 +

+

+ The React build on this page includes both development and production versions because dead code elimination has not been applied correctly. +
+
+ This makes its size larger, and causes React to run slower. +
+
+ Make sure to set up dead code elimination before deployment. +

+
+

+ Open the developer tools, and the React tab will appear to the right. +

diff --git a/shells/browser/shared/popups/development.html b/shells/browser/shared/popups/development.html new file mode 100644 index 0000000000..9c2089cc2f --- /dev/null +++ b/shells/browser/shared/popups/development.html @@ -0,0 +1,28 @@ + + +

+ This page is using the development build of React. 🚧 +

+

+ Note that the development build is not suitable for production. +
+ Make sure to use the production build before deployment. +

+
+

+ Open the developer tools, and the React tab will appear to the right. +

diff --git a/shells/browser/shared/popups/disabled.html b/shells/browser/shared/popups/disabled.html new file mode 100644 index 0000000000..a89b178d49 --- /dev/null +++ b/shells/browser/shared/popups/disabled.html @@ -0,0 +1,21 @@ + + +

+ This page doesn’t appear to be using React. +
+ If this seems wrong, follow the troubleshooting instructions. +

diff --git a/shells/browser/shared/popups/outdated.html b/shells/browser/shared/popups/outdated.html new file mode 100644 index 0000000000..a6ec12bcaf --- /dev/null +++ b/shells/browser/shared/popups/outdated.html @@ -0,0 +1,29 @@ + + +

+ This page is using an outdated version of React. ⌛ +

+

+ We recommend updating React to ensure that you receive important bugfixes and performance improvements. +
+
+ You can find the upgrade instructions on the React blog. +

+
+

+ Open the developer tools, and the React tab will appear to the right. +

diff --git a/shells/browser/shared/popups/production.html b/shells/browser/shared/popups/production.html new file mode 100644 index 0000000000..1b65eb5b21 --- /dev/null +++ b/shells/browser/shared/popups/production.html @@ -0,0 +1,21 @@ + + +

+ This page is using the production build of React. ✅ +
+ Open the developer tools, and the React tab will appear to the right. +

diff --git a/shells/browser/shared/popups/shared.js b/shells/browser/shared/popups/shared.js new file mode 100644 index 0000000000..130f1f457e --- /dev/null +++ b/shells/browser/shared/popups/shared.js @@ -0,0 +1,22 @@ +/* globals chrome */ + +document.addEventListener('DOMContentLoaded', function() { + // Make links work + const links = document.getElementsByTagName('a'); + for (let i = 0; i < links.length; i++) { + (function() { + const ln = links[i]; + const location = ln.href; + ln.onclick = function() { + chrome.tabs.create({ active: true, url: location }); + }; + })(); + } + + // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=428044 + document.body.style.opacity = 0; + document.body.style.transition = 'opacity ease-out .4s'; + requestAnimationFrame(function() { + document.body.style.opacity = 1; + }); +}); diff --git a/shells/browser/shared/popups/unminified.html b/shells/browser/shared/popups/unminified.html new file mode 100644 index 0000000000..553c9ac6ac --- /dev/null +++ b/shells/browser/shared/popups/unminified.html @@ -0,0 +1,31 @@ + + +

+ This page is using an unminified build of React. 🚧 +

+

+ The React build on this page appears to be unminified. +
+ This makes its size larger, and causes React to run slower. +
+
+ Make sure to set up minification before deployment. +

+
+

+ Open the developer tools, and the React tab will appear to the right. +

diff --git a/shells/browser/shared/src/GlobalHook.js b/shells/browser/shared/src/GlobalHook.js new file mode 100644 index 0000000000..9ccab3bb8a --- /dev/null +++ b/shells/browser/shared/src/GlobalHook.js @@ -0,0 +1,64 @@ +/* global chrome */ + +import nullthrows from 'nullthrows'; +import { installHook } from 'src/hook'; + +let lastDetectionResult; + +// We want to detect when a renderer attaches, and notify the "background +// page" (which is shared between tabs and can highlight the React icon). +// Currently we are in "content script" context, so we can't listen +// to the hook directly (it will be injected directly into the page). +// So instead, the hook will use postMessage() to pass message to us here. +// And when this happens, we'll send a message to the "background page". +window.addEventListener('message', function(evt) { + if ( + evt.source === window && + evt.data && + evt.data.source === 'react-devtools-detector' + ) { + lastDetectionResult = { + hasDetectedReact: true, + reactBuildType: evt.data.reactBuildType, + }; + chrome.runtime.sendMessage(lastDetectionResult); + } +}); + +// NOTE: Firefox WebExtensions content scripts are still alive and not re-injected +// while navigating the history to a document that has not been destroyed yet, +// replay the last detection result if the content script is active and the +// document has been hidden and shown again. +window.addEventListener('pageshow', function(evt) { + if (!lastDetectionResult || evt.target !== window.document) { + return; + } + chrome.runtime.sendMessage(lastDetectionResult); +}); + +const detectReact = ` +window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('renderer', function(evt) { + window.postMessage({ + source: 'react-devtools-detector', + reactBuildType: evt.reactBuildType, + }, '*'); +}); +`; +const saveNativeValues = ` +window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeObjectCreate = Object.create; +window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeMap = Map; +window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeWeakMap = WeakMap; +window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeSet = Set; +`; + +// Inject a `__REACT_DEVTOOLS_GLOBAL_HOOK__` global so that React can detect that the +// devtools are installed (and skip its suggestion to install the devtools). +const js = + ';(' + installHook.toString() + '(window))' + saveNativeValues + detectReact; + +// This script runs before the element is created, so we add the script +// to instead. +const script = document.createElement('script'); +script.textContent = js; +nullthrows(document.documentElement).appendChild(script); +nullthrows(script.parentNode).removeChild(script); diff --git a/shells/browser/shared/src/backend.js b/shells/browser/shared/src/backend.js new file mode 100644 index 0000000000..fa13e02527 --- /dev/null +++ b/shells/browser/shared/src/backend.js @@ -0,0 +1,67 @@ +// Do not use imports or top-level requires here! +// Running module factories is intentionally delayed until we know the hook exists. +// This is to avoid issues like: https://github.com/facebook/react-devtools/issues/1039 + +/** @flow */ + +function welcome(event) { + if ( + event.source !== window || + event.data.source !== 'react-devtools-content-script' + ) { + return; + } + + window.removeEventListener('message', welcome); + + setup(window.__REACT_DEVTOOLS_GLOBAL_HOOK__); +} + +window.addEventListener('message', welcome); + +function setup(hook) { + const Agent = require('src/backend/agent').default; + const Bridge = require('src/bridge').default; + const { initBackend } = require('src/backend'); + + const listeners = []; + + const bridge = new Bridge({ + listen(fn) { + const listener = event => { + if ( + event.source !== window || + !event.data || + event.data.source !== 'react-devtools-content-script' || + !event.data.payload + ) { + return; + } + fn(event.data.payload); + }; + listeners.push(listener); + window.addEventListener('message', listener); + }, + send(data) { + window.postMessage( + { + source: 'react-devtools-bridge', + payload: data, + }, + '*' + ); + }, + }); + + const agent = new Agent(); + agent.addBridge(bridge); + agent.addListener('shutdown', () => { + hook.emit('shutdown'); + listeners.forEach(fn => { + window.removeEventListener('message', fn); + }); + listeners.splice(0); + }); + + initBackend(window.__REACT_DEVTOOLS_GLOBAL_HOOK__, agent); +} diff --git a/shells/browser/shared/src/background.js b/shells/browser/shared/src/background.js new file mode 100644 index 0000000000..ca22f3950c --- /dev/null +++ b/shells/browser/shared/src/background.js @@ -0,0 +1,111 @@ +/* global chrome */ + +const ports = {}; + +const IS_FIREFOX = navigator.userAgent.indexOf('Firefox') >= 0; + +chrome.runtime.onConnect.addListener(function(port) { + let tab = null; + let name = null; + if (isNumeric(port.name)) { + tab = port.name; + name = 'devtools'; + installContentScript(+port.name); + } else { + tab = port.sender.tab.id; + name = 'content-script'; + } + + if (!ports[tab]) { + ports[tab] = { + devtools: null, + 'content-script': null, + }; + } + ports[tab][name] = port; + + if (ports[tab].devtools && ports[tab]['content-script']) { + doublePipe(ports[tab].devtools, ports[tab]['content-script']); + } +}); + +function isNumeric(str: string): boolean { + return +str + '' === str; +} + +function installContentScript(tabId: number) { + chrome.tabs.executeScript( + tabId, + { file: '/build/contentScript.js' }, + function() {} + ); +} + +function doublePipe(one, two) { + one.onMessage.addListener(lOne); + function lOne(message) { + two.postMessage(message); + } + two.onMessage.addListener(lTwo); + function lTwo(message) { + one.postMessage(message); + } + function shutdown() { + one.onMessage.removeListener(lOne); + two.onMessage.removeListener(lTwo); + one.disconnect(); + two.disconnect(); + } + one.onDisconnect.addListener(shutdown); + two.onDisconnect.addListener(shutdown); +} + +function setIconAndPopup(reactBuildType, tabId) { + chrome.browserAction.setIcon({ + tabId: tabId, + path: { + '16': 'icons/16-' + reactBuildType + '.png', + '32': 'icons/32-' + reactBuildType + '.png', + '48': 'icons/48-' + reactBuildType + '.png', + '128': 'icons/128-' + reactBuildType + '.png', + }, + }); + chrome.browserAction.setPopup({ + tabId: tabId, + popup: 'popups/' + reactBuildType + '.html', + }); +} + +// Listen to URL changes on the active tab and reset the DevTools icon. +// This prevents non-disabled icons from sticking in Firefox. +// Don't listen to this event in Chrome though. +// It fires more frequently, often after onMessage() has been called. +if (IS_FIREFOX) { + chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { + if (tab.active && changeInfo.status === 'loading') { + setIconAndPopup('disabled', tabId); + } + }); +} + +chrome.runtime.onMessage.addListener((req, sender) => { + // This is sent from the hook content script. + // It tells us a renderer has attached. + if (req.hasDetectedReact && sender.tab) { + // We use browserAction instead of pageAction because this lets us + // display a custom default popup when React is *not* detected. + // It is specified in the manifest. + let reactBuildType = req.reactBuildType; + if (sender.url.indexOf('facebook.github.io/react') !== -1) { + // Cheat: We use the development version on the website because + // it is better for interactive examples. However we're going + // to get misguided bug reports if the extension highlights it + // as using the dev version. We're just going to special case + // our own documentation and cheat. It is acceptable to use dev + // version of React in React docs, but not in any other case. + reactBuildType = 'production'; + } + + setIconAndPopup(reactBuildType, sender.tab.id); + } +}); diff --git a/shells/browser/shared/src/checkForReact.js b/shells/browser/shared/src/checkForReact.js new file mode 100644 index 0000000000..b0334ff3ae --- /dev/null +++ b/shells/browser/shared/src/checkForReact.js @@ -0,0 +1,12 @@ +/* global chrome */ + +export default function checkForReact(done: (pageHasReact: boolean) => void) { + chrome.devtools.inspectedWindow.eval( + `!!( + (window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && Object.keys(window.__REACT_DEVTOOLS_GLOBAL_HOOK__._renderers).length) || window.React || (window.require && (require('react') || require('React'))) + )`, + function(pageHasReact, err) { + done(pageHasReact); + } + ); +} diff --git a/shells/browser/shared/src/contentScript.js b/shells/browser/shared/src/contentScript.js new file mode 100644 index 0000000000..c72d17918f --- /dev/null +++ b/shells/browser/shared/src/contentScript.js @@ -0,0 +1,52 @@ +/* global chrome */ + +// proxy from main page to devtools (via the background page) +var port = chrome.runtime.connect({ + name: 'content-script', +}); + +port.onMessage.addListener(handleMessageFromDevtools); +port.onDisconnect.addListener(handleDisconnect); +window.addEventListener('message', handleMessageFromPage); + +window.postMessage( + { + source: 'react-devtools-content-script', + hello: true, + }, + '*' +); + +function handleMessageFromDevtools(message) { + window.postMessage( + { + source: 'react-devtools-content-script', + payload: message, + }, + '*' + ); +} + +function handleMessageFromPage(evt) { + if ( + evt.source === window && + evt.data && + evt.data.source === 'react-devtools-bridge' + ) { + port.postMessage(evt.data.payload); + } +} + +function handleDisconnect() { + window.removeEventListener('message', handleMessageFromPage); + window.postMessage( + { + source: 'react-devtools-content-script', + payload: { + type: 'event', + evt: 'shutdown', + }, + }, + '*' + ); +} diff --git a/shells/browser/shared/src/inject.js b/shells/browser/shared/src/inject.js new file mode 100644 index 0000000000..522b322786 --- /dev/null +++ b/shells/browser/shared/src/inject.js @@ -0,0 +1,21 @@ +/* global chrome */ + +export default function inject(scriptName: string, done: () => void) { + const src = ` + // the prototype stuff is in case document.createElement has been modified + (function () { + var script = document.constructor.prototype.createElement.call(document, 'script'); + script.src = "${scriptName}"; + script.charset = "utf-8"; + document.documentElement.appendChild(script); + script.parentNode.removeChild(script); + })() + `; + + chrome.devtools.inspectedWindow.eval(src, function(res, err) { + if (err) { + console.log(err); + } + done(); + }); +} diff --git a/shells/browser/shared/src/main.js b/shells/browser/shared/src/main.js new file mode 100644 index 0000000000..ab511445cd --- /dev/null +++ b/shells/browser/shared/src/main.js @@ -0,0 +1,40 @@ +/* global chrome */ + +let panelCreated = false; + +function createPanelIfReactLoaded() { + if (panelCreated) { + return; + } + chrome.devtools.inspectedWindow.eval( + 'window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && Object.keys(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers).length > 0', + function(pageHasReact, err) { + if (!pageHasReact || panelCreated) { + return; + } + + clearInterval(loadCheckInterval); + panelCreated = true; + chrome.devtools.panels.create('React', '', 'panel.html', function(panel) { + panel.onShown.addListener(function(window) { + // TODO: When the user switches to the panel, check for an Elements tab selection. + }); + panel.onHidden.addListener(function() { + // TODO: Stop highlighting and stuff. + }); + }); + } + ); +} + +chrome.devtools.network.onNavigated.addListener(function() { + createPanelIfReactLoaded(); +}); + +// Check to see if React has loaded once per second in case React is added +// after page load +const loadCheckInterval = setInterval(function() { + createPanelIfReactLoaded(); +}, 1000); + +createPanelIfReactLoaded(); diff --git a/shells/browser/shared/src/panel.js b/shells/browser/shared/src/panel.js new file mode 100644 index 0000000000..e93f1c66f7 --- /dev/null +++ b/shells/browser/shared/src/panel.js @@ -0,0 +1,62 @@ +/* global chrome */ + +import { createElement } from 'react'; +import { render } from 'react-dom'; +import Bridge from 'src/bridge'; +import App from 'src/devtools/views/App'; +import inject from './inject'; + +const IS_CHROME = navigator.userAgent.indexOf('Firefox') < 0; + +let browserName; +let themeName; + +if (IS_CHROME) { + browserName = 'Chrome'; + + // chrome.devtools.panels added in Chrome 18. + // chrome.devtools.panels.themeName added in Chrome 54. + themeName = chrome.devtools.panels.themeName === 'dark' ? 'Dark' : 'Default'; +} else { + browserName = 'Firefox'; + + // chrome.devtools.panels.themeName added in Firefox 55. + // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/devtools.panels/themeName + if (chrome.devtools && chrome.devtools.panels) { + switch (chrome.devtools.panels.themeName) { + case 'dark': + themeName = 'Dark'; + break; + default: + themeName = 'Light'; + break; + } + } +} + +const node = ((document.getElementById('container'): any): HTMLElement); + +inject(chrome.runtime.getURL('build/backend.js'), () => { + let disconnected = false; + + const port = chrome.runtime.connect({ + name: '' + chrome.devtools.inspectedWindow.tabId, + }); + port.onDisconnect.addListener(() => { + disconnected = true; + }); + + const bridge = new Bridge({ + listen(fn) { + port.onMessage.addListener(message => fn(message)); + }, + send(data) { + if (disconnected) { + return; + } + port.postMessage(data); + }, + }); + + render(createElement(App, { bridge, browserName, themeName }), node); +}); diff --git a/shells/browser/webpack.backend.js b/shells/browser/shared/webpack.backend.js similarity index 61% rename from shells/browser/webpack.backend.js rename to shells/browser/shared/webpack.backend.js index 9c1aa96809..59e0b617bc 100644 --- a/shells/browser/webpack.backend.js +++ b/shells/browser/shared/webpack.backend.js @@ -1,7 +1,8 @@ const { readFileSync } = require('fs'); const { resolve } = require('path'); -const __DEV__ = process.env.NODE_ENV !== 'production'; +//const __DEV__ = process.env.NODE_ENV !== 'production'; +const __DEV__ = true; // TODO module.exports = { mode: __DEV__ ? 'development' : 'production', @@ -13,12 +14,19 @@ module.exports = { path: __dirname + '/build', filename: '[name].js', }, + resolve: { + alias: { + src: resolve(__dirname, '../../../src'), + }, + }, module: { rules: [ { test: /\.js$/, loader: 'babel-loader', - options: JSON.parse(readFileSync(resolve(__dirname, '../../.babelrc'))), + options: JSON.parse( + readFileSync(resolve(__dirname, '../../../.babelrc')) + ), }, ], }, diff --git a/shells/browser/webpack.config.js b/shells/browser/shared/webpack.config.js similarity index 80% rename from shells/browser/webpack.config.js rename to shells/browser/shared/webpack.config.js index 39c08ff056..933d7a79ce 100644 --- a/shells/browser/webpack.config.js +++ b/shells/browser/shared/webpack.config.js @@ -2,7 +2,8 @@ const { readFileSync } = require('fs'); const { resolve } = require('path'); const webpack = require('webpack'); -const __DEV__ = process.env.NODE_ENV !== 'production'; +//const __DEV__ = process.env.NODE_ENV !== 'production'; +const __DEV__ = true; // TODO module.exports = { mode: __DEV__ ? 'development' : 'production', @@ -18,6 +19,11 @@ module.exports = { path: __dirname + '/build', filename: '[name].js', }, + resolve: { + alias: { + src: resolve(__dirname, '../../../src'), + }, + }, plugins: __DEV__ ? [] : [ @@ -31,7 +37,9 @@ module.exports = { { test: /\.js$/, loader: 'babel-loader', - options: JSON.parse(readFileSync(resolve(__dirname, '../../.babelrc'))), + options: JSON.parse( + readFileSync(resolve(__dirname, '../../../.babelrc')) + ), }, { test: /\.css$/, diff --git a/shells/createConfig.js b/shells/createConfig.js deleted file mode 100644 index a5eea836b7..0000000000 --- a/shells/createConfig.js +++ /dev/null @@ -1,148 +0,0 @@ -const path = require('path'); -const webpack = require('webpack'); -const merge = require('webpack-merge'); -const { VueLoaderPlugin } = require('vue-loader'); -const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin'); - -module.exports = (config, target = { chrome: 52, firefox: 48 }) => { - const bubleOptions = { - target, - objectAssign: 'Object.assign', - transforms: { - forOf: false, - modules: false, - }, - }; - - const baseConfig = { - mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', - resolve: { - alias: { - src: path.resolve(__dirname, '../src'), - views: path.resolve(__dirname, '../src/devtools/views'), - components: path.resolve(__dirname, '../src/devtools/components'), - filters: path.resolve(__dirname, '../src/devtools/filters'), - }, - }, - module: { - rules: [ - { - test: /\.js$/, - loader: 'buble-loader', - exclude: /node_modules|vue\/dist|vuex\/dist/, - options: bubleOptions, - }, - { - test: /\.vue$/, - loader: 'vue-loader', - options: { - compilerOptions: { - preserveWhitespace: false, - }, - transpileOptions: bubleOptions, - }, - }, - { - test: /\.css$/, - use: ['vue-style-loader', 'css-loader', 'postcss-loader'], - }, - { - test: /\.styl(us)?$/, - use: [ - 'vue-style-loader', - 'css-loader', - 'postcss-loader', - 'stylus-loader', - { - loader: 'style-resources-loader', - options: { - patterns: [ - path.resolve(__dirname, '../src/devtools/style/imports.styl'), - ], - }, - }, - ], - }, - { - test: /\.(png|woff2)$/, - loader: 'url-loader?limit=0', - }, - ], - }, - performance: { - hints: false, - }, - plugins: [ - new VueLoaderPlugin(), - ...(process.env.VUE_DEVTOOL_TEST ? [] : [new FriendlyErrorsPlugin()]), - new webpack.DefinePlugin({ - 'process.env.RELEASE_CHANNEL': JSON.stringify( - process.env.RELEASE_CHANNEL || 'stable' - ), - }), - ], - devServer: { - port: process.env.PORT, - }, - stats: { - colors: true, - }, - }; - - if (process.env.NODE_ENV === 'production') { - const UglifyPlugin = require('uglifyjs-webpack-plugin'); - baseConfig.plugins.push( - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': '"production"', - }) - ); - baseConfig.optimization = { - minimizer: [ - new UglifyPlugin({ - exclude: /backend/, - uglifyOptions: { - compress: { - // turn off flags with small gains to speed up minification - arrows: false, - collapse_vars: false, // 0.3kb - comparisons: false, - computed_props: false, - hoist_funs: false, - hoist_props: false, - hoist_vars: false, - inline: false, - loops: false, - negate_iife: false, - properties: false, - reduce_funcs: false, - reduce_vars: false, - switches: false, - toplevel: false, - typeofs: false, - - // a few flags with noticable gains/speed ratio - // numbers based on out of the box vendor bundle - booleans: true, // 0.7kb - if_return: true, // 0.4kb - sequences: true, // 0.7kb - unused: true, // 2.3kb - - // required features to drop conditional branches - conditionals: true, - dead_code: true, - evaluate: true, - }, - mangle: { - safari10: true, - }, - }, - sourceMap: false, - cache: true, - parallel: true, - }), - ], - }; - } - - return merge(baseConfig, config); -}; diff --git a/shells/dev/app/ElementTypes/index.js b/shells/dev/app/ElementTypes/index.js index 1a5ab821fb..ccfcb5fb33 100644 --- a/shells/dev/app/ElementTypes/index.js +++ b/shells/dev/app/ElementTypes/index.js @@ -15,7 +15,8 @@ import React, { } from 'react'; const Context = createContext('abc'); -Context.displayName = 'testContext'; +// $FlowFixMe Flow doesn't support this property yet. +Context.displayName = 'ExampleContext'; class ClassComponent extends Component { render() { diff --git a/shells/dev/src/devtools.js b/shells/dev/src/devtools.js index 948dbf565f..da162a517a 100644 --- a/shells/dev/src/devtools.js +++ b/shells/dev/src/devtools.js @@ -30,7 +30,11 @@ initDevTools({ cb(bridge); render( - createElement(App, { bridge }), + createElement(App, { + bridge, + browserName: 'Chrome', + themeName: 'light', + }), ((document.getElementById('devtools'): any): HTMLElement) ); }); diff --git a/src/backend/agent.js b/src/backend/agent.js index 25241716df..218743d2a1 100644 --- a/src/backend/agent.js +++ b/src/backend/agent.js @@ -15,7 +15,7 @@ import type { Bridge } from '../types'; import type { Element } from 'src/devtools/types'; const debug = (methodName, ...args) => { - //console.log(`%cAgent %c${methodName}`, 'color: blue; font-weight: bold;', 'font-weight: bold;', ...args); + // debug(`%cAgent %c${methodName}`, 'color: blue; font-weight: bold;', 'font-weight: bold;', ...args); }; const THROTTLE_BY_MS = 350; diff --git a/src/backend/renderer.js b/src/backend/renderer.js index bfc34a9eda..3b29dc1274 100644 --- a/src/backend/renderer.js +++ b/src/backend/renderer.js @@ -263,8 +263,8 @@ export function attach( const symbolOrNumber = typeof type === 'object' && type !== null ? type.$$typeof : type; - // $FlowFixMe facebook/flow/issues/2362 const switchValue = + // $FlowFixMe facebook/flow/issues/2362 typeof symbolOrNumber === 'symbol' ? symbolOrNumber.toString() : symbolOrNumber; diff --git a/src/devtools/store.js b/src/devtools/store.js index e3bcf4082e..14d75b0dc5 100644 --- a/src/devtools/store.js +++ b/src/devtools/store.js @@ -6,7 +6,7 @@ import type { Element } from './types'; import type { Bridge } from '../types'; const debug = (methodName, ...args) => { - //console.log(`%cAgent %c${methodName}`, 'color: red; font-weight: bold;', 'font-weight: bold;', ...args); + // console.log(`%cAgent %c${methodName}`, 'color: red; font-weight: bold;', 'font-weight: bold;', ...args); }; /** diff --git a/src/devtools/views/App.js b/src/devtools/views/App.js index 0615a60e34..24b5898fb0 100644 --- a/src/devtools/views/App.js +++ b/src/devtools/views/App.js @@ -8,11 +8,13 @@ import styles from './App.css'; import type { Bridge } from '../../types'; -type Props = {| +export type Props = {| bridge: Bridge, + browserName: string, + themeName: string, |}; -export default function App({ bridge }: Props) { +export default function App({ bridge, browserName, themeName }: Props) { const store = useMemo(() => new Store(bridge), []); return (