Bug 1446572 - Adjust node inspection for toolbox's selected frame. r=pbro a=RyanVM
authorJ. Ryan Stinnett <jryans@gmail.com>
Wed, 28 Mar 2018 19:54:43 -0500
changeset 462986 85da1c81b9f47c0d78c7b16ba877bc3675d6021c
parent 462985 5bfafbfd63e116d1e147a362da8c6fa76521d963
child 462987 7289d6c5c778cca8e6c3cc9dbb4c98b8d0992c4a
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspbro, RyanVM
bugs1446572
milestone60.0
Bug 1446572 - Adjust node inspection for toolbox's selected frame. r=pbro a=RyanVM When inspecting a node with the toolbox focused on an inner frame, we need to adjust the selectors used to remove hidden parent frames. MozReview-Commit-ID: CXwb3FmnJFO
devtools/client/framework/devtools.js
devtools/client/framework/toolbox.js
devtools/client/inspector/test/browser_inspector_inspect_node_contextmenu.js
--- a/devtools/client/framework/devtools.js
+++ b/devtools/client/framework/devtools.js
@@ -622,16 +622,20 @@ DevTools.prototype = {
    *         markup view.
    */
   async inspectNode(tab, nodeSelectors, startTime) {
     let target = TargetFactory.forTab(tab);
 
     let toolbox = await gDevTools.showToolbox(target, "inspector", null, null, startTime);
     let inspector = toolbox.getCurrentPanel();
 
+    // If the toolbox has been switched into a nested frame, we should first remove
+    // selectors according to the frame depth.
+    nodeSelectors.splice(0, toolbox.selectedFrameDepth);
+
     // new-node-front tells us when the node has been selected, whether the
     // browser is remote or not.
     let onNewNode = inspector.selection.once("new-node-front");
 
     // Evaluate the cross iframes query selectors
     async function querySelectors(nodeFront) {
       let selector = nodeSelectors.shift();
       if (!selector) {
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -2283,18 +2283,17 @@ Toolbox.prototype = {
     this._target.client.request(packet);
   },
 
   /**
    * Highlight a frame in the page
    */
   onHightlightFrame: async function (frameId) {
     // Only enable frame highlighting when the top level document is targeted
-    if (this._supportsFrameHighlight &&
-        this.frameMap.get(this.selectedFrameId).parentID === undefined) {
+    if (this._supportsFrameHighlight && this.rootFrameSelected) {
       let frameActor = await this.walker.getNodeActorFromWindowID(frameId);
       this.highlighterUtils.highlightNodeFront(frameActor);
     }
   },
 
   /**
    * A handler for 'frameUpdate' packets received from the backend.
    * Following properties might be set on the packet:
@@ -2358,16 +2357,44 @@ Toolbox.prototype = {
     // If non-top level frame is selected the toolbar button is
     // marked as 'checked' indicating that a child frame is active.
     if (!topFrameSelected && this.selectedFrameId) {
       this._framesButtonChecked = false;
     }
   },
 
   /**
+   * Returns a 0-based selected frame depth.
+   *
+   * For example, if the root frame is selected, the returned value is 0.  For a sub-frame
+   * of the root document, the returned value is 1, and so on.
+   */
+  get selectedFrameDepth() {
+    // If the frame switcher is disabled, we won't have a selected frame ID.
+    // In this case, we're always showing the root frame.
+    if (!this.selectedFrameId) {
+      return 0;
+    }
+    let depth = 0;
+    let frame = this.frameMap.get(this.selectedFrameId);
+    while (frame) {
+      depth++;
+      frame = this.frameMap.get(frame.parentID);
+    }
+    return depth - 1;
+  },
+
+  /**
+   * Returns whether a root frame (with no parent frame) is selected.
+   */
+  get rootFrameSelected() {
+    return this.selectedFrameDepth == 0;
+  },
+
+  /**
    * Switch to the last used host for the toolbox UI.
    */
   switchToPreviousHost: function () {
     return this.switchHost("previous");
   },
 
   /**
    * Switch to a new host for the toolbox UI. E.g. bottom, sidebar, window,
--- a/devtools/client/inspector/test/browser_inspector_inspect_node_contextmenu.js
+++ b/devtools/client/inspector/test/browser_inspector_inspect_node_contextmenu.js
@@ -1,38 +1,73 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 /* globals getTestActorWithoutToolbox */
+
 "use strict";
 
 // Tests for inspect node in browser context menu
 
 const FRAME_URI = "data:text/html;charset=utf-8," +
   encodeURI(`<div id="in-frame">div in the iframe</div>`);
 const HTML = `
   <div id="salutation">Salution in top document</div>
   <iframe src="${FRAME_URI}"></iframe>
 `;
 
 const TEST_URI = "data:text/html;charset=utf-8," + encodeURI(HTML);
 
-add_task(function* () {
-  let tab = yield addTab(TEST_URI);
-  let testActor = yield getTestActorWithoutToolbox(tab);
+add_task(async function () {
+  Services.prefs.setBoolPref("devtools.command-button-frames.enabled", true);
+  registerCleanupFunction(() => {
+    Services.prefs.clearUserPref("devtools.command-button-frames.enabled");
+  });
+
+  let tab = await addTab(TEST_URI);
+  let testActor = await getTestActorWithoutToolbox(tab);
 
-  yield testContextMenuWithinIframe(testActor);
+  // Use context menu with root frame selected in toolbox
+  await testContextMenuWithinIframe(testActor, async inspector => {
+    return getNodeFrontInFrame("#in-frame", "iframe", inspector);
+  });
+
+  // Use context menu with inner frame selected in toolbox
+  await changeToolboxToInnerFrame();
+  await testContextMenuWithinIframe(testActor, async inspector => {
+    return getNodeFront("#in-frame", inspector);
+  });
 });
 
-function* testContextMenuWithinIframe(testActor) {
+async function testContextMenuWithinIframe(testActor, nodeFrontGetter) {
   info("Opening inspector via 'Inspect Element' context menu item within an iframe");
   let selector = ["iframe", "#in-frame"];
-  yield clickOnInspectMenuItem(testActor, selector);
+  await clickOnInspectMenuItem(testActor, selector);
 
   info("Checking inspector state.");
   let inspector = getActiveInspector();
-  let nodeFront = yield getNodeFrontInFrame("#in-frame", "iframe", inspector);
+  let nodeFront = await nodeFrontGetter(inspector);
 
   is(inspector.selection.nodeFront, nodeFront,
      "Right node is selected in the markup view");
 }
+
+async function changeToolboxToInnerFrame() {
+  let { toolbox } = getActiveInspector();
+
+  let frameButton = toolbox.doc.getElementById("command-button-frames");
+  let menu = await toolbox.showFramesMenu({
+    target: frameButton
+  });
+  await once(menu, "open");
+
+  let frames = menu.items;
+  is(frames.length, 2, "Two frames shown in the switcher");
+
+  let innerFrameButton = frames.filter(f => f.label == FRAME_URI)[0];
+  ok(innerFrameButton, "Found frame button for inner frame");
+
+  let newRoot = toolbox.getPanel("inspector").once("new-root");
+  info("Switch toolbox to inner frame");
+  innerFrameButton.click();
+  await newRoot;
+}