Bug 1540792 - Show element highlighter when hovering an element in Scopes/Watch Expression/Popup r=nchevobbe
authorDavid Walsh <dwalsh@mozilla.com>
Thu, 04 Apr 2019 15:03:22 +0000
changeset 468061 9c82e3eccf65a88a196356d7076472c56b521aff
parent 468060 e2237e45cd4a440029e4e7ef5c398fd3b7c0cd5d
child 468062 156aba60571fb7910d1b46412b0f26c93419bfee
push id82362
push userdwalsh@mozilla.com
push dateThu, 04 Apr 2019 20:13:04 +0000
treeherderautoland@9c82e3eccf65 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnchevobbe
bugs1540792
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1540792 - Show element highlighter when hovering an element in Scopes/Watch Expression/Popup r=nchevobbe Differential Revision: https://phabricator.services.mozilla.com/D25635
devtools/client/debugger/new/panel.js
devtools/client/debugger/new/src/actions/toolbox.js
devtools/client/debugger/new/src/client/firefox/types.js
devtools/client/debugger/new/src/components/Editor/Preview/Popup.js
devtools/client/debugger/new/src/components/SecondaryPanes/Expressions.js
devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.js
devtools/client/debugger/new/src/components/SecondaryPanes/tests/__snapshots__/Expressions.spec.js.snap
devtools/client/debugger/new/src/main.development.js
devtools/client/debugger/new/test/mochitest/browser_dbg-inspector-integration.js
--- a/devtools/client/debugger/new/panel.js
+++ b/devtools/client/debugger/new/panel.js
@@ -94,16 +94,31 @@ DebuggerPanel.prototype = {
     const onInspectorUpdated = inspector.once("inspector-updated");
     const onNodeFrontSet = this.toolbox.selection.setNodeFront(front, {
       reason: "debugger"
     });
 
     return Promise.all([onNodeFrontSet, onInspectorUpdated]);
   },
 
