diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 5d3cf7a173..0000000000 --- a/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["react", "es2015", "stage-0"], -} diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 6acfe4c656..0000000000 --- a/.eslintignore +++ /dev/null @@ -1,6 +0,0 @@ -# node_modules ignored by default - -**/build/** -**/vendor/** -**/node_modules/** -flow/* diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index ed520fa977..0000000000 --- a/.eslintrc +++ /dev/null @@ -1,46 +0,0 @@ ---- -parser: babel-eslint - -extends: - - ./node_modules/fbjs-scripts/eslint/.eslintrc.js - -plugins: - - react - -globals: - React$Element: false - -rules: - brace-style: [2, 1tbs] - comma-dangle: [2, always-multiline] - consistent-return: 2 - dot-location: [2, property] - dot-notation: 2 - eol-last: 2 - indent: [2, 2, {SwitchCase: 1}] - jsx-quotes: 2 - keyword-spacing: 2 - max-len: 0 - no-multi-spaces: 2 - no-redeclare: 2 - no-shadow: 2 - no-unused-expressions: 2 - no-unused-vars: [2, {args: none}] - prefer-const: 2 - quotes: [2, single, avoid-escape] - space-before-blocks: 2 - space-before-function-paren: 2 - - # JSX - # Our transforms set this automatically - react/display-name: 0 - react/jsx-boolean-value: [2, always] - react/jsx-no-undef: 2 - react/jsx-sort-prop-types: 0 - react/jsx-sort-props: 0 - react/jsx-uses-react: 2 - react/jsx-uses-vars: 2 - react/no-unknown-property: 2 - react/react-in-jsx-scope: 2 - react/self-closing-comp: 2 - react/wrap-multilines: [2, {declaration: false, assignment: false}] diff --git a/.flowconfig b/.flowconfig deleted file mode 100644 index c0f52e6d66..0000000000 --- a/.flowconfig +++ /dev/null @@ -1,20 +0,0 @@ -[ignore] -.*/react/node_modules/.* -.*/electron/node_modules/.* -.*node_modules/babel.* -.*node_modules/classnames.* -.*node_modules/json-loader.* -.*node_modules/json5.* -.*node_modules/node-libs-browser.* -.*node_modules/webpack.* -.*node_modules/fbjs/flow.* - -[libs] -flow - -[options] -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue - -[version] -0.23.0 diff --git a/.github/ISSUE_TEMPLATE/issues-have-been-disabled.md b/.github/ISSUE_TEMPLATE/issues-have-been-disabled.md new file mode 100644 index 0000000000..196cf396ef --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issues-have-been-disabled.md @@ -0,0 +1,15 @@ +--- +name: Issues have been disabled +about: Please report new issues at github.com/facebook/react +title: NEW ISSUES ARE NOT MONITORED +labels: '' +assignees: '' + +--- + +This repository has been merged into the main React repo (github.com/facebook/react) + +Please open issues there: +https://github.com/facebook/react/issues/new + +New issues opened in this repository will be ignored. diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 1c37934e17..0000000000 --- a/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -/shells/chrome.crx -/shells/chrome.pem -/shells/firefox/*.xpi -build -node_modules -npm-debug.log -.DS_Store - diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b73ccd5cce..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -sudo: false -install: - - npm install -language: node_js -node_js: - - '4' -script: - - npm run lint - - flow check - - npm test -env: - - CXX=g++-4.8 -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-4.8 - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 04a721b89b..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,54 +0,0 @@ -# Contributing to React Devtools - -### Pull Requests - -The core team will be monitoring for pull requests. - -*Before* submitting a pull request, please make sure the following is done… - -1. Fork the repo and create your branch from `master`. -2. If you've added code that should be tested, add tests! -3. If you've changed APIs, update the documentation. -4. Make sure your code lints (`npm run lint`) - we've done our best to make sure these rules match our internal linting guidelines. -5. If you haven't already, complete the CLA. - -### Contributor License Agreement ("CLA") - -In order to accept your pull request, we need you to submit a CLA. You only need to do this once, so if you've done this for another Facebook open source project, you're good to go. If you are submitting a pull request for the first time, just let us know that you have completed the CLA and we can cross-check with your GitHub username. - -Complete your CLA here: - -## Bugs - -### Where to Find Known Issues - -We will be using GitHub Issues for our public bugs. We will keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new task, try to make sure your problem doesn't already exist. - -### Reporting New Issues - -The best way to get your bug fixed is to provide a reduced test case. jsFiddle, jsBin, and other sites provide a way to give live examples. Those are especially helpful though may not work for `JSX`-based code. - -### Security Bugs - -Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. With that in mind, please do not file public issues and go through the process outlined on that page. - -## How to Get in Touch - -* IRC - [#reactjs on freenode](http://webchat.freenode.net/?channels=reactjs) -* Mailing list - [reactjs on Google Groups](http://groups.google.com/group/reactjs) - -## Coding Style - -* Use semicolons; -* Commas last, -* 2 spaces for indentation (no tabs) -* Prefer `'` over `"` -* `"use strict";` -* 80 character line length -* "Attractive" - -Please `npm run lint`. - -## License - -By contributing to React, you agree that your contributions will be licensed under the [attached License](LICENSE). diff --git a/PATENTS b/PATENTS deleted file mode 100644 index e6b4bb420f..0000000000 --- a/PATENTS +++ /dev/null @@ -1,33 +0,0 @@ -Additional Grant of Patent Rights Version 2 - -“Software” means the react-devtools software distributed by Facebook, Inc. - -Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software -("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable -(subject to the termination provision below) license under any Necessary -Claims, to make, have made, use, sell, offer to sell, import, and otherwise -transfer the Software. For avoidance of doubt, no license is granted under -Facebook's rights in any patent claims that are infringed by (i) modifications -to the Software made by you or any third party or (ii) the Software in -combination with any software or other technology. - -The license granted hereunder will terminate, automatically and without notice, -if you (or any of your subsidiaries, corporate affiliates or agents) initiate -directly or indirectly, or take a direct financial interest in, any Patent -Assertion: (i) against Facebook or any of its subsidiaries or corporate -affiliates, (ii) against any party if such Patent Assertion arises in whole or -in part from any software, technology, product or service of Facebook or any of -its subsidiaries or corporate affiliates, or (iii) against any party relating -to the Software. Notwithstanding the foregoing, if Facebook or any of its -subsidiaries or corporate affiliates files a lawsuit alleging patent -infringement against you in the first instance, and you respond by filing a -patent infringement counterclaim in that lawsuit against that party that is -unrelated to the Software, the license granted hereunder will not terminate -under section (i) of this paragraph due to such counterclaim. - -A "Necessary Claim" is a claim of a patent owned by Facebook that is -necessarily infringed by the Software standing alone. - -A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, -or contributory infringement or inducement to infringe any patent, including a -cross-claim or counterclaim. diff --git a/README.md b/README.md index a2857fdb02..a9168c3ed1 100644 --- a/README.md +++ b/README.md @@ -1,109 +1,14 @@ -# React Developer Tools [![Build Status](https://travis-ci.org/facebook/react-devtools.svg?branch=master)](https://travis-ci.org/facebook/react-devtools) +#### This project has migrated to [github.com/facebook/react](https://github.com/facebook/react) -React Developer Tools is a system that allows you to inspect a React Renderer, -including the Component hierarchy, props, state, and more. +The source code for the v3 of the extension can be found in the [`v3` branch](https://github.com/facebook/react-devtools/tree/v3). -There are shells for Chrome (adding it to the Chrome devtools), Firefox, -Atom/Nuclide, and as a standalone Electron app. +To build the v3 browser extension from source: +```sh +git checkout v3 -![](/images/devtools-full.gif) +# Install dependencies and build the unpacked extension +yarn install +yarn build:extension -## Installation - -### Pre-packaged -The official extensions represent the current stable release. - -- [Chrome extension](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) -- [Firefox extension](https://addons.mozilla.org/firefox/addon/react-devtools/) -- [Standalone app (supports React Native too!)](https://github.com/facebook/react-devtools/blob/master/packages/react-devtools/README.md) - -Opera users can [enable Chrome extensions](https://addons.opera.com/extensions/details/download-chrome-extension-9/) and then install the [Chrome extension](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi). - -If you inspect an element or launch the developer tools on a React page, you -should see an extra tab called **React** in the inspector. - -Check out [For Hacking](#for-hacking) if you want to develop the Developer -Tools or use a pre-prelease version. - -## Usage - -### Supporting tools - -- The babel plugin [transform-react-jsx-source](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-react-jsx-source) is required if you want react devtools to tell you the source file & line number of created react elements. Its display is in the bottom of the right panel if the information is present. - -### React Native - -There is a [standalone version](https://github.com/facebook/react-devtools/blob/master/packages/react-devtools/README.md) that works with React Native. - -### Tree View - -- Arrow keys or hjkl for navigation -- Right click a component to show in elements pane, scroll into view, show - source, etc. -- Use the search bar to find components by name -- A red collapser means the component has state/context - -![](/images/devtools-tree-view.png) - -### Side Pane - -- Right-click to store as global variable -- Updates are highlighted - -![](/images/devtools-side-pane.gif) - -## For Hacking -For changes that don't directly involve Chrome/Firefox/etc. APIs, there is a -"plain" shell that just renders the devtools into an html page along with a -TodoMVC test app. This is by far the quickest way to develop. Check out -[the Readme.md](/shells/plain) in `/shells/plain` for info. - -For other shells (Chrome, Firefox, etc.), see the respective directories in `/shells`. - -## FAQ - -### The React Tab Doesn't Show Up - -If you are running your app from `file://` URL, don't forget to check "Allow access to file URLs" on the Chrome Extensions settings page. - -The "React" tab won't show up if the site doesn't use React, or if React can't communicate with the devtools. When the page loads, the devtools sets a global named `__REACT_DEVTOOLS_GLOBAL_HOOK__`, then React communicates with that hook during initialization. - -You can test this on the [React website](http://facebook.github.io/react/) or by inspecting [Facebook](https://www.facebook.com/). - -If your app is inside an iframe, using React Native, or in another unusual environment, try [the standalone version instead](https://github.com/facebook/react-devtools/tree/master/packages/react-devtools). - -Chrome apps/extensions are currently not inspectable. - -### Does "Trace React Updates" trace renders? - -Yes, but it's also tracing if a component *may* render. -In order to fully understand what counts as an "update", you need to understand how [shouldComponentUpdate](https://facebook.github.io/react/docs/advanced-performance.html#shouldcomponentupdate-in-action) works. -![](https://facebook.github.io/react/img/docs/should-component-update.png) - -Here "Trace React Updates" will draw a border around every node but C4 and C5. -Why does it trace components that don't actually update? (via shouldComponentUpdate() -> false) -This is a limitation of the system used to track updates, and will hopefully change in the future. It doesn't, however, trace the children of components that opt out, as there's no possibility of them updating. -The higher the rate of updates happening per second the more the color changes from blue to red. - -### ProTips - -If you inspect a React element on the page using the regular **Elements** tab, -then switch over to the **React** tab, that element will be automatically -selected in the React tree. - -## Debugging (in Chrome) - -What to do if the extension breaks. - -- check the error console of devtools. Part of React Devtools runs scripts in - the context of your page, and is vulnerable to misbehaving polyfills. -- open devtools out into a new window, and then hit the shortcut to open - devtools again (cmd+option+j or ctrl+shift+j). This is the "debug - devtools" debugger. Check the console there for errors. -- open `chrome://extensions`, find react devtools, and click "background page" - under "Inspected views". You might find the errors there. - -## Contributing - -To read more about the community and guidelines for submitting pull requests, -please read the [Contributing document](CONTRIBUTING.md). +# Follow the on-screen instructions to complete installation +``` \ No newline at end of file diff --git a/agent/Agent.js b/agent/Agent.js deleted file mode 100644 index cefed2aac6..0000000000 --- a/agent/Agent.js +++ /dev/null @@ -1,415 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var {EventEmitter} = require('events'); - -var assign = require('object-assign'); -var guid = require('../utils/guid'); - -import type {RendererID, DataType, OpaqueNodeHandle, NativeType, Helpers} from '../backend/types'; - -type ElementID = string; - -import type Bridge from './Bridge'; - -/** - * The agent lives on the page in the same context as React, observes events - * from the `backend`, and communicates (via a `Bridge`) with the frontend. - * - * It is responsible for generating string IDs (ElementID) for each react - * element, maintaining a mapping of those IDs to elements, handling messages - * from the frontend, and translating between react elements and native - * handles. - * - * - * React - * | - * v - * backend - * | - * v - * ----------- - * | **Agent** | - * ----------- - * ^ - * | - * v - * (Bridge) - * ^ - * | - * serialization - * | - * v - * (Bridge) - * ^ - * | - * v - * ---------------- - * | Frontend Store | - * ---------------- - * - * - * Events from the `backend`: - * - root (got a root) - * - mount (a component mounted) - * - update (a component updated) - * - unmount (a component mounted) - * - * Events from the `frontend` Store: - * - see `addBridge` for subscriptions - * - * Events that Agent fires: - * - selected - * - hideHighlight - * - startInspecting - * - stopInspecting - * - shutdown - * - highlight /highlightMany - * - setSelection - * - root - * - mount - * - update - * - unmount - */ -class Agent extends EventEmitter { - // the window or global -> used to "make a value available in the console" - global: Object; - reactElements: Map; - ids: WeakMap; - elementData: Map; - roots: Set; - reactInternals: {[key: RendererID]: Helpers}; - capabilities: {[key: string]: boolean}; - renderers: Map; - _prevSelected: ?NativeType; - _scrollUpdate: boolean; - _updateScroll: () => void; - - constructor(global: Object, capabilities?: Object) { - super(); - this.global = global; - this.reactElements = new Map(); - this.ids = new WeakMap(); - this.renderers = new Map(); - this.elementData = new Map(); - this.roots = new Set(); - this.reactInternals = {}; - var lastSelected; - this.on('selected', id => { - var data = this.elementData.get(id); - if (data && data.publicInstance && this.global.$r === lastSelected) { - this.global.$r = data.publicInstance; - lastSelected = data.publicInstance; - } - }); - this._prevSelected = null; - this._scrollUpdate = false; - var isReactDOM = window.document && typeof window.document.createElement === 'function'; - this.capabilities = assign({ - scroll: isReactDOM && typeof window.document.body.scrollIntoView === 'function', - dom: isReactDOM, - editTextContent: false, - }, capabilities); - - if (isReactDOM) { - this._updateScroll = this._updateScroll.bind(this); - window.addEventListener('scroll', this._onScroll.bind(this), true); - } - } - - // returns an "unsubscribe" function - sub(ev: string, fn: (data: any) => void): () => void { - this.on(ev, fn); - return () => { - this.removeListener(ev, fn); - }; - } - - setReactInternals(renderer: RendererID, reactInternals: Helpers) { - this.reactInternals[renderer] = reactInternals; - } - - addBridge(bridge: Bridge) { - /** Events received from the frontend **/ - // the initial handshake - bridge.on('requestCapabilities', () => { - bridge.send('capabilities', this.capabilities); - this.emit('connected'); - }); - bridge.on('setState', this._setState.bind(this)); - bridge.on('setProps', this._setProps.bind(this)); - bridge.on('setContext', this._setContext.bind(this)); - bridge.on('makeGlobal', this._makeGlobal.bind(this)); - bridge.on('highlight', id => this.highlight(id)); - bridge.on('highlightMany', id => this.highlightMany(id)); - bridge.on('hideHighlight', () => this.emit('hideHighlight')); - bridge.on('startInspecting', () => this.emit('startInspecting')); - bridge.on('stopInspecting', () => this.emit('stopInspecting')); - bridge.on('selected', id => this.emit('selected', id)); - bridge.on('shutdown', () => this.emit('shutdown')); - bridge.on('changeTextContent', ({id, text}) => { - var node = this.getNodeForID(id); - if (!node) { - return; - } - node.textContent = text; - }); - // used to "inspect node in Elements pane" - bridge.on('putSelectedNode', id => { - window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$node = this.getNodeForID(id); - }); - // used to "view source in Sources pane" - bridge.on('putSelectedInstance', id => { - var node = this.elementData.get(id); - if (node && node.publicInstance) { - window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$inst = node.publicInstance; - } else { - window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$inst = null; - } - }); - // used to select the inspected node ($0) - bridge.on('checkSelection', () => { - var newSelected = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0; - if (newSelected !== this._prevSelected) { - this._prevSelected = newSelected; - var sentSelected = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$node; - if (newSelected !== sentSelected) { - this.selectFromDOMNode(newSelected, true); - } - } - }); - bridge.on('scrollToNode', id => this.scrollToNode(id)); - bridge.on('bananaslugchange', value => this.emit('bananaslugchange', value)); - bridge.on('colorizerchange', value => this.emit('colorizerchange', value)); - - /** Events sent to the frontend **/ - this.on('root', id => bridge.send('root', id)); - this.on('mount', data => bridge.send('mount', data)); - this.on('update', data => bridge.send('update', data)); - this.on('unmount', id => { - bridge.send('unmount', id); - // once an element has been unmounted, the bridge doesn't need to be - // able to inspect it anymore. - bridge.forget(id); - }); - this.on('setSelection', data => bridge.send('select', data)); - } - - scrollToNode(id: ElementID): void { - var node = this.getNodeForID(id); - if (!node) { - console.warn('unable to get the node for scrolling'); - return; - } - var element = node.nodeType === Node.ELEMENT_NODE ? - node : - node.parentElement; - if (!element) { - console.warn('unable to get the element for scrolling'); - return; - } - - if (typeof element.scrollIntoViewIfNeeded === 'function') { - element.scrollIntoViewIfNeeded(); - } else if (typeof element.scrollIntoView === 'function') { - element.scrollIntoView(); - } - this.highlight(id); - } - - highlight(id: ElementID) { - var data = this.elementData.get(id); - var node = this.getNodeForID(id); - if (data && node) { - this.emit('highlight', {node, name: data.name, props: data.props}); - } - } - - highlightMany(ids: Array) { - var nodes = []; - ids.forEach(id => { - var node = this.getNodeForID(id); - if (node) { - nodes.push(node); - } - }); - if (nodes.length) { - this.emit('highlightMany', nodes); - } - } - - getNodeForID(id: ElementID): ?Object { - var component = this.reactElements.get(id); - if (!component) { - return null; - } - var renderer = this.renderers.get(id); - if (renderer && this.reactInternals[renderer].getNativeFromReactElement) { - return this.reactInternals[renderer].getNativeFromReactElement(component); - } - return null; - } - - selectFromDOMNode(node: Object, quiet?: boolean) { - var id = this.getIDForNode(node); - if (!id) { - return; - } - this.emit('setSelection', {id, quiet}); - } - - selectFromReactInstance(instance: OpaqueNodeHandle, quiet?: boolean) { - var id = this.getId(instance); - if (!id) { - console.log('no instance id', instance); - return; - } - this.emit('setSelection', {id, quiet}); - } - - getIDForNode(node: Object): ?ElementID { - if (!this.reactInternals) { - return null; - } - var component; - for (var renderer in this.reactInternals) { - // If a renderer doesn't know about a reactId, it will throw an error. - try { - // $FlowFixMe possibly null - it's not null - component = this.reactInternals[renderer].getReactElementFromNative(node); - } catch (e) {} - if (component) { - return this.getId(component); - } - } - return null; - } - - _setProps({id, path, value}: {id: ElementID, path: Array, value: any}) { - var data = this.elementData.get(id); - if (data && data.updater && data.updater.setInProps) { - data.updater.setInProps(path, value); - } else { - console.warn("trying to set props on a component that doesn't support it"); - } - } - - _setState({id, path, value}: {id: ElementID, path: Array, value: any}) { - var data = this.elementData.get(id); - if (data && data.updater && data.updater.setInState) { - data.updater.setInState(path, value); - } else { - console.warn("trying to set state on a component that doesn't support it"); - } - } - - _setContext({id, path, value}: {id: ElementID, path: Array, value: any}) { - var data = this.elementData.get(id); - if (data && data.updater && data.updater.setInContext) { - data.updater.setInContext(path, value); - } else { - console.warn("trying to set state on a component that doesn't support it"); - } - } - - _makeGlobal({id, path}: {id: ElementID, path: Array}) { - var data = this.elementData.get(id); - if (!data) { - return; - } - var value; - if (path === 'instance') { - value = data.publicInstance; - } else { - value = getIn(data, path); - } - this.global.$tmp = value; - console.log('$tmp =', value); - } - - getId(element: OpaqueNodeHandle): ElementID { - if (typeof element !== 'object' || !element) { - return element; - } - if (!this.ids.has(element)) { - this.ids.set(element, guid()); - this.reactElements.set(this.ids.get(element), element); - } - return this.ids.get(element); - } - - addRoot(renderer: RendererID, element: OpaqueNodeHandle) { - var id = this.getId(element); - this.roots.add(id); - this.emit('root', id); - } - - onMounted(renderer: RendererID, component: OpaqueNodeHandle, data: DataType) { - var id = this.getId(component); - this.renderers.set(id, renderer); - this.elementData.set(id, data); - - var send = assign({}, data); - if (send.children && send.children.map) { - send.children = send.children.map(c => this.getId(c)); - } - send.id = id; - send.canUpdate = send.updater && !!send.updater.forceUpdate; - delete send.type; - delete send.updater; - this.emit('mount', send); - } - - onUpdated(component: OpaqueNodeHandle, data: DataType) { - var id = this.getId(component); - this.elementData.set(id, data); - - var send = assign({}, data); - if (send.children && send.children.map) { - send.children = send.children.map(c => this.getId(c)); - } - send.id = id; - send.canUpdate = send.updater && !!send.updater.forceUpdate; - delete send.type; - delete send.updater; - this.emit('update', send); - } - - onUnmounted(component: OpaqueNodeHandle) { - var id = this.getId(component); - this.elementData.delete(id); - this.roots.delete(id); - this.renderers.delete(id); - this.emit('unmount', id); - this.ids.delete(component); - } - - _onScroll() { - if (!this._scrollUpdate) { - this._scrollUpdate = true; - window.requestAnimationFrame(this._updateScroll); - } - } - - _updateScroll() { - this.emit('refreshMultiOverlay'); - this._scrollUpdate = false; - } -} - -function getIn(base, path) { - return path.reduce((obj, attr) => { - return obj ? obj[attr] : null; - }, base); -} - -module.exports = Agent; diff --git a/agent/Bridge.js b/agent/Bridge.js deleted file mode 100644 index db41f48288..0000000000 --- a/agent/Bridge.js +++ /dev/null @@ -1,447 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var consts = require('./consts'); -var hydrate = require('./hydrate'); -var dehydrate = require('./dehydrate'); -var performanceNow = require('fbjs/lib/performanceNow'); - -// Custom polyfill that runs the queue with a backoff. -// If you change it, make sure it behaves reasonably well in Firefox. -var lastRunTimeMS = 5; -var cancelIdleCallback = window.cancelIdleCallback || clearTimeout; -var requestIdleCallback = window.requestIdleCallback || function(cb, options) { - // Magic numbers determined by tweaking in Firefox. - // There is no special meaning to them. - var delayMS = 3000 * lastRunTimeMS; - if (delayMS > 500) { - delayMS = 500; - } - - return setTimeout(() => { - var startTime = performanceNow(); - cb({ - didTimeout: false, - timeRemaining() { - return Infinity; - }, - }); - var endTime = performanceNow(); - lastRunTimeMS = (endTime - startTime) / 1000; - }, delayMS); -}; - -type AnyFn = (...x: any) => any; -export type Wall = { - listen: (fn: (data: PayloadType) => void) => void, - send: (data: PayloadType) => void, -}; - -type IdleDeadline = { - didTimeout: bool, - timeRemaining: () => number, -}; - -type EventPayload = { - type: 'event', - cleaned: ?Array>, - evt: string, - data: any, -}; - -type PayloadType = { - type: 'inspect', - id: string, - path: Array, - callback: number, -} | { - type: 'many-events', - events: Array, -} | { - type: 'call', - name: string, - args: Array, - callback: number, -} | { - type: 'callback', - id: number, - args: Array, -} | { - type: 'pause', -} | { - type: 'resume', -} | EventPayload; - -/** - * The bridge is responsible for serializing requests between the Agent and - * the Frontend Store. It needs to be connected to a Wall object that can send - * JSONable data to the bridge on the other side. - * - * complex data - * | - * v - * [Bridge] - * | - * jsonable data - * | - * v - * [wall] - * | - * v - * ~ some barrier ~ - * | - * v - * [wall] - * | - * v - * [Bridge] - * | - * v - * "hydrated" data - * - * When an item is passed in that can't be serialized (anything other than a - * plain array, object, or literal value), the object is "cleaned", and - * rehydrated on the other side with `Symbol` attributes indicating that the - * object needs to be inspected for more detail. - * - * Example: - * - * bridge.send('evname', {id: 'someid', foo: MyCoolObjectInstance}) - * -> - * shows up, hydrated as - * { - * id: 'someid', - * foo: { - * [consts.name]: 'MyCoolObjectInstance', - * [consts.type]: 'object', - * [consts.meta]: {}, - * [consts.inspected]: false, - * } - * } - * - * The `consts` variables are Symbols, and as such are non-ennumerable. - * The front-end therefore needs to check for `consts.inspected` on received - * objects, and can thereby display object proxies and inspect them. - * - * Complex objects that are passed are expected to have a top-level `id` - * attribute, which is used for later lookup + inspection. Once it has been - * determined that an object is no longer needed, call `.forget(id)` to clean - * up. - */ -class Bridge { - _buffer: Array<{evt: string, data: any}>; - _cbs: Map; - _cid: number; - _inspectables: Map; - _listeners: {[key: string]: Array<(data: any) => void>}; - _flushHandle: ?number; - _wall: Wall; - _callers: {[key: string]: AnyFn}; - _paused: boolean; - - constructor(wall: Wall) { - this._cbs = new Map(); - this._inspectables = new Map(); - this._cid = 0; - this._listeners = {}; - this._buffer = []; - this._flushHandle = null; - this._callers = {}; - this._paused = false; - this._wall = wall; - - wall.listen(this._handleMessage.bind(this)); - } - - inspect(id: string, path: Array, cb: (val: any) => any) { - var _cid = this._cid++; - this._cbs.set(_cid, (data, cleaned, proto, protoclean) => { - if (cleaned.length) { - hydrate(data, cleaned); - } - if (proto && protoclean.length) { - hydrate(proto, protoclean); - } - if (proto) { - data[consts.proto] = proto; - } - cb(data); - }); - - this._wall.send({ - type: 'inspect', - callback: _cid, - path, - id, - }); - } - - call(name: string, args: Array, cb: (val: any) => any) { - var _cid = this._cid++; - this._cbs.set(_cid, cb); - - this._wall.send({ - type: 'call', - callback: _cid, - args, - name, - }); - } - - onCall(name: string, handler: (data: any) => any) { - if (this._callers[name]) { - throw new Error('only one call handler per call name allowed'); - } - this._callers[name] = handler; - } - - pause() { - this._wall.send({ - type: 'pause', - }); - } - - resume() { - this._wall.send({ - type: 'resume', - }); - } - - setInspectable(id: string, data: Object) { - var prev = this._inspectables.get(id); - if (!prev) { - this._inspectables.set(id, data); - return; - } - this._inspectables.set(id, {...prev, ...data}); - } - - send(evt: string, data: any) { - this._buffer.push({evt, data}); - this.scheduleFlush(); - } - - scheduleFlush() { - if (!this._flushHandle && this._buffer.length) { - var timeout = this._paused ? 5000 : 500; - this._flushHandle = requestIdleCallback( - this.flushBufferWhileIdle.bind(this), - {timeout} - ); - } - } - - cancelFlush() { - if (this._flushHandle) { - cancelIdleCallback(this._flushHandle); - this._flushHandle = null; - } - } - - flushBufferWhileIdle(deadline: IdleDeadline) { - this._flushHandle = null; - - // Magic numbers were determined by tweaking in a heavy UI and seeing - // what performs reasonably well both when DevTools are hidden and visible. - // The goal is that we try to catch up but avoid blocking the UI. - // When paused, it's okay to lag more, but not forever because otherwise - // when user activates React tab, it will freeze syncing. - var chunkCount = this._paused ? 20 : 10; - var chunkSize = Math.round(this._buffer.length / chunkCount); - var minChunkSize = this._paused ? 50 : 100; - - while (this._buffer.length && ( - deadline.timeRemaining() > 0 || - deadline.didTimeout - )) { - var take = Math.min(this._buffer.length, Math.max(minChunkSize, chunkSize)); - var currentBuffer = this._buffer.splice(0, take); - this.flushBufferSlice(currentBuffer); - } - - if (this._buffer.length) { - this.scheduleFlush(); - } - } - - flushBufferSlice(bufferSlice: Array<{evt: string, data: any}>) { - var events = bufferSlice.map(({evt, data}) => { - var cleaned = []; - var san = dehydrate(data, cleaned); - if (cleaned.length) { - this.setInspectable(data.id, data); - } - return {type: 'event', evt, data: san, cleaned}; - }); - this._wall.send({type: 'many-events', events}); - } - - forget(id: string) { - this._inspectables.delete(id); - } - - on(evt: string, fn: AnyFn) { - if (!this._listeners[evt]) { - this._listeners[evt] = [fn]; - } else { - this._listeners[evt].push(fn); - } - } - - off(evt: string, fn: AnyFn) { - if (!this._listeners[evt]) { - return; - } - var ix = this._listeners[evt].indexOf(fn); - if (ix !== -1) { - this._listeners[evt].splice(ix, 1); - } - } - - once(evt: string, fn: AnyFn) { - var self = this; - var listener = function() { - fn.apply(this, arguments); - self.off(evt, listener); - }; - this.on(evt, listener); - } - - _handleMessage(payload: PayloadType) { - if (payload.type === 'resume') { - this._paused = false; - this.scheduleFlush(); - return; - } - - if (payload.type === 'pause') { - this._paused = true; - this.cancelFlush(); - return; - } - - if (payload.type === 'callback') { - var callback = this._cbs.get(payload.id); - if (callback) { - callback(...payload.args); - this._cbs.delete(payload.id); - } - return; - } - - if (payload.type === 'call') { - this._handleCall(payload.name, payload.args, payload.callback); - return; - } - - if (payload.type === 'inspect') { - this._inspectResponse(payload.id, payload.path, payload.callback); - return; - } - - if (payload.type === 'event') { - // console.log('[bridge<-]', payload.evt); - if (payload.cleaned) { - hydrate(payload.data, payload.cleaned); - } - var fns = this._listeners[payload.evt]; - var data = payload.data; - if (fns) { - fns.forEach(fn => fn(data)); - } - } - - if (payload.type === 'many-events') { - payload.events.forEach(event => { - // console.log('[bridge<-]', payload.evt); - if (event.cleaned) { - hydrate(event.data, event.cleaned); - } - var handlers = this._listeners[event.evt]; - if (handlers) { - handlers.forEach(fn => fn(event.data)); - } - }); - } - } - - _handleCall(name: string, args: Array, callback: number) { - if (!this._callers[name]) { - console.warn('unknown call: "' + name + '"'); - return; - } - args = !Array.isArray(args) ? [args] : args; - var result; - try { - result = this._callers[name].apply(null, args); - } catch (e) { - console.error('Failed to call', e); - return; - } - this._wall.send({ - type: 'callback', - id: callback, - args: [result], - }); - } - - _inspectResponse(id: string, path: Array, callback: number) { - var inspectable = this._inspectables.get(id); - - var result = {}; - var cleaned = []; - var proto = null; - var protoclean = []; - if (inspectable) { - var val = getIn(inspectable, path); - var protod = false; - var isFn = typeof val === 'function'; - Object.getOwnPropertyNames(val).forEach(name => { - if (name === '__proto__') { - protod = true; - } - if (isFn && (name === 'arguments' || name === 'callee' || name === 'caller')) { - return; - } - result[name] = dehydrate(val[name], cleaned, [name]); - }); - - /* eslint-disable no-proto */ - if (!protod && val.__proto__ && val.constructor.name !== 'Object') { - var newProto = {}; - var pIsFn = typeof val.__proto__ === 'function'; - Object.getOwnPropertyNames(val.__proto__).forEach(name => { - if (pIsFn && (name === 'arguments' || name === 'callee' || name === 'caller')) { - return; - } - newProto[name] = dehydrate(val.__proto__[name], protoclean, [name]); - }); - proto = newProto; - } - /* eslint-enable no-proto */ - } - - this._wall.send({ - type: 'callback', - id: callback, - args: [result, cleaned, proto, protoclean], - }); - } -} - -function getIn(base, path) { - return path.reduce((obj, attr) => { - return obj ? obj[attr] : null; - }, base); -} - -module.exports = Bridge; diff --git a/agent/__tests__/Agent-test.js b/agent/__tests__/Agent-test.js deleted file mode 100644 index e2e6bd3a1f..0000000000 --- a/agent/__tests__/Agent-test.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -jest.dontMock('../Agent'); -var Agent = require('../Agent'); - -describe('Agent', () => { - - const publicInstance1 = {}; - const publicInstance2 = {}; - let agent; - beforeEach(() => { - agent = new Agent({}); - agent.elementData.set('test1', { publicInstance: publicInstance1 }); - agent.elementData.set('test2', { publicInstance: publicInstance2 }); - }); - - it('sets global $r if it is not set', () => { - delete agent.global.$r; - agent.emit('selected', 'test1'); - expect(agent.global.$r).toBe(publicInstance1); - }); - - it('overwrites global $r if it was last set by itself', () => { - agent.emit('selected', 'test1'); - expect(agent.global.$r).toBe(publicInstance1); - agent.emit('selected', 'test2'); - expect(agent.global.$r).toBe(publicInstance2); - }); - - it('does not overwrite global $r if was not last set by itself', () => { - agent.emit('selected', 'test1'); - expect(agent.global.$r).toBe(publicInstance1); - agent.global.$r = 'set externally'; - expect(agent.global.$r).toBe('set externally'); - agent.emit('selected', 'test1'); - expect(agent.global.$r).toBe('set externally'); - }); - -}); diff --git a/agent/__tests__/dehydrate-test.js b/agent/__tests__/dehydrate-test.js deleted file mode 100644 index 3c3dea3a23..0000000000 --- a/agent/__tests__/dehydrate-test.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -jest.dontMock('../dehydrate'); -var dehydrate = require('../dehydrate'); - -describe('dehydrate', () => { - it('leaves an empty object alone', () => { - var cleaned = []; - var result = dehydrate({}, cleaned); - expect(result).toEqual({}); - }); - - it('preserves a shallowly nested object', () => { - var object = { - a: {b: 1, c: 2, d: 3}, - b: ['h', 'i', 'j'], - }; - var cleaned = []; - var result = dehydrate(object, cleaned); - expect(cleaned).toEqual([]); - expect(result).toEqual(object); - }); - - it('cleans a deeply nested object', () => { - var object = {a: {b: {c: {d: 4}}}}; - var cleaned = []; - var result = dehydrate(object, cleaned); - expect(cleaned).toEqual([['a', 'b', 'c']]); - expect(result.a.b.c).toEqual({type: 'object', name: '', meta: null}); - }); - - it('cleans a deeply nested array', () => { - var object = {a: {b: {c: [1, 3]}}}; - var cleaned = []; - var result = dehydrate(object, cleaned); - expect(cleaned).toEqual([['a', 'b', 'c']]); - expect(result.a.b.c).toEqual({type: 'array', name: 'Array', meta: {length: 2}}); - }); - - it('cleans multiple things', () => { - var Something = function() {}; - var object = {a: {b: {c: [1, 3], d: new Something()}}}; - var cleaned = []; - var result = dehydrate(object, cleaned); - expect(cleaned).toEqual([['a', 'b', 'c'], ['a', 'b', 'd']]); - expect(result.a.b.c).toEqual({type: 'array', name: 'Array', meta: {length: 2}}); - expect(result.a.b.d).toEqual({type: 'object', name: 'Something', meta: null}); - }); -}); diff --git a/agent/consts.js b/agent/consts.js deleted file mode 100644 index 38e1b5ef05..0000000000 --- a/agent/consts.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var Symbol = require('es6-symbol'); - -module.exports = { - name: Symbol('name'), - type: Symbol('type'), - inspected: Symbol('inspected'), - meta: Symbol('meta'), - proto: Symbol('proto'), -}; diff --git a/agent/dehydrate.js b/agent/dehydrate.js deleted file mode 100644 index 41003771c6..0000000000 --- a/agent/dehydrate.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -/** - * Strip out complex data (instances, functions, and data nested > 2 levels - * deep). The paths of the stripped out objects are appended to the `cleaned` - * list. On the other side of the barrier, the cleaned list is used to - * "re-hydrate" the cleaned representation into an object with symbols as - * attributes, so that a sanitized object can be distinguished from a normal - * object. - * - * Input: {"some": {"attr": fn()}, "other": AnInstance} - * Output: { - * "some": { - * "attr": {"name": the fn.name, type: "function"} - * }, - * "other": { - * "name": "AnInstance", - * "type": "object", - * }, - * } - * and cleaned = [["some", "attr"], ["other"]] - */ -function dehydrate(data: Object, cleaned: Array>, path?: Array, level?: number): string | Object { - level = level || 0; - path = path || []; - if (typeof data === 'function') { - cleaned.push(path); - return { - name: data.name, - type: 'function', - }; - } - if (!data || typeof data !== 'object') { - if (typeof data === 'string' && data.length > 500) { - return data.slice(0, 500) + '...'; - } - // We have to do this assignment b/c Flow doesn't think "symbol" is - // something typeof would return. Error 'unexpected predicate "symbol"' - var type = typeof data; - if (type === 'symbol') { - cleaned.push(path); - return { - type: 'symbol', - name: data.toString(), - }; - } - return data; - } - if (data._reactFragment) { - // React Fragments error if you try to inspect them. - return 'A react fragment'; - } - if (level > 2) { - cleaned.push(path); - return { - type: Array.isArray(data) ? 'array' : 'object', - name: !data.constructor || data.constructor.name === 'Object' ? '' : data.constructor.name, - meta: Array.isArray(data) ? { - length: data.length, - } : null, - }; - } - if (Array.isArray(data)) { - // $FlowFixMe path is not undefined. - return data.map((item, i) => dehydrate(item, cleaned, path.concat([i]), level + 1)); - } - // TODO when this is in the iframe window, we can just use Object - if (data.constructor && typeof data.constructor === 'function' && data.constructor.name !== 'Object') { - cleaned.push(path); - return { - name: data.constructor.name, - type: 'object', - }; - } - var res = {}; - var names = Object.getOwnPropertyNames(data); - var name; - - for (var nameIndex = 0; nameIndex < names.length; nameIndex++) { - name = names[nameIndex]; - - res[name] = dehydrate(data[name], cleaned, path.concat([name]), level + 1); - } - - return res; -} - -module.exports = dehydrate; diff --git a/agent/hydrate.js b/agent/hydrate.js deleted file mode 100644 index 7a3d2d6a3c..0000000000 --- a/agent/hydrate.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -var consts = require('./consts'); - -function hydrate(data: Object, cleaned: Array>): void { - cleaned.forEach(path => { - var last = path.pop(); - var obj = path.reduce((obj_, attr) => obj_ ? obj_[attr] : null, data); - if (!obj || !obj[last]) { - return; - } - var replace: {[key: Symbol]: boolean | string} = {}; - replace[consts.name] = obj[last].name; - replace[consts.type] = obj[last].type; - replace[consts.meta] = obj[last].meta; - replace[consts.inspected] = false; - obj[last] = replace; - }); -} - -module.exports = hydrate; diff --git a/agent/inject.js b/agent/inject.js deleted file mode 100644 index 26b70f10a4..0000000000 --- a/agent/inject.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Hook} from '../backend/types'; -import type Agent from './Agent'; - -var setupBackend = require('../backend/backend'); - -module.exports = function(hook: Hook, agent: Agent) { - var subs = [ - hook.sub('renderer-attached', ({id, renderer, helpers}) => { - agent.setReactInternals(id, helpers); - helpers.walkTree(agent.onMounted.bind(agent, id), agent.addRoot.bind(agent, id)); - }), - hook.sub('root', ({renderer, element}) => agent.addRoot(renderer, element)), - hook.sub('mount', ({renderer, element, data}) => agent.onMounted(renderer, element, data)), - hook.sub('update', ({renderer, element, data}) => agent.onUpdated(element, data)), - hook.sub('unmount', ({renderer, element}) => agent.onUnmounted(element)), - ]; - - var success = setupBackend(hook); - if (!success) { - return; - } - - hook.emit('react-devtools', agent); - hook.reactDevtoolsAgent = agent; - agent.on('shutdown', () => { - subs.forEach(fn => fn()); - hook.reactDevtoolsAgent = null; - }); -}; diff --git a/backend/ReactTypeOfWork.js b/backend/ReactTypeOfWork.js deleted file mode 100644 index 2664fa8a53..0000000000 --- a/backend/ReactTypeOfWork.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -// Copied from React repo. - -module.exports = { - IndeterminateComponent: 0, // Before we know whether it is functional or class - FunctionalComponent: 1, - ClassComponent: 2, - HostRoot: 3, // Root of a host tree. Could be nested inside another node. - HostPortal: 4, // A subtree. Could be an entry point to a different renderer. - HostComponent: 5, - HostText: 6, - CoroutineComponent: 7, - CoroutineHandlerPhase: 8, - YieldComponent: 9, - Fragment: 10, -}; diff --git a/backend/__tests__/copyWithSet-test.js b/backend/__tests__/copyWithSet-test.js deleted file mode 100644 index 2589fa7184..0000000000 --- a/backend/__tests__/copyWithSet-test.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -'use strict'; - -jest.dontMock('../copyWithSet'); -var copyWithSet = require('../copyWithSet'); - -describe('copyWithSet', function() { - it('adds a property', function() { - var res = copyWithSet({c: 2, d: 4}, ['a'], 'b'); - expect(res).toEqual({a: 'b', c: 2, d: 4}); - }); - - it('modifies a property', function() { - var res = copyWithSet({c: 2, d: 4}, ['c'], '10'); - expect(res).toEqual({c: '10', d: 4}); - }); - - it('preserves deep objects', function() { - var res = copyWithSet({a: {b: {c: 3}, d: 2}, e: 1}, ['a', 'b', 'c'], 10); - expect(res).toEqual({a: {b: {c: 10}, d: 2}, e: 1}); - }); - - it('modifies an array', function() { - var res = copyWithSet(['a', 'b', 'x'], [2], 'c'); - expect(res).toEqual(['a', 'b', 'c']); - }); - - it('works with complexity', function() { - var res = copyWithSet([0, 1, {2: {3: [4, 5, {6: {7: 8}}, 9], 10: 11}}, 12], [2, '2', '3', 2, '6', '7'], 'moose'); - expect(res).toEqual([0, 1, {2: {3: [4, 5, {6: {7: 'moose'}}, 9], 10: 11}}, 12]); - }); -}); diff --git a/backend/attachRenderer.js b/backend/attachRenderer.js deleted file mode 100644 index 75a60cdd41..0000000000 --- a/backend/attachRenderer.js +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {DataType, OpaqueNodeHandle, Hook, ReactRenderer, Helpers} from './types'; -var getData = require('./getData'); -var getData012 = require('./getData012'); -var attachRendererFiber = require('./attachRendererFiber'); - -type NodeLike = {}; - -/** - * This takes care of patching the renderer to emit events on the global - * `Hook`. The returned object has a `.cleanup` method to un-patch everything. - */ -function attachRenderer(hook: Hook, rid: string, renderer: ReactRenderer): Helpers { - var rootNodeIDMap = new Map(); - var extras = {}; - // Before 0.13 there was no Reconciler, so we patch Component.Mixin - var isPre013 = !renderer.Reconciler; - - // React Fiber - if (typeof renderer.findFiberByHostInstance === 'function') { - return attachRendererFiber(hook, rid, renderer); - } - - // React Native - if (renderer.Mount.findNodeHandle && renderer.Mount.nativeTagToRootNodeID) { - extras.getNativeFromReactElement = function(component) { - return renderer.Mount.findNodeHandle(component); - }; - - extras.getReactElementFromNative = function(nativeTag) { - var id = renderer.Mount.nativeTagToRootNodeID(nativeTag); - return rootNodeIDMap.get(id); - }; - // React DOM 15+ - } else if (renderer.ComponentTree) { - extras.getNativeFromReactElement = function(component) { - return renderer.ComponentTree.getNodeFromInstance(component); - }; - - extras.getReactElementFromNative = function(node) { - return renderer.ComponentTree.getClosestInstanceFromNode(node); - }; - // React DOM - } else if (renderer.Mount.getID && renderer.Mount.getNode) { - extras.getNativeFromReactElement = function(component) { - try { - return renderer.Mount.getNode(component._rootNodeID); - } catch (e) { - return undefined; - } - }; - - extras.getReactElementFromNative = function(node) { - var id = renderer.Mount.getID(node); - while (node && node.parentNode && !id) { - node = node.parentNode; - id = renderer.Mount.getID(node); - } - return rootNodeIDMap.get(id); - }; - } else { - console.warn('Unknown react version (does not have getID), probably an unshimmed React Native'); - } - - var oldMethods; - var oldRenderComponent; - var oldRenderRoot; - - // React DOM - if (renderer.Mount._renderNewRootComponent) { - oldRenderRoot = decorateResult(renderer.Mount, '_renderNewRootComponent', (element) => { - hook.emit('root', {renderer: rid, element}); - }); - // React Native - } else if (renderer.Mount.renderComponent) { - oldRenderComponent = decorateResult(renderer.Mount, 'renderComponent', element => { - hook.emit('root', {renderer: rid, element: element._reactInternalInstance}); - }); - } - - if (renderer.Component) { - console.error('You are using a version of React with limited support in this version of the devtools.\nPlease upgrade to use at least 0.13, or you can downgrade to use the old version of the devtools:\ninstructions here https://github.com/facebook/react-devtools/tree/devtools-next#how-do-i-use-this-for-react--013'); - // 0.11 - 0.12 - // $FlowFixMe renderer.Component is not "possibly undefined" - oldMethods = decorateMany(renderer.Component.Mixin, { - mountComponent() { - rootNodeIDMap.set(this._rootNodeID, this); - // FIXME DOMComponent calls Component.Mixin, and sets up the - // `children` *after* that call, meaning we don't have access to the - // children at this point. Maybe we should find something else to shim - // (do we have access to DOMComponent here?) so that we don't have to - // setTimeout. - setTimeout(() => { - hook.emit('mount', {element: this, data: getData012(this), renderer: rid}); - }, 0); - }, - updateComponent() { - setTimeout(() => { - hook.emit('update', {element: this, data: getData012(this), renderer: rid}); - }, 0); - }, - unmountComponent() { - hook.emit('unmount', {element: this, renderer: rid}); - rootNodeIDMap.delete(this._rootNodeID, this); - }, - }); - } else if (renderer.Reconciler) { - oldMethods = decorateMany(renderer.Reconciler, { - mountComponent(element, rootID, transaction, context) { - var data = getData(element); - rootNodeIDMap.set(element._rootNodeID, element); - hook.emit('mount', {element, data, renderer: rid}); - }, - performUpdateIfNecessary(element, nextChild, transaction, context) { - hook.emit('update', {element, data: getData(element), renderer: rid}); - }, - receiveComponent(element, nextChild, transaction, context) { - hook.emit('update', {element, data: getData(element), renderer: rid}); - }, - unmountComponent(element) { - hook.emit('unmount', {element, renderer: rid}); - rootNodeIDMap.delete(element._rootNodeID, element); - }, - }); - } - - extras.walkTree = function(visit: (component: OpaqueNodeHandle, data: DataType) => void, visitRoot: (element: OpaqueNodeHandle) => void) { - var onMount = (component, data) => { - rootNodeIDMap.set(component._rootNodeID, component); - visit(component, data); - }; - walkRoots(renderer.Mount._instancesByReactRootID || renderer.Mount._instancesByContainerID, onMount, visitRoot, isPre013); - }; - - extras.cleanup = function() { - if (oldMethods) { - if (renderer.Component) { - restoreMany(renderer.Component.Mixin, oldMethods); - } else { - restoreMany(renderer.Reconciler, oldMethods); - } - } - if (oldRenderRoot) { - renderer.Mount._renderNewRootComponent = oldRenderRoot; - } - if (oldRenderComponent) { - renderer.Mount.renderComponent = oldRenderComponent; - } - oldMethods = null; - oldRenderRoot = null; - oldRenderComponent = null; - }; - - return extras; -} - -function walkRoots(roots, onMount, onRoot, isPre013) { - for (var name in roots) { - walkNode(roots[name], onMount, isPre013); - onRoot(roots[name]); - } -} - -function walkNode(element, onMount, isPre013) { - var data = isPre013 ? getData012(element) : getData(element); - if (data.children && Array.isArray(data.children)) { - data.children.forEach(child => walkNode(child, onMount, isPre013)); - } - onMount(element, data); -} - -function decorateResult(obj, attr, fn) { - var old = obj[attr]; - obj[attr] = function(instance: NodeLike) { - var res = old.apply(this, arguments); - fn(res); - return res; - }; - return old; -} - -function decorate(obj, attr, fn) { - var old = obj[attr]; - obj[attr] = function(instance: NodeLike) { - var res = old.apply(this, arguments); - fn.apply(this, arguments); - return res; - }; - return old; -} - -function decorateMany(source, fns) { - var olds = {}; - for (var name in fns) { - olds[name] = decorate(source, name, fns[name]); - } - return olds; -} - -function restoreMany(source, olds) { - for (var name in olds) { - source[name] = olds[name]; - } -} - -module.exports = attachRenderer; diff --git a/backend/attachRendererFiber.js b/backend/attachRendererFiber.js deleted file mode 100644 index 45329bedbc..0000000000 --- a/backend/attachRendererFiber.js +++ /dev/null @@ -1,277 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {Hook, ReactRenderer, Helpers} from './types'; -var getDataFiber = require('./getDataFiber'); -var { - ClassComponent, - HostRoot, -} = require('./ReactTypeOfWork'); - -function attachRendererFiber(hook: Hook, rid: string, renderer: ReactRenderer): Helpers { - // This is a slightly annoying indirection. - // It is currently necessary because DevTools wants - // to use unique objects as keys for instances. - // However fibers have two versions. - // We use this set to remember first encountered fiber for - // each conceptual instance. - const opaqueNodes = new Set(); - function getOpaqueNode(fiber) { - if (opaqueNodes.has(fiber)) { - return fiber; - } - const {alternate} = fiber; - if (alternate != null && opaqueNodes.has(alternate)) { - return alternate; - } - opaqueNodes.add(fiber); - return fiber; - } - - function hasDataChanged(prevFiber, nextFiber) { - if (prevFiber.tag === ClassComponent) { - // Only classes have context. - if (prevFiber.stateNode.context !== nextFiber.stateNode.context) { - return true; - } - // Force updating won't update state or props. - if (nextFiber.updateQueue != null && nextFiber.updateQueue.hasForceUpdate) { - return true; - } - } - // Compare the fields that would result in observable changes in DevTools. - // We don't compare type, tag, index, and key, because these are known to match. - return ( - prevFiber.memoizedProps !== nextFiber.memoizedProps || - prevFiber.memoizedState !== nextFiber.memoizedState || - prevFiber.ref !== nextFiber.ref || - prevFiber._debugSource !== nextFiber._debugSource - ); - } - - let pendingEvents = []; - - function flushPendingEvents() { - const events = pendingEvents; - pendingEvents = []; - for (let i = 0; i < events.length; i++) { - const event = events[i]; - hook.emit(event.type, event); - } - } - - function enqueueMount(fiber) { - pendingEvents.push({ - // TODO: the naming is confusing. `element` is *not* a React element. It is an opaque ID. - element: getOpaqueNode(fiber), - data: getDataFiber(fiber, getOpaqueNode), - renderer: rid, - type: 'mount', - }); - - const isRoot = fiber.tag === HostRoot; - if (isRoot) { - pendingEvents.push({ - element: getOpaqueNode(fiber), - renderer: rid, - type: 'root', - }); - } - } - - function enqueueUpdateIfNecessary(fiber, hasChildOrderChanged) { - if (!hasChildOrderChanged && !hasDataChanged(fiber.alternate, fiber)) { - return; - } - pendingEvents.push({ - element: getOpaqueNode(fiber), - data: getDataFiber(fiber, getOpaqueNode), - renderer: rid, - type: 'update', - }); - } - - function enqueueUnmount(fiber) { - const isRoot = fiber.tag === HostRoot; - const opaqueNode = getOpaqueNode(fiber); - const event = { - element: opaqueNode, - renderer: rid, - type: 'unmount', - }; - if (isRoot) { - pendingEvents.push(event); - } else { - // Non-root fibers are deleted during the commit phase. - // They are deleted in the child-first order. However - // DevTools currently expects deletions to be parent-first. - // This is why we unshift deletions rather than push them. - pendingEvents.unshift(event); - } - opaqueNodes.delete(opaqueNode); - } - - function mountFiber(fiber) { - // Depth-first. - // Logs mounting of children first, parents later. - let node = fiber; - outer: while (true) { - if (node.child) { - node.child.return = node; - node = node.child; - continue; - } - enqueueMount(node); - if (node == fiber) { - return; - } - if (node.sibling) { - node.sibling.return = node.return; - node = node.sibling; - continue; - } - while (node.return) { - node = node.return; - enqueueMount(node); - if (node == fiber) { - return; - } - if (node.sibling) { - node.sibling.return = node.return; - node = node.sibling; - continue outer; - } - } - return; - } - } - - function updateFiber(nextFiber, prevFiber) { - let hasChildOrderChanged = false; - if (nextFiber.child !== prevFiber.child) { - // If the first child is different, we need to traverse them. - // Each next child will be either a new child (mount) or an alternate (update). - let nextChild = nextFiber.child; - let prevChildAtSameIndex = prevFiber.child; - while (nextChild) { - // We already know children will be referentially different because - // they are either new mounts or alternates of previous children. - // Schedule updates and mounts depending on whether alternates exist. - // We don't track deletions here because they are reported separately. - if (nextChild.alternate) { - const prevChild = nextChild.alternate; - updateFiber(nextChild, prevChild); - // However we also keep track if the order of the children matches - // the previous order. They are always different referentially, but - // if the instances line up conceptually we'll want to know that. - if (!hasChildOrderChanged && prevChild !== prevChildAtSameIndex) { - hasChildOrderChanged = true; - } - } else { - mountFiber(nextChild); - if (!hasChildOrderChanged) { - hasChildOrderChanged = true; - } - } - // Try the next child. - nextChild = nextChild.sibling; - // Advance the pointer in the previous list so that we can - // keep comparing if they line up. - if (!hasChildOrderChanged && prevChildAtSameIndex != null) { - prevChildAtSameIndex = prevChildAtSameIndex.sibling; - } - } - // If we have no more children, but used to, they don't line up. - if (!hasChildOrderChanged && prevChildAtSameIndex != null) { - hasChildOrderChanged = true; - } - } - enqueueUpdateIfNecessary(nextFiber, hasChildOrderChanged); - } - - function walkTree() { - hook.getFiberRoots(rid).forEach(root => { - // Hydrate all the roots for the first time. - mountFiber(root.current); - }); - flushPendingEvents(); - } - - function cleanup() { - // We don't patch any methods so there is no cleanup. - } - - function handleCommitFiberUnmount(fiber) { - // This is not recursive. - // We can't traverse fibers after unmounting so instead - // we rely on React telling us about each unmount. - // It will be flushed after the root is committed. - enqueueUnmount(fiber); - } - - function handleCommitFiberRoot(root) { - const current = root.current; - const alternate = current.alternate; - if (alternate) { - // TODO: relying on this seems a bit fishy. - const wasMounted = alternate.memoizedState != null && alternate.memoizedState.element != null; - const isMounted = current.memoizedState != null && current.memoizedState.element != null; - if (!wasMounted && isMounted) { - // Mount a new root. - mountFiber(current); - } else if (wasMounted && isMounted) { - // Update an existing root. - updateFiber(current, alternate); - } else if (wasMounted && !isMounted) { - // Unmount an existing root. - enqueueUnmount(current); - } - } else { - // Mount a new root. - mountFiber(current); - } - // We're done here. - flushPendingEvents(); - } - - // The naming is confusing. - // They deal with opaque nodes (fibers), not elements. - function getNativeFromReactElement(fiber) { - try { - const opaqueNode = fiber; - const hostInstance = renderer.findHostInstanceByFiber(opaqueNode); - return hostInstance; - } catch (err) { - // The fiber might have unmounted by now. - return null; - } - } - function getReactElementFromNative(hostInstance) { - const fiber = renderer.findFiberByHostInstance(hostInstance); - if (fiber != null) { - // TODO: type fibers. - const opaqueNode = getOpaqueNode((fiber: any)); - return opaqueNode; - } - return null; - } - return { - getNativeFromReactElement, - getReactElementFromNative, - handleCommitFiberRoot, - handleCommitFiberUnmount, - cleanup, - walkTree, - }; -} - -module.exports = attachRendererFiber; diff --git a/backend/backend.js b/backend/backend.js deleted file mode 100644 index ff64a5ccd9..0000000000 --- a/backend/backend.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - * - * This is the chrome devtools - * - * 1. Devtools sets the __REACT_DEVTOOLS_GLOBAL_HOOK__ global. - * 2. React (if present) calls .inject() with the internal renderer - * 3. Devtools sees the renderer, and then adds this backend, along with the Agent - * and whatever else is needed. - * 4. The agent then calls `.emit('react-devtools', agent)` - * - * Now things are hooked up. - * - * When devtools closes, it calls `cleanup()` to remove the listeners - * and any overhead caused by the backend. - */ -'use strict'; - -import type {Hook} from './types'; - -var attachRenderer = require('./attachRenderer'); - -module.exports = function setupBackend(hook: Hook): boolean { - var oldReact = window.React && window.React.__internals; - if (oldReact && Object.keys(hook._renderers).length === 0) { - hook.inject(oldReact); - } - - for (var rid in hook._renderers) { - hook.helpers[rid] = attachRenderer(hook, rid, hook._renderers[rid]); - hook.emit('renderer-attached', {id: rid, renderer: hook._renderers[rid], helpers: hook.helpers[rid]}); - } - - hook.on('renderer', ({id, renderer}) => { - hook.helpers[id] = attachRenderer(hook, id, renderer); - hook.emit('renderer-attached', {id, renderer, helpers: hook.helpers[id]}); - }); - - var shutdown = () => { - for (var id in hook.helpers) { - hook.helpers[id].cleanup(); - } - hook.off('shutdown', shutdown); - }; - hook.on('shutdown', shutdown); - - return true; -}; diff --git a/backend/copyWithSet.js b/backend/copyWithSet.js deleted file mode 100644 index 8c04739614..0000000000 --- a/backend/copyWithSet.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -function copyWithSetImpl(obj, path, idx, value) { - if (idx >= path.length) { - return value; - } - var key = path[idx]; - var updated = Array.isArray(obj) ? obj.slice() : {...obj}; - // $FlowFixMe number or string is fine here - updated[key] = copyWithSetImpl(obj[key], path, idx + 1, value); - return updated; -} - -function copyWithSet(obj: Object | Array, path: Array, value: any): Object | Array { - return copyWithSetImpl(obj, path, 0, value); -} - -module.exports = copyWithSet; diff --git a/backend/getData.js b/backend/getData.js deleted file mode 100644 index 49d031bda8..0000000000 --- a/backend/getData.js +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow - */ -'use strict'; - -import type {DataType} from './types'; -var copyWithSet = require('./copyWithSet'); -var getDisplayName = require('./getDisplayName'); - -/** - * Convert a react internal instance to a sanitized data object. - */ -function getData(element: Object): DataType { - var children = null; - var props = null; - var state = null; - var context = null; - var updater = null; - var name = null; - var type = null; - var key = null; - var ref = null; - var source = null; - var text = null; - var publicInstance = null; - var nodeType = 'Native'; - // If the parent is a native node without rendered children, but with - // multiple string children, then the `element` that gets passed in here is - // a plain value -- a string or number. - if (typeof element !== 'object') { - nodeType = 'Text'; - text = element + ''; - } else if (element._currentElement === null || element._currentElement === false) { - nodeType = 'Empty'; - } else if (element._renderedComponent) { - nodeType = 'NativeWrapper'; - children = [element._renderedComponent]; - props = element._instance.props; - state = element._instance.state; - context = element._instance.context; - if (context && Object.keys(context).length === 0) { - context = null; - } - } else if (element._renderedChildren) { - children = childrenList(element._renderedChildren); - } else if (element._currentElement && element._currentElement.props) { - // This is a native node without rendered children -- meaning the children - // prop is just a string or (in the case of the