diff --git a/src/backend/agent.js b/src/backend/agent.js
index 185c1d34a1..4876921c78 100644
--- a/src/backend/agent.js
+++ b/src/backend/agent.js
@@ -28,10 +28,21 @@ export default class Agent extends EventEmitter {
bridge.addListener('highlightElementInDOM', this.highlightElementInDOM);
bridge.addListener('inspectElement', this.inspectElement);
bridge.addListener('selectElement', this.selectElement);
+ bridge.addListener('startInspectingDOM', this.startInspectingDOM);
+ bridge.addListener('stopInspectingDOM', this.stopInspectingDOM);
bridge.addListener('shutdown', this.shutdown);
+ }
- // TODO Listen to bridge for things like selection.
- // bridge.addListener('...'), this...);
+ getIDForNode(node: Object): number | null {
+ for (let rendererID in this._rendererInterfaces) {
+ // A renderer will throw if it can't find a fiber for the specified node.
+ try {
+ // $FlowFixMe
+ const renderer = this._rendererInterfaces[rendererID];
+ return renderer.getFiberIDFromNative(node, true);
+ } catch (e) {}
+ }
+ return null;
}
highlightElementInDOM = ({
@@ -98,6 +109,20 @@ export default class Agent extends EventEmitter {
this.emit('shutdown');
};
+ startInspectingDOM = () => {
+ window.addEventListener('click', this._onClick);
+ window.addEventListener('mousedown', this._onMouseDown);
+ window.addEventListener('mouseover', this._onMouseOver);
+ };
+
+ stopInspectingDOM = () => {
+ hideOverlay();
+
+ window.removeEventListener('click', this._onClick);
+ window.removeEventListener('mousedown', this._onMouseDown);
+ window.removeEventListener('mouseover', this._onMouseOver);
+ };
+
onHookOperations = (operations: Uint32Array) => {
debug('onHookOperations', operations);
@@ -107,4 +132,33 @@ export default class Agent extends EventEmitter {
// this._bridge.send('operations', operations, [operations.buffer]);
this._bridge.send('operations', operations);
};
+
+ _onClick = (event: MouseEvent) => {
+ event.preventDefault();
+ event.stopPropagation();
+
+ this.stopInspectingDOM();
+ this._bridge.send('stopInspectingDOM');
+ };
+
+ _onMouseDown = (event: MouseEvent) => {
+ event.preventDefault();
+ event.stopPropagation();
+
+ const target = ((event.target: any): HTMLElement);
+ const id = this.getIDForNode(target);
+
+ if (id !== null) {
+ this._bridge.send('selectFiber', id);
+ }
+ };
+
+ _onMouseOver = (event: MouseEvent) => {
+ event.preventDefault();
+ event.stopPropagation();
+
+ const target = ((event.target: any): HTMLElement);
+
+ showOverlay(target, target.tagName.toLowerCase());
+ };
}
diff --git a/src/backend/renderer.js b/src/backend/renderer.js
index 0fcb9e3a46..8114e2a898 100644
--- a/src/backend/renderer.js
+++ b/src/backend/renderer.js
@@ -826,11 +826,18 @@ export function attach(
return null;
}
}
- function getReactElementFromNative(hostInstance) {
- const fiber = renderer.findFiberByHostInstance(hostInstance);
+ function getFiberIDFromNative(
+ hostInstance,
+ findNearestUnfilteredAncestor = false
+ ) {
+ let fiber = renderer.findFiberByHostInstance(hostInstance);
if (fiber != null) {
- const primaryFiber = getPrimaryFiber(((fiber: any): Fiber));
- return primaryFiber;
+ if (findNearestUnfilteredAncestor) {
+ while (fiber !== null && shouldFilterFiber(fiber)) {
+ fiber = fiber.return;
+ }
+ }
+ return getFiberID(getPrimaryFiber(((fiber: any): Fiber)));
}
return null;
}
@@ -1150,8 +1157,8 @@ export function attach(
}
return {
+ getFiberIDFromNative,
getNativeFromReactElement,
- getReactElementFromNative,
handleCommitFiberRoot,
handleCommitFiberUnmount,
inspectElement,
diff --git a/src/backend/types.js b/src/backend/types.js
index 64305c5952..f9dbbe0e49 100644
--- a/src/backend/types.js
+++ b/src/backend/types.js
@@ -46,7 +46,10 @@ export type ReactRenderer = {
export type RendererInterface = {
cleanup: () => void,
getNativeFromReactElement?: ?(component: Fiber) => ?NativeType,
- getReactElementFromNative?: ?(component: NativeType) => ?Fiber,
+ getFiberIDFromNative: (
+ component: NativeType,
+ findNearestUnfilteredAncestor?: boolean
+ ) => number | null,
handleCommitFiberRoot: (fiber: Object) => void,
handleCommitFiberUnmount: (fiber: Object) => void,
inspectElement: (id: number) => InspectedElement | null,
diff --git a/src/devtools/store.js b/src/devtools/store.js
index d968e1d4a2..511812430d 100644
--- a/src/devtools/store.js
+++ b/src/devtools/store.js
@@ -130,7 +130,7 @@ export default class Store extends EventEmitter {
const element = this._idToElement.get(id);
if (element == null) {
- console.warn(`No element found with id "${id}`);
+ console.warn(`No element found with id "${id}"`);
return null;
}
diff --git a/src/devtools/views/InspectHostNodesToggle.js b/src/devtools/views/InspectHostNodesToggle.js
new file mode 100644
index 0000000000..2546059cd9
--- /dev/null
+++ b/src/devtools/views/InspectHostNodesToggle.js
@@ -0,0 +1,41 @@
+// @flow
+
+import React, { useCallback, useContext, useEffect, useState } from 'react';
+import { BridgeContext } from './context';
+import Toggle from './Toggle';
+import ButtonIcon from './ButtonIcon';
+
+export default function InspectHostNodesToggle() {
+ const [isInspecting, setIsInspecting] = useState(false);
+ const bridge = useContext(BridgeContext);
+
+ const handleChange = useCallback(
+ (isChecked: boolean) => {
+ setIsInspecting(isChecked);
+
+ if (isChecked) {
+ bridge.send('startInspectingDOM');
+ } else {
+ bridge.send('stopInspectingDOM');
+ }
+ },
+ [bridge]
+ );
+
+ useEffect(() => {
+ const onStopInspectingDOM = () => setIsInspecting(false);
+ bridge.addListener('stopInspectingDOM', onStopInspectingDOM);
+ return () =>
+ bridge.removeListener('stopInspectingDOM', onStopInspectingDOM);
+ }, [bridge]);
+
+ return (
+