+  highlightDomElement: async function(grip) {
+    await this.toolbox.initInspector();
+    if (!this.toolbox.highlighter) {
+      return null;
+    }
+    const nodeFront = await this.toolbox.walker.gripToNodeFront(grip);
+    return this.toolbox.highlighter.highlight(nodeFront);
+  },
+
+  unHighlightDomElement: function() {
+    return this.toolbox.highlighter
+      ? this.toolbox.highlighter.unhighlight(false)
+      : null;
+  },
+
   getFrames: function() {
     const thread = this._selectors.getCurrentThread(this._getState());
     const frames = this._selectors.getFrames(this._getState(), thread);
 
     // Frames is null when the debugger is not paused.
     if (!frames) {
       return {
         frames: [],
--- a/devtools/client/debugger/new/src/actions/toolbox.js
+++ b/devtools/client/debugger/new/src/actions/toolbox.js
@@ -29,8 +29,20 @@ export function evaluateInConsole(inputS
   };
 }
 
 export function openElementInInspectorCommand(grip: Grip) {
   return async ({ panel }: ThunkArgs) => {
     return panel.openElementInInspector(grip);
   };
 }
+
+export function highlightDomElement(grip: Grip) {
+  return async ({ panel }: ThunkArgs) => {
+    return panel.highlightDomElement(grip);
+  };
+}
+
+export function unHighlightDomElement(grip: Grip) {
+  return async ({ panel }: ThunkArgs) => {
+    return panel.unHighlightDomElement(grip);
+  };
+}
--- a/devtools/client/debugger/new/src/client/firefox/types.js
+++ b/devtools/client/debugger/new/src/client/firefox/types.js
@@ -359,10 +359,12 @@ export type ThreadClient = {
   skipBreakpoints: boolean => Promise<{| skip: boolean |}>
 };
 
 export type Panel = {|
   emit: (eventName: string) => void,
   openLink: (url: string) => void,
   openWorkerToolbox: (worker: Worker) => void,
   openElementInInspector: (grip: Object) => void,
-  openConsoleAndEvaluate: (input: string) => void
+  openConsoleAndEvaluate: (input: string) => void,
+  highlightDomElement: (grip: Object) => void,
+  unHighlightDomElement: (grip: Object) => void
 |};
--- a/devtools/client/debugger/new/src/components/Editor/Preview/Popup.js
+++ b/devtools/client/debugger/new/src/components/Editor/Preview/Popup.js
@@ -46,17 +46,19 @@ type Props = {
   onClose: () => void,
   range: EditorRange,
   editor: any,
   editorRef: ?HTMLDivElement,
   setPopupObjectProperties: typeof actions.setPopupObjectProperties,
   addExpression: typeof actions.addExpression,
   selectSourceURL: typeof actions.selectSourceURL,
   openLink: typeof actions.openLink,
-  openElementInInspector: typeof actions.openElementInInspectorCommand
+  openElementInInspector: typeof actions.openElementInInspectorCommand,
+  highlightDomElement: typeof actions.highlightDomElement,
+  unHighlightDomElement: typeof actions.unHighlightDomElement
 };
 
 type State = {
   top: number
 };
 
 function inPreview(event) {
   const relatedTarget: Element = (event.relatedTarget: any);
@@ -238,28 +240,35 @@ export class Popup extends Component<Pro
           mode: MODE.LONG,
           openLink
         })}
       </div>
     );
   }
 
   renderObjectInspector(roots: Array<Object>) {
-    const { openLink, openElementInInspector } = this.props;
+    const {
+      openLink,
+      openElementInInspector,
+      highlightDomElement,
+      unHighlightDomElement
+    } = this.props;
 
     return (
       <ObjectInspector
         roots={roots}
         autoExpandDepth={0}
         disableWrap={true}
         focusable={false}
         openLink={openLink}
         createObjectClient={grip => createObjectClient(grip)}
         onDOMNodeClick={grip => openElementInInspector(grip)}
         onInspectIconClick={grip => openElementInInspector(grip)}
+        onDOMNodeMouseOver={grip => highlightDomElement(grip)}
+        onDOMNodeMouseOut={grip => unHighlightDomElement(grip)}
       />
     );
   }
 
   renderPreview() {
     // We don't have to check and
     // return on `false`, `""`, `0`, `undefined` etc,
     // these falsy simple typed value because we want to
@@ -327,23 +336,27 @@ const mapStateToProps = state => ({
   )
 });
 
 const {
   addExpression,
   selectSourceURL,
   setPopupObjectProperties,
   openLink,
-  openElementInInspectorCommand
+  openElementInInspectorCommand,
+  highlightDomElement,
+  unHighlightDomElement
 } = actions;
 
 const mapDispatchToProps = {
   addExpression,
   selectSourceURL,
   setPopupObjectProperties,
   openLink,
-  openElementInInspector: openElementInInspectorCommand
+  openElementInInspector: openElementInInspectorCommand,
+  highlightDomElement,
+  unHighlightDomElement
 };
 
 export default connect(
   mapStateToProps,
   mapDispatchToProps
 )(Popup);
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/Expressions.js
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Expressions.js
@@ -44,17 +44,19 @@ type Props = {
   autocomplete: typeof actions.autocomplete,
   clearAutocomplete: typeof actions.clearAutocomplete,
   addExpression: typeof actions.addExpression,
   clearExpressionError: typeof actions.clearExpressionError,
   evaluateExpressions: typeof actions.evaluateExpressions,
   updateExpression: typeof actions.updateExpression,
   deleteExpression: typeof actions.deleteExpression,
   openLink: typeof actions.openLink,
-  openElementInInspector: typeof actions.openElementInInspectorCommand
+  openElementInInspector: typeof actions.openElementInInspectorCommand,
+  highlightDomElement: typeof actions.highlightDomElement,
+  unHighlightDomElement: typeof actions.unHighlightDomElement
 };
 
 class Expressions extends Component<Props, State> {
   _input: ?HTMLInputElement;
   renderExpression: (
     expression: Expression,
     index: number
   ) => React$Element<"li">;
@@ -210,17 +212,24 @@ class Expressions extends Component<Prop
     if (!this.props.expressionError) {
       this.hideInput();
     }
 
     this.props.clearAutocomplete();
   };
 
   renderExpression = (expression: Expression, index: number) => {
-    const { expressionError, openLink, openElementInInspector } = this.props;
+    const {
+      expressionError,
+      openLink,
+      openElementInInspector,
+      highlightDomElement,
+      unHighlightDomElement
+    } = this.props;
+
     const { editing, editIndex } = this.state;
     const { input, updating } = expression;
     const isEditingExpr = editing && editIndex === index;
     if (isEditingExpr || (isEditingExpr && expressionError)) {
       return this.renderExpressionEditInput(expression);
     }
 
     if (updating) {
@@ -248,16 +257,18 @@ class Expressions extends Component<Prop
           <ObjectInspector
             roots={[root]}
             autoExpandDepth={0}
             disableWrap={true}
             openLink={openLink}
             createObjectClient={grip => createObjectClient(grip)}
             onDOMNodeClick={grip => openElementInInspector(grip)}
             onInspectIconClick={grip => openElementInInspector(grip)}
+            onDOMNodeMouseOver={grip => highlightDomElement(grip)}
+            onDOMNodeMouseOut={grip => unHighlightDomElement(grip)}
           />
           <div className="expression-container__close-btn">
             <CloseButton
               handleClick={e => this.deleteExpression(e, expression)}
               tooltip={L10N.getStr("expressions.remove.tooltip")}
             />
           </div>
         </div>
@@ -378,11 +389,13 @@ export default connect(
     autocomplete: actions.autocomplete,
     clearAutocomplete: actions.clearAutocomplete,
     addExpression: actions.addExpression,
     clearExpressionError: actions.clearExpressionError,
     evaluateExpressions: actions.evaluateExpressions,
     updateExpression: actions.updateExpression,
     deleteExpression: actions.deleteExpression,
     openLink: actions.openLink,
-    openElementInInspector: actions.openElementInInspectorCommand
+    openElementInInspector: actions.openElementInInspectorCommand,
+    highlightDomElement: actions.highlightDomElement,
+    unHighlightDomElement: actions.unHighlightDomElement
   }
 )(Expressions);
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.js
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.js
@@ -34,16 +34,18 @@ type Props = {
   selectedFrame: Object,
   generatedFrameScopes: Object,
   originalFrameScopes: Object | null,
   isLoading: boolean,
   why: Why,
   shouldMapScopes: boolean,
   openLink: typeof actions.openLink,
   openElementInInspector: typeof actions.openElementInInspectorCommand,
+  highlightDomElement: typeof actions.highlightDomElement,
+  unHighlightDomElement: typeof actions.unHighlightDomElement,
   toggleMapScopes: typeof actions.toggleMapScopes
 };
 
 type State = {
   originalScopes: ?(NamedValue[]),
   generatedScopes: ?(NamedValue[]),
   showOriginal: boolean
 };
@@ -106,16 +108,18 @@ class Scopes extends PureComponent<Props
   };
 
   renderScopesList() {
     const {
       isPaused,
       isLoading,
       openLink,
       openElementInInspector,
+      highlightDomElement,
+      unHighlightDomElement,
       shouldMapScopes
     } = this.props;
     const { originalScopes, generatedScopes, showOriginal } = this.state;
 
     const scopes =
       (showOriginal && shouldMapScopes && originalScopes) || generatedScopes;
 
     if (scopes && !isLoading) {
@@ -126,16 +130,18 @@ class Scopes extends PureComponent<Props
             autoExpandAll={false}
             autoExpandDepth={1}
             disableWrap={true}
             dimTopLevelWindow={true}
             openLink={openLink}
             createObjectClient={grip => createObjectClient(grip)}
             onDOMNodeClick={grip => openElementInInspector(grip)}
             onInspectIconClick={grip => openElementInInspector(grip)}
+            onDOMNodeMouseOver={grip => highlightDomElement(grip)}
+            onDOMNodeMouseOut={grip => unHighlightDomElement(grip)}
           />
         </div>
       );
     }
 
     let stateText = L10N.getStr("scopes.notPaused");
     if (isPaused) {
       if (isLoading) {
@@ -195,11 +201,13 @@ const mapStateToProps = state => {
   };
 };
 
 export default connect(
   mapStateToProps,
   {
     openLink: actions.openLink,
     openElementInInspector: actions.openElementInInspectorCommand,
+    highlightDomElement: actions.highlightDomElement,
+    unHighlightDomElement: actions.unHighlightDomElement,
     toggleMapScopes: actions.toggleMapScopes
   }
 )(Scopes);
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/tests/__snapshots__/Expressions.spec.js.snap
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/tests/__snapshots__/Expressions.spec.js.snap
@@ -13,16 +13,18 @@ exports[`Expressions should always have 
     <div
       className="expression-content"
     >
       <Component
         autoExpandDepth={0}
         createObjectClient={[Function]}
         disableWrap={true}
         onDOMNodeClick={[Function]}
+        onDOMNodeMouseOut={[Function]}
+        onDOMNodeMouseOver={[Function]}
         onInspectIconClick={[Function]}
         roots={
           Array [
             Object {
               "contents": Object {
                 "value": Object {
                   "class": "",
                   "value": undefined,
@@ -53,16 +55,18 @@ exports[`Expressions should always have 
     <div
       className="expression-content"
     >
       <Component
         autoExpandDepth={0}
         createObjectClient={[Function]}
         disableWrap={true}
         onDOMNodeClick={[Function]}
+        onDOMNodeMouseOut={[Function]}
+        onDOMNodeMouseOver={[Function]}
         onInspectIconClick={[Function]}
         roots={
           Array [
             Object {
               "contents": Object {
                 "value": Object {
                   "class": "",
                   "value": undefined,
@@ -127,16 +131,18 @@ exports[`Expressions should render 1`] =
     <div
       className="expression-content"
     >
       <Component
         autoExpandDepth={0}
         createObjectClient={[Function]}
         disableWrap={true}
         onDOMNodeClick={[Function]}
+        onDOMNodeMouseOut={[Function]}
+        onDOMNodeMouseOver={[Function]}
         onInspectIconClick={[Function]}
         roots={
           Array [
             Object {
               "contents": Object {
                 "value": Object {
                   "class": "",
                   "value": "foo",
@@ -167,16 +173,18 @@ exports[`Expressions should render 1`] =
     <div
       className="expression-content"
     >
       <Component
         autoExpandDepth={0}
         createObjectClient={[Function]}
         disableWrap={true}
         onDOMNodeClick={[Function]}
+        onDOMNodeMouseOut={[Function]}
+        onDOMNodeMouseOver={[Function]}
         onInspectIconClick={[Function]}
         roots={
           Array [
             Object {
               "contents": Object {
                 "value": Object {
                   "class": "",
                   "value": "bar",
--- a/devtools/client/debugger/new/src/main.development.js
+++ b/devtools/client/debugger/new/src/main.development.js
@@ -20,11 +20,15 @@ bootstrap(React, ReactDOM).then(connecti
     emit: eventName => console.log(`emitted: ${eventName}`),
     openLink: url => {
       const win = window.open(url, "_blank");
       win.focus();
     },
     openWorkerToolbox: worker => alert(worker.url),
     openElementInInspector: grip =>
       alert(`Opening node in Inspector: ${grip.class}`),
-    openConsoleAndEvaluate: input => alert(`console.log: ${input}`)
+    openConsoleAndEvaluate: input => alert(`console.log: ${input}`),
+    highlightDomElement: (grip: Object) =>
+      console.log("highlighting dom element"),
+    unHighlightDomElement: (grip: Object) =>
+      console.log("unhighlighting dom element")
   });
 });
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-inspector-integration.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-inspector-integration.js
@@ -12,23 +12,38 @@ function waitForInspectorPanelChange(dbg
     toolbox.getPanelWhenReady("inspector").then(() => {
       ok(toolbox.inspector, "Inspector is shown.");
       resolve(toolbox.inspector);
     });
   });
 }
 
 add_task(async function() {
-  const dbg = await initDebugger("doc-script-switching.html");
+  // Ensures the end panel is wide enough to show the inspector icon
+  await pushPref("devtools.debugger.end-panel-size", 600);
 
-  await addExpression(dbg, "window.document.body.firstChild");
+  const dbg = await initDebugger("doc-script-switching.html");
+  const { toolbox } = dbg;
+
+  await addExpression(dbg, "window.document.querySelector('button')");
 
   await waitForElement(dbg, "openInspector");
-  findElement(dbg, "openInspector").click();
+
+  const inspectorNode = findElement(dbg, "openInspector");
 
+  // Ensure hovering over button highlights the node in content pane
+  const view = inspectorNode.ownerDocument.defaultView;
+  const onNodeHighlight = toolbox.target.once("inspector")
+    .then(inspector => inspector.highlighter.once("node-highlight"));
+  EventUtils.synthesizeMouseAtCenter(inspectorNode, {type: "mousemove"}, view);
+  const nodeFront = await onNodeHighlight;
+  is(nodeFront.displayName, "button", "The correct node was highlighted");
+
+  // Ensure panel changes when button is clicked
+  inspectorNode.click();
   await waitForInspectorPanelChange(dbg);
 });
 
 add_task(async function() {
   const dbg = await initDebugger("doc-event-handler.html");
 
   invokeInTab("synthesizeClick");
   await waitForPaused(dbg);