diff --git a/src/renderers/native/ReactNativeFiber.js b/src/renderers/native/ReactNativeFiber.js index 79a4acac1804c..b8e59bfe9f7d9 100644 --- a/src/renderers/native/ReactNativeFiber.js +++ b/src/renderers/native/ReactNativeFiber.js @@ -23,6 +23,7 @@ const ReactNativeInjection = require('ReactNativeInjection'); const ReactNativeTagHandles = require('ReactNativeTagHandles'); const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry'); const ReactPortal = require('ReactPortal'); +const ReactNativeFiberInspector = require('ReactNativeFiberInspector'); const ReactVersion = require('ReactVersion'); const UIManager = require('UIManager'); @@ -465,6 +466,7 @@ if (typeof injectInternals === 'function') { injectInternals({ findFiberByHostInstance: ReactNativeComponentTree.getClosestInstanceFromNode, findHostInstanceByFiber: NativeRenderer.findHostInstance, + getInspectorDataForViewTag: ReactNativeFiberInspector.getInspectorDataForViewTag, // This is an enum because we may add more (e.g. profiler build) bundleType: __DEV__ ? 1 : 0, version: ReactVersion, diff --git a/src/renderers/native/ReactNativeFiberInspector.js b/src/renderers/native/ReactNativeFiberInspector.js new file mode 100644 index 0000000000000..f4ad2b6e2e2c7 --- /dev/null +++ b/src/renderers/native/ReactNativeFiberInspector.js @@ -0,0 +1,124 @@ +/** + * 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. + * + * @providesModule ReactNativeFiberInspector + * @flow + */ +'use strict'; + +const ReactNativeComponentTree = require('ReactNativeComponentTree'); +const ReactFiberTreeReflection = require('ReactFiberTreeReflection'); +const getComponentName = require('getComponentName'); +const emptyObject = require('fbjs/lib/emptyObject'); +const ReactTypeOfWork = require('ReactTypeOfWork'); +const UIManager = require('UIManager'); +const invariant = require('fbjs/lib/invariant'); + +const {getClosestInstanceFromNode} = ReactNativeComponentTree; +const {findCurrentFiberUsingSlowPath} = ReactFiberTreeReflection; +const {HostComponent} = ReactTypeOfWork; + +import type {Fiber} from 'ReactFiber'; + +let getInspectorDataForViewTag; + +if (__DEV__) { + var traverseOwnerTreeUp = function(hierarchy, instance: any) { + if (instance) { + hierarchy.unshift(instance); + traverseOwnerTreeUp(hierarchy, instance._debugOwner); + } + }; + + var getOwnerHierarchy = function(instance: any) { + var hierarchy = []; + traverseOwnerTreeUp(hierarchy, instance); + return hierarchy; + }; + + var lastNonHostInstance = function(hierarchy) { + for (let i = hierarchy.length - 1; i > 1; i--) { + const instance = hierarchy[i]; + + if (instance.tag !== HostComponent) { + return instance; + } + } + return hierarchy[0]; + }; + + var getHostProps = function(fiber) { + const host = ReactFiberTreeReflection.findCurrentHostFiber(fiber); + if (host) { + return host.memoizedProps || emptyObject; + } + return emptyObject; + }; + + var getHostNode = function(fiber: Fiber | null, findNodeHandle) { + let hostNode; + // look for children first for the hostNode + // as composite fibers do not have a hostNode + while (fiber) { + if (fiber.stateNode !== null && fiber.tag === HostComponent) { + hostNode = findNodeHandle(fiber.stateNode); + } + if (hostNode) { + return hostNode; + } + fiber = fiber.child; + } + return null; + }; + + var stripTopSecret = str => + typeof str === 'string' && str.replace('topsecret-', ''); + + var createHierarchy = function(fiberHierarchy) { + return fiberHierarchy.map(fiber => ({ + name: stripTopSecret(getComponentName(fiber)), + getInspectorData: findNodeHandle => ({ + measure: callback => + UIManager.measure(getHostNode(fiber, findNodeHandle), callback), + props: getHostProps(fiber), + source: fiber._debugSource, + }), + })); + }; + + getInspectorDataForViewTag = function(viewTag: number): Object { + const fiber = findCurrentFiberUsingSlowPath( + getClosestInstanceFromNode(viewTag), + ); + const fiberHierarchy = getOwnerHierarchy(fiber); + const instance = lastNonHostInstance(fiberHierarchy); + const hierarchy = createHierarchy(fiberHierarchy); + const props = getHostProps(instance); + const source = instance._debugSource; + const selection = fiberHierarchy.indexOf(instance); + + return { + hierarchy, + instance, + props, + selection, + source, + }; + }; +} else { + getInspectorDataForViewTag = () => { + invariant( + false, + 'getInspectorDataForViewTag() is not available in production', + ); + }; +} + +module.exports = { + getInspectorDataForViewTag, +}; diff --git a/src/renderers/native/ReactNativeStack.js b/src/renderers/native/ReactNativeStack.js index a9fa118cf27b6..e3ca928834e93 100644 --- a/src/renderers/native/ReactNativeStack.js +++ b/src/renderers/native/ReactNativeStack.js @@ -16,6 +16,7 @@ var ReactNativeInjection = require('ReactNativeInjection'); var ReactNativeMount = require('ReactNativeMount'); var ReactNativeStackInjection = require('ReactNativeStackInjection'); var ReactUpdates = require('ReactUpdates'); +var ReactNativeStackInspector = require('ReactNativeStackInspector'); var findNodeHandle = require('findNodeHandle'); @@ -81,6 +82,7 @@ if ( }, Mount: ReactNativeMount, Reconciler: require('ReactReconciler'), + getInspectorDataForViewTag: ReactNativeStackInspector.getInspectorDataForViewTag, }); } diff --git a/src/renderers/native/ReactNativeStackInspector.js b/src/renderers/native/ReactNativeStackInspector.js new file mode 100644 index 0000000000000..42ecbf7d5398a --- /dev/null +++ b/src/renderers/native/ReactNativeStackInspector.js @@ -0,0 +1,96 @@ +/** + * 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. + * + * @providesModule ReactNativeStackInspector + * @flow + */ +'use strict'; + +const ReactNativeComponentTree = require('ReactNativeComponentTree'); +const getComponentName = require('getComponentName'); +const emptyObject = require('fbjs/lib/emptyObject'); +const UIManager = require('UIManager'); +const invariant = require('fbjs/lib/invariant'); + +let getInspectorDataForViewTag; + +if (__DEV__) { + var traverseOwnerTreeUp = function(hierarchy, instance) { + if (instance) { + hierarchy.unshift(instance); + traverseOwnerTreeUp(hierarchy, instance._currentElement._owner); + } + }; + + var getOwnerHierarchy = function(instance) { + var hierarchy = []; + traverseOwnerTreeUp(hierarchy, instance); + return hierarchy; + }; + + var lastNotNativeInstance = function(hierarchy) { + for (let i = hierarchy.length - 1; i > 1; i--) { + const instance = hierarchy[i]; + if (!instance.viewConfig) { + return instance; + } + } + return hierarchy[0]; + }; + + var getHostProps = function(component) { + const instance = component._instance; + if (instance) { + return instance.props || emptyObject; + } + return emptyObject; + }; + + var createHierarchy = function(componentHierarchy) { + return componentHierarchy.map(component => ({ + name: getComponentName(component), + getInspectorData: () => ({ + measure: callback => + UIManager.measure(component.getHostNode(), callback), + props: getHostProps(component), + source: component._currentElement && component._currentElement._source, + }), + })); + }; + + getInspectorDataForViewTag = function(viewTag: any): Object { + const component = ReactNativeComponentTree.getClosestInstanceFromNode( + viewTag, + ); + const componentHierarchy = getOwnerHierarchy(component); + const instance = lastNotNativeInstance(componentHierarchy); + const hierarchy = createHierarchy(componentHierarchy); + const props = getHostProps(instance); + const source = instance._currentElement && instance._currentElement._source; + const selection = componentHierarchy.indexOf(instance); + + return { + hierarchy, + instance, + props, + selection, + source, + }; + }; +} else { + getInspectorDataForViewTag = () => { + invariant( + false, + 'getInspectorDataForViewTag() is not available in production', + ); + }; +} + +module.exports = { + getInspectorDataForViewTag, +};