Bug 1132475 - Refactor LayoutHelpers.jsm to avoid dependencies. r=pbrosset
authorMatteo Ferretti <mferretti@mozilla.com>
Tue, 15 Sep 2015 00:32:00 -0400
changeset 295240 e8029818643a8a419b392fba4a4a5a3d4efc7f4e
parent 295239 4121aaa473b8a26dba48798042c51904db1f82f7
child 295241 cb711870d2b8d52f51b18e68c4d4cd3f9925bf53
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspbrosset
bugs1132475
milestone43.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 1132475 - Refactor LayoutHelpers.jsm to avoid dependencies. r=pbrosset
browser/devtools/framework/selection.js
browser/devtools/inspector/test/browser_inspector_highlighter-csstransform_02.js
browser/devtools/markupview/markup-view.js
browser/devtools/shared/test/browser_layoutHelpers-getBoxQuads.js
browser/devtools/shared/test/browser_layoutHelpers.js
browser/devtools/shared/test/test-actor.js
browser/devtools/shared/widgets/Graphs.js
browser/devtools/tilt/test/browser_tilt_utils05.js
browser/devtools/tilt/test/browser_tilt_utils07.js
browser/devtools/tilt/test/head.js
browser/devtools/tilt/tilt-utils.js
docshell/base/nsDocShell.cpp
docshell/base/nsIDocShell.idl
toolkit/devtools/gcli/commands/screenshot.js
toolkit/devtools/layout-helpers.js
toolkit/devtools/layout/utils.js
toolkit/devtools/moz.build
toolkit/devtools/server/actors/highlighter.js
toolkit/devtools/server/actors/inspector.js
toolkit/devtools/server/actors/storage.js
toolkit/devtools/styleinspector/css-logic.js
toolkit/devtools/webconsole/utils.js
--- a/browser/devtools/framework/selection.js
+++ b/browser/devtools/framework/selection.js
@@ -1,19 +1,20 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript 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/. */
 
 "use strict";
 
-const {Cu, Ci} = require("chrome");
+const { Cu, Ci } = require("chrome");
+const { getRootBindingParent } = require("devtools/toolkit/layout/utils");
+
 let EventEmitter = require("devtools/toolkit/event-emitter");
-let LayoutHelpers = require("devtools/toolkit/layout-helpers");
 
 /**
  * API
  *
  *   new Selection(walker=null, node=null, track={attributes,detached});
  *   destroy()
  *   node (readonly)
  *   setNode(node, origin="unknown")
@@ -223,17 +224,17 @@ Selection.prototype = {
     if (node.isLocal_toBeDeprecated()) {
       rawNode = node.rawNode();
     }
     if (rawNode) {
       try {
         let doc = this.document;
         if (doc && doc.defaultView) {
           let docEl = doc.documentElement;
-          let bindingParent = LayoutHelpers.getRootBindingParent(rawNode);
+          let bindingParent = getRootBindingParent(rawNode);
 
           if (docEl.contains(bindingParent)) {
             return true;
           }
         }
       } catch (e) {
         // "can't access dead object" error
       }
--- a/browser/devtools/inspector/test/browser_inspector_highlighter-csstransform_02.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-csstransform_02.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 /*
 Bug 1014547 - CSS transforms highlighter
 Test that the highlighter elements created have the right size and coordinates.
 
 Note that instead of hard-coding values here, the assertions are made by
-comparing with the result of LayoutHelpers.getAdjustedQuads.
+comparing with the result of getAdjustedQuads.
 
 There's a separate test for checking that getAdjustedQuads actually returns
 sensible values
 (browser/devtools/shared/test/browser_layoutHelpers-getBoxQuads.js),
 so the present test doesn't care about that, it just verifies that the css
 transform highlighter applies those values correctly to the SVG elements
 */
 
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -27,17 +27,17 @@ const {HTMLEditor} = require("devtools/m
 const promise = require("promise");
 const {Tooltip} = require("devtools/shared/widgets/Tooltip");
 const EventEmitter = require("devtools/toolkit/event-emitter");
 const Heritage = require("sdk/core/heritage");
 const {setTimeout, clearTimeout, setInterval, clearInterval} = require("sdk/timers");
 const {parseAttribute} = require("devtools/shared/node-attribute-parser");
 const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
 const {Task} = require("resource://gre/modules/Task.jsm");
-const LayoutHelpers = require("devtools/toolkit/layout-helpers");
+const {scrollIntoViewIfNeeded} = require("devtools/toolkit/layout/utils");
 
 Cu.import("resource://gre/modules/devtools/Templater.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 loader.lazyGetter(this, "DOMParser", function() {
  return Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
 });
@@ -70,18 +70,16 @@ function MarkupView(aInspector, aFrame, 
   this._inspector = aInspector;
   this.walker = this._inspector.walker;
   this._frame = aFrame;
   this.win = this._frame.contentWindow;
   this.doc = this._frame.contentDocument;
   this._elt = this.doc.querySelector("#root");
   this.htmlEditor = new HTMLEditor(this.doc);
 
-  this.layoutHelpers = new LayoutHelpers(this.doc.defaultView);
-
   try {
     this.maxChildren = Services.prefs.getIntPref("devtools.markup.pagesize");
   } catch(ex) {
     this.maxChildren = DEFAULT_MAX_CHILDREN;
   }
 
   // Creating the popup to be used to show CSS suggestions.
   let options = {
@@ -935,17 +933,17 @@ MarkupView.prototype = {
     }
 
     return this._waitForChildren().then(() => {
       if (this._destroyer) {
         return promise.reject("markupview destroyed");
       }
       return this._ensureVisible(aNode);
     }).then(() => {
-      this.layoutHelpers.scrollIntoViewIfNeeded(this.getContainer(aNode).editor.elt, centered);
+      scrollIntoViewIfNeeded(this.getContainer(aNode).editor.elt, centered);
     }, e => {
       // Only report this rejection as an error if the panel hasn't been
       // destroyed in the meantime.
       if (!this._destroyer) {
         console.error(e);
       } else {
         console.warn("Could not show the node, the markup-view was destroyed " +
           "while waiting for children");
--- a/browser/devtools/shared/test/browser_layoutHelpers-getBoxQuads.js
+++ b/browser/devtools/shared/test/browser_layoutHelpers-getBoxQuads.js
@@ -1,50 +1,47 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// Tests that LayoutHelpers.getAdjustedQuads works properly in a variety of use
-// cases including iframes, scroll and zoom
+// Tests getAdjustedQuads works properly in a variety of use cases including
+// iframes, scroll and zoom
 
 const {utils: Cu} = Components;
-const LayoutHelpers = require("devtools/toolkit/layout-helpers");
+let {getAdjustedQuads} = require("devtools/toolkit/layout/utils");
 
 const TEST_URI = TEST_URI_ROOT + "browser_layoutHelpers-getBoxQuads.html";
 
 function test() {
   addTab(TEST_URI, function(browser, tab) {
     let doc = browser.contentDocument;
-    let win = doc.defaultView;
 
-    info("Creating a new LayoutHelpers instance for the test window");
-    let helper = new LayoutHelpers(win);
-    ok(helper.getAdjustedQuads, "getAdjustedQuads is defined");
+    ok(typeof getAdjustedQuads === "function", "getAdjustedQuads is defined");
 
     info("Running tests");
 
-    returnsTheRightDataStructure(doc, helper);
-    isEmptyForMissingNode(doc, helper);
-    isEmptyForHiddenNodes(doc, helper);
-    defaultsToBorderBoxIfNoneProvided(doc, helper);
-    returnsLikeGetBoxQuadsInSimpleCase(doc, helper);
-    takesIframesOffsetsIntoAccount(doc, helper);
-    takesScrollingIntoAccount(doc, helper);
-    takesZoomIntoAccount(doc, helper);
-    returnsMultipleItemsForWrappingInlineElements(doc, helper);
+    returnsTheRightDataStructure(doc);
+    isEmptyForMissingNode(doc);
+    isEmptyForHiddenNodes(doc);
+    defaultsToBorderBoxIfNoneProvided(doc);
+    returnsLikeGetBoxQuadsInSimpleCase(doc);
+    takesIframesOffsetsIntoAccount(doc);
+    takesScrollingIntoAccount(doc);
+    takesZoomIntoAccount(doc);
+    returnsMultipleItemsForWrappingInlineElements(doc);
 
     gBrowser.removeCurrentTab();
     finish();
   });
 }
 
-function returnsTheRightDataStructure(doc, helper) {
+function returnsTheRightDataStructure(doc) {
   info("Checks that the returned data contains bounds and 4 points");
 
   let node = doc.querySelector("body");
-  let [res] = helper.getAdjustedQuads(node, "content");
+  let [res] = getAdjustedQuads(doc.defaultView, node, "content");
 
   ok("bounds" in res, "The returned data has a bounds property");
   ok("p1" in res, "The returned data has a p1 property");
   ok("p2" in res, "The returned data has a p2 property");
   ok("p3" in res, "The returned data has a p3 property");
   ok("p4" in res, "The returned data has a p4 property");
 
   for (let boundProp of
@@ -54,70 +51,70 @@ function returnsTheRightDataStructure(do
 
   for (let point of ["p1", "p2", "p3", "p4"]) {
     for (let pointProp of ["x", "y", "z", "w"]) {
       ok(pointProp in res[point], point + " has a " + pointProp + " property");
     }
   }
 }
 
-function isEmptyForMissingNode(doc, helper) {
+function isEmptyForMissingNode(doc) {
   info("Checks that null is returned for invalid nodes");
 
   for (let input of [null, undefined, "", 0]) {
-    is(helper.getAdjustedQuads(input).length, 0, "A 0-length array is returned" +
+    is(getAdjustedQuads(doc.defaultView, input).length, 0, "A 0-length array is returned" +
       "for input " + input);
   }
 }
 
-function isEmptyForHiddenNodes(doc, helper) {
+function isEmptyForHiddenNodes(doc) {
   info("Checks that null is returned for nodes that aren't rendered");
 
   let style = doc.querySelector("#styles");
-  is(helper.getAdjustedQuads(style).length, 0,
+  is(getAdjustedQuads(doc.defaultView, style).length, 0,
     "null is returned for a <style> node");
 
   let hidden = doc.querySelector("#hidden-node");
-  is(helper.getAdjustedQuads(hidden).length, 0,
+  is(getAdjustedQuads(doc.defaultView, hidden).length, 0,
     "null is returned for a hidden node");
 }
 
-function defaultsToBorderBoxIfNoneProvided(doc, helper) {
+function defaultsToBorderBoxIfNoneProvided(doc) {
   info("Checks that if no boxtype is passed, then border is the default one");
 
   let node = doc.querySelector("#simple-node-with-margin-padding-border");
-  let [withBoxType] = helper.getAdjustedQuads(node, "border");
-  let [withoutBoxType] = helper.getAdjustedQuads(node);
+  let [withBoxType] = getAdjustedQuads(doc.defaultView, node, "border");
+  let [withoutBoxType] = getAdjustedQuads(doc.defaultView, node);
 
   for (let boundProp of
     ["bottom", "top", "right", "left", "width", "height", "x", "y"]) {
     is(withBoxType.bounds[boundProp], withoutBoxType.bounds[boundProp],
       boundProp + " bound is equal with or without the border box type");
   }
 
   for (let point of ["p1", "p2", "p3", "p4"]) {
     for (let pointProp of ["x", "y", "z", "w"]) {
       is(withBoxType[point][pointProp], withoutBoxType[point][pointProp],
         point + "." + pointProp +
         " is equal with or without the border box type");
     }
   }
 }
 
-function returnsLikeGetBoxQuadsInSimpleCase(doc, helper) {
+function returnsLikeGetBoxQuadsInSimpleCase(doc) {
   info("Checks that for an element in the main frame, without scroll nor zoom" +
     "that the returned value is similar to the returned value of getBoxQuads");
 
   let node = doc.querySelector("#simple-node-with-margin-padding-border");
 
   for (let region of ["content", "padding", "border", "margin"]) {
     let expected = node.getBoxQuads({
       box: region
     })[0];
-    let [actual] = helper.getAdjustedQuads(node, region);
+    let [actual] = getAdjustedQuads(doc.defaultView, node, region);
 
     for (let boundProp of
       ["bottom", "top", "right", "left", "width", "height", "x", "y"]) {
       is(actual.bounds[boundProp], expected.bounds[boundProp],
         boundProp + " bound is equal to the one returned by getBoxQuads for " +
         region + " box");
     }
 
@@ -126,97 +123,97 @@ function returnsLikeGetBoxQuadsInSimpleC
         is(actual[point][pointProp], expected[point][pointProp],
           point + "." + pointProp +
           " is equal to the one returned by getBoxQuads for " + region + " box");
       }
     }
   }
 }
 
-function takesIframesOffsetsIntoAccount(doc, helper) {
+function takesIframesOffsetsIntoAccount(doc) {
   info("Checks that the quad returned for a node inside iframes that have " +
     "margins takes those offsets into account");
 
   let rootIframe = doc.querySelector("iframe");
   let subIframe = rootIframe.contentDocument.querySelector("iframe");
   let innerNode = subIframe.contentDocument.querySelector("#inner-node");
 
-  let [quad] = helper.getAdjustedQuads(innerNode, "content");
+  let [quad] = getAdjustedQuads(doc.defaultView, innerNode, "content");
 
   //rootIframe margin + subIframe margin + node margin + node border + node padding
   let p1x = 10 + 10 + 10 + 10 + 10;
   is(quad.p1.x, p1x, "The inner node's p1 x position is correct");
 
   // Same as p1x + the inner node width
   let p2x = p1x + 100;
   is(quad.p2.x, p2x, "The inner node's p2 x position is correct");
 }
 
-function takesScrollingIntoAccount(doc, helper) {
+function takesScrollingIntoAccount(doc) {
   info("Checks that the quad returned for a node inside multiple scrolled " +
     "containers takes the scroll values into account");
 
   // For info, the container being tested here is absolutely positioned at 0 0
   // to simplify asserting the coordinates
 
   info("Scroll the container nodes down");
   let scrolledNode = doc.querySelector("#scrolled-node");
   scrolledNode.scrollTop = 100;
   let subScrolledNode = doc.querySelector("#sub-scrolled-node");
   subScrolledNode.scrollTop = 200;
   let innerNode = doc.querySelector("#inner-scrolled-node");
 
-  let [quad] = helper.getAdjustedQuads(innerNode, "content");
+  let [quad] = getAdjustedQuads(doc.defaultView, innerNode, "content");
   is(quad.p1.x, 0, "p1.x of the scrolled node is correct after scrolling down");
   is(quad.p1.y, -300, "p1.y of the scrolled node is correct after scrolling down");
 
   info("Scrolling back up");
   scrolledNode.scrollTop = 0;
   subScrolledNode.scrollTop = 0;
 
-  [quad] = helper.getAdjustedQuads(innerNode, "content");
+  [quad] = getAdjustedQuads(doc.defaultView, innerNode, "content");
   is(quad.p1.x, 0, "p1.x of the scrolled node is correct after scrolling up");
   is(quad.p1.y, 0, "p1.y of the scrolled node is correct after scrolling up");
 }
 
-function takesZoomIntoAccount(doc, helper) {
+function takesZoomIntoAccount(doc) {
   info("Checks that if the page is zoomed in/out, the quad returned is correct");
 
   // Hard-coding coordinates in this zoom test is a bad idea as it can vary
   // depending on the platform, so we simply test that zooming in produces a
   // bigger quad and zooming out produces a smaller quad
 
   let node = doc.querySelector("#simple-node-with-margin-padding-border");
-  let [defaultQuad] = helper.getAdjustedQuads(node);
+  let [defaultQuad] = getAdjustedQuads(doc.defaultView, node);
 
   info("Zoom in");
   window.FullZoom.enlarge();
-  let [zoomedInQuad] = helper.getAdjustedQuads(node);
+  let [zoomedInQuad] = getAdjustedQuads(doc.defaultView, node);
 
   ok(zoomedInQuad.bounds.width > defaultQuad.bounds.width,
     "The zoomed in quad is bigger than the default one");
   ok(zoomedInQuad.bounds.height > defaultQuad.bounds.height,
     "The zoomed in quad is bigger than the default one");
 
   info("Zoom out");
   window.FullZoom.reset();
   window.FullZoom.reduce();
-  let [zoomedOutQuad] = helper.getAdjustedQuads(node);
+  let [zoomedOutQuad] = getAdjustedQuads(doc.defaultView, node);
 
   ok(zoomedOutQuad.bounds.width < defaultQuad.bounds.width,
     "The zoomed out quad is smaller than the default one");
   ok(zoomedOutQuad.bounds.height < defaultQuad.bounds.height,
     "The zoomed out quad is smaller than the default one");
 
   window.FullZoom.reset();
 }
 
-function returnsMultipleItemsForWrappingInlineElements(doc, helper) {
+function returnsMultipleItemsForWrappingInlineElements(doc) {
   info("Checks that several quads are returned for inline elements that span line-breaks");
 
   let node = doc.querySelector("#inline");
-  let quads = helper.getAdjustedQuads(node, "content");
+  let quads = getAdjustedQuads(doc.defaultView, node, "content");
   // At least 3 because of the 2 <br />, maybe more depending on the window size.
   ok(quads.length >= 3, "Multiple quads were returned");
 
   is(quads.length, node.getBoxQuads().length,
     "The same number of boxes as getBoxQuads was returned");
 }
--- a/browser/devtools/shared/test/browser_layoutHelpers.js
+++ b/browser/devtools/shared/test/browser_layoutHelpers.js
@@ -1,85 +1,83 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that scrollIntoViewIfNeeded works properly.
-const LayoutHelpers = require("devtools/toolkit/layout-helpers");
-
+let {scrollIntoViewIfNeeded} = require("devtools/toolkit/layout/utils");
 
 const TEST_URI = TEST_URI_ROOT + "browser_layoutHelpers.html";
 
 add_task(function*() {
   let [host, win, doc] = yield createHost("bottom", TEST_URI);
   runTest(win);
   host.destroy();
 });
 
 function runTest(win) {
-  let lh = new LayoutHelpers(win);
   let some = win.document.getElementById('some');
 
   some.style.top = win.innerHeight + 'px';
   some.style.left = win.innerWidth + 'px';
   // The tests start with a black 2x2 pixels square below bottom right.
   // Do not resize the window during the tests.
 
   let xPos = Math.floor(win.innerWidth / 2);
   win.scroll(xPos, win.innerHeight + 2);  // Above the viewport.
-  lh.scrollIntoViewIfNeeded(some);
+  scrollIntoViewIfNeeded(some);
   is(win.scrollY, Math.floor(win.innerHeight / 2) + 1,
      'Element completely hidden above should appear centered.');
   is(win.scrollX, xPos,
      'scrollX position has not changed.');
 
   win.scroll(win.innerWidth / 2, win.innerHeight + 1);  // On the top edge.
-  lh.scrollIntoViewIfNeeded(some);
+  scrollIntoViewIfNeeded(some);
   is(win.scrollY, win.innerHeight,
      'Element partially visible above should appear above.');
   is(win.scrollX, xPos,
      'scrollX position has not changed.');
 
   win.scroll(win.innerWidth / 2, 0);  // Just below the viewport.
-  lh.scrollIntoViewIfNeeded(some);
+  scrollIntoViewIfNeeded(some);
   is(win.scrollY, Math.floor(win.innerHeight / 2) + 1,
      'Element completely hidden below should appear centered.');
   is(win.scrollX, xPos,
      'scrollX position has not changed.');
 
   win.scroll(win.innerWidth / 2, 1);  // On the bottom edge.
-  lh.scrollIntoViewIfNeeded(some);
+  scrollIntoViewIfNeeded(some);
   is(win.scrollY, 2,
      'Element partially visible below should appear below.');
   is(win.scrollX, xPos,
      'scrollX position has not changed.');
 
   win.scroll(win.innerWidth / 2, win.innerHeight + 2);  // Above the viewport.
-  lh.scrollIntoViewIfNeeded(some, false);
+  scrollIntoViewIfNeeded(some, false);
   is(win.scrollY, win.innerHeight,
      'Element completely hidden above should appear above ' +
      'if parameter is false.');
   is(win.scrollX, xPos,
      'scrollX position has not changed.');
 
   win.scroll(win.innerWidth / 2, win.innerHeight + 1);  // On the top edge.
-  lh.scrollIntoViewIfNeeded(some, false);
+  scrollIntoViewIfNeeded(some, false);
   is(win.scrollY, win.innerHeight,
      'Element partially visible above should appear above ' +
      'if parameter is false.');
   is(win.scrollX, xPos,
      'scrollX position has not changed.');
 
   win.scroll(win.innerWidth / 2, 0);  // Below the viewport.
-  lh.scrollIntoViewIfNeeded(some, false);
+  scrollIntoViewIfNeeded(some, false);
   is(win.scrollY, 2,
      'Element completely hidden below should appear below ' +
      'if parameter is false.');
   is(win.scrollX, xPos,
      'scrollX position has not changed.');
 
   win.scroll(win.innerWidth / 2, 1);  // On the bottom edge.
-  lh.scrollIntoViewIfNeeded(some, false);
+  scrollIntoViewIfNeeded(some, false);
   is(win.scrollY, 2,
      'Element partially visible below should appear below ' +
      'if parameter is false.');
   is(win.scrollX, xPos,
      'scrollX position has not changed.');
 }
--- a/browser/devtools/shared/test/test-actor.js
+++ b/browser/devtools/shared/test/test-actor.js
@@ -2,17 +2,17 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // A helper actor for brower/devtools/inspector tests.
 
 let { Cc, Ci, Cu, Cr } = require("chrome");
-const LayoutHelpers = require("devtools/toolkit/layout-helpers");
+const {getElementFromPoint, getAdjustedQuads} = require("devtools/toolkit/layout/utils");
 const promise = require("promise");
 const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
 let DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
             .getService(Ci.mozIJSSubScriptLoader);
 let EventUtils = {};
 loader.loadSubScript("chrome://marionette/content/EventUtils.js", EventUtils);
 
@@ -234,18 +234,17 @@ const TestActor = exports.TestActor = pr
     request: {
       level: Arg(0, "string"),
       actorID: Arg(1, "string"),
     },
     response: {}
   }),
 
   assertElementAtPoint: protocol.method(function (x, y, selector) {
-    let helper = new LayoutHelpers(this.content);
-    let elementAtPoint = helper.getElementFromPoint(this.content.document, x, y);
+    let elementAtPoint = getElementFromPoint(this.content.document, x, y);
     if (!elementAtPoint) {
       throw new Error("Unable to find element at (" + x + ", " + y + ")");
     }
     let node = this._querySelector(selector);
     return node == elementAtPoint;
   }, {
     request: {
       x: Arg(0, "number"),
@@ -261,20 +260,19 @@ const TestActor = exports.TestActor = pr
   /**
    * Get all box-model regions' adjusted boxquads for the given element
    * @param {String} selector The node selector to target a given element
    * @return {Object} An object with each property being a box-model region, each
    * of them being an object with the p1/p2/p3/p4 properties
    */
   getAllAdjustedQuads: protocol.method(function(selector) {
     let regions = {};
-    let helper = new LayoutHelpers(this.content);
     let node = this._querySelector(selector);
     for (let boxType of ["content", "padding", "border", "margin"]) {
-      regions[boxType] = helper.getAdjustedQuads(node, boxType);
+      regions[boxType] = getAdjustedQuads(this.content, node, boxType);
     }
 
     return regions;
   }, {
     request: {
       selector: Arg(0, "string")
     },
     response: {
--- a/browser/devtools/shared/widgets/Graphs.js
+++ b/browser/devtools/shared/widgets/Graphs.js
@@ -2,17 +2,17 @@
  * 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/. */
 "use strict";
 
 const { Cc, Ci, Cu, Cr } = require("chrome");
 
 const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 const { Heritage, setNamedTimeout, clearNamedTimeout } = require("resource:///modules/devtools/ViewHelpers.jsm");
-const LayoutHelpers = require("devtools/toolkit/layout-helpers");
+const { getCurrentZoom } = require("devtools/toolkit/layout/utils");
 
 loader.lazyRequireGetter(this, "promise");
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/toolkit/event-emitter");
 
 loader.lazyImporter(this, "DevToolsWorker",
   "resource://gre/modules/devtools/shared/worker.js");
 
@@ -920,17 +920,17 @@ AbstractCanvasGraph.prototype = {
     // or less than 0.
     let maxX = quad.p2.x - quad.p1.x;
     let maxY = quad.p3.y - quad.p1.y;
     let mouseX = Math.max(0, Math.min(x, maxX)) * this._pixelRatio;
     let mouseY = Math.max(0, Math.min(x, maxY)) * this._pixelRatio;
 
     // The coordinates need to be modified with the current zoom level
     // to prevent them from being wrong.
-    let zoom = LayoutHelpers.getCurrentZoom(this._canvas);
+    let zoom = getCurrentZoom(this._canvas);
     mouseX /= zoom;
     mouseY /= zoom;
 
     return {mouseX,mouseY};
   },
 
   /**
    * Listener for the "mousemove" event on the graph's container.
--- a/browser/devtools/tilt/test/browser_tilt_utils05.js
+++ b/browser/devtools/tilt/test/browser_tilt_utils05.js
@@ -50,21 +50,21 @@ function test() {
 
     is(cwDimensions.width - iframe.contentWindow.scrollMaxX,
       iframe.contentWindow.innerWidth,
       "The content window width wasn't calculated correctly.");
     is(cwDimensions.height - iframe.contentWindow.scrollMaxY,
       iframe.contentWindow.innerHeight,
       "The content window height wasn't calculated correctly.");
 
-    let lh = new LayoutHelpers(gBrowser.contentWindow);
-    let nodeCoordinates = lh.getRect(
+    let nodeCoordinates = getRect(
+      gBrowser.contentWindow,
       iframe.contentDocument.getElementById("test-div"), iframe.contentWindow);
 
-    let frameOffset = lh.getIframeContentOffset(iframe);
+    let frameOffset = getIframeContentOffset(iframe);
     let frameRect = iframe.getBoundingClientRect();
 
     is(nodeCoordinates.top, frameRect.top + frameOffset[0] + 98,
       "The node coordinates top value wasn't calculated correctly.");
     is(nodeCoordinates.left, frameRect.left + frameOffset[1] + 76,
       "The node coordinates left value wasn't calculated correctly.");
     is(nodeCoordinates.width, 123,
       "The node coordinates width value wasn't calculated correctly.");
--- a/browser/devtools/tilt/test/browser_tilt_utils07.js
+++ b/browser/devtools/tilt/test/browser_tilt_utils07.js
@@ -99,21 +99,21 @@ function test() {
 
     is(cwDimensions.width - iframe.contentWindow.scrollMaxX,
       iframe.contentWindow.innerWidth,
       "The content window width wasn't calculated correctly.");
     is(cwDimensions.height - iframe.contentWindow.scrollMaxY,
       iframe.contentWindow.innerHeight,
       "The content window height wasn't calculated correctly.");
 
-    let lh = new LayoutHelpers(gBrowser.contentWindow);
-    let nodeCoordinates = lh.getRect(
+    let nodeCoordinates = getRect(
+      gBrowser.contentWindow,
       iframe.contentDocument.getElementById("test-div"), iframe.contentWindow);
 
-    let frameOffset = lh.getIframeContentOffset(iframe);
+    let frameOffset = getIframeContentOffset(iframe);
     let frameRect = iframe.getBoundingClientRect();
 
     is(nodeCoordinates.top, frameRect.top + frameOffset[0],
       "The node coordinates top value wasn't calculated correctly.");
     is(nodeCoordinates.left, frameRect.left + frameOffset[1],
       "The node coordinates left value wasn't calculated correctly.");
     is(nodeCoordinates.width, 123,
       "The node coordinates width value wasn't calculated correctly.");
--- a/browser/devtools/tilt/test/head.js
+++ b/browser/devtools/tilt/test/head.js
@@ -4,17 +4,17 @@
 
 let {require} = Components.utils.import("resource://gre/modules/devtools/Loader.jsm", {});
 let TiltManager = require("devtools/tilt/tilt").TiltManager;
 let TiltGL = require("devtools/tilt/tilt-gl");
 let {EPSILON, TiltMath, vec3, mat3, mat4, quat4} = require("devtools/tilt/tilt-math");
 let TiltUtils = require("devtools/tilt/tilt-utils");
 let {TiltVisualizer} = require("devtools/tilt/tilt-visualizer");
 let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
-let LayoutHelpers = require("devtools/toolkit/layout-helpers");
+let {getRect, getIframeContentOffset} = require("devtools/toolkit/layout/utils");
 
 
 const DEFAULT_HTML = "data:text/html," +
   "<DOCTYPE html>" +
   "<html>" +
     "<head>" +
       "<meta charset='utf-8'/>" +
       "<title>Three Laws</title>" +
--- a/browser/devtools/tilt/tilt-utils.js
+++ b/browser/devtools/tilt/tilt-utils.js
@@ -1,20 +1,20 @@
 /* -*- 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/. */
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
+const {getRect} = require("devtools/toolkit/layout/utils");
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-let LayoutHelpers = require("devtools/toolkit/layout-helpers");
 
 const STACK_THICKNESS = 15;
 
 /**
  * Module containing various helper functions used throughout Tilt.
  */
 this.TiltUtils = {};
 module.exports = this.TiltUtils;
@@ -400,19 +400,18 @@ TiltUtils.DOM = {
    *                  width of the node
    *         {Number} height
    *                  height of the node
    *         {Number} thickness
    *                  thickness of the node
    */
   getNodePosition: function TUD_getNodePosition(aContentWindow, aNode,
                                                 aParentPosition) {
-    let lh = new LayoutHelpers(aContentWindow);
     // get the x, y, width and height coordinates of the node
-    let coord = lh.getRect(aNode, aContentWindow);
+    let coord = getRect(aContentWindow, aNode, aContentWindow);
     if (!coord) {
       return null;
     }
 
     coord.depth = aParentPosition ? (aParentPosition.depth + aParentPosition.thickness) : 0;
     coord.thickness = STACK_THICKNESS;
 
     return coord;
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -3361,16 +3361,35 @@ nsDocShell::GetSameTypeRootTreeItem(nsID
     NS_ENSURE_SUCCESS(
       (*aRootTreeItem)->GetSameTypeParent(getter_AddRefs(parent)),
       NS_ERROR_FAILURE);
   }
   NS_ADDREF(*aRootTreeItem);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDocShell::GetSameTypeRootTreeItemIgnoreBrowserAndAppBoundaries(nsIDocShell ** aRootTreeItem)
+{
+    NS_ENSURE_ARG_POINTER(aRootTreeItem);
+    *aRootTreeItem = static_cast<nsIDocShell *>(this);
+
+    nsCOMPtr<nsIDocShell> parent;
+    NS_ENSURE_SUCCESS(GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent)),
+                      NS_ERROR_FAILURE);
+    while (parent) {
+      *aRootTreeItem = parent;
+      NS_ENSURE_SUCCESS((*aRootTreeItem)->
+        GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent)),
+        NS_ERROR_FAILURE);
+    }
+    NS_ADDREF(*aRootTreeItem);
+    return NS_OK;
+}
+
 /* static */
 bool
 nsDocShell::CanAccessItem(nsIDocShellTreeItem* aTargetItem,
                           nsIDocShellTreeItem* aAccessingItem,
                           bool aConsiderOpener)
 {
   NS_PRECONDITION(aTargetItem, "Must have target item!");
 
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -38,31 +38,31 @@ interface nsIScriptGlobalObject;
 interface nsIDOMStorage;
 interface nsIPrincipal;
 interface nsIWebBrowserPrint;
 interface nsIVariant;
 interface nsIPrivacyTransitionObserver;
 interface nsIReflowObserver;
 interface nsIScrollObserver;
 interface nsITabParent;
- 
+
 typedef unsigned long nsLoadFlags;
 
-[scriptable, builtinclass, uuid(35a26f70-dbb9-450d-b634-cd0bbb9b8e13)]
+[scriptable, builtinclass, uuid(e534b6ee-c35d-4ee2-acce-1bcd08a91230)]
 interface nsIDocShell : nsIDocShellTreeItem
 {
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing	this interface.  If it can't be loaded here
    * however, the URL dispatcher will go through its normal process of content
    * loading.
    *
    * @param uri        - The URI to load.
    * @param loadInfo   - This is the extended load info for this load.  This
-   *                     most often will be null, but if you need to do 
+   *                     most often will be null, but if you need to do
    *                     additional setup for this load you can get a loadInfo
    *                     object by calling createLoadInfo.  Once you have this
    *                     object you can set the needed properties on it and
    *                     then pass it to loadURI.
    * @param aLoadFlags - Flags to modify load behaviour. Flags are defined in
    *                     nsIWebNavigation.  Note that using flags outside
    *                     LOAD_FLAGS_MASK is only allowed if passing in a
    *                     non-null loadInfo.  And even some of those might not
@@ -81,20 +81,20 @@ interface nsIDocShell : nsIDocShellTreeI
    *
    * @param aStream         - The input stream that provides access to the data
    *                          to be loaded.  This must be a blocking, threadsafe
    *                          stream implementation.
    * @param aURI            - The URI representing the stream, or null.
    * @param aContentType    - The type (MIME) of data being loaded (empty if unknown).
    * @param aContentCharset - The charset of the data being loaded (empty if unknown).
    * @param aLoadInfo       - This is the extended load info for this load.  This
-   *                          most often will be null, but if you need to do 
+   *                          most often will be null, but if you need to do
    *                          additional setup for this load you can get a
    *                          loadInfo object by calling createLoadInfo.  Once
-   *                          you have this object you can set the needed 
+   *                          you have this object you can set the needed
    *                          properties on it and then pass it to loadStream.
    */
   [noscript]void loadStream(in nsIInputStream aStream,
                             in nsIURI aURI,
                             in ACString aContentType,
                             in ACString aContentCharset,
                             in nsIDocShellLoadInfo aLoadInfo);
 
@@ -120,17 +120,17 @@ interface nsIDocShell : nsIDocShellTreeI
   /**
    * Loads the given URI.  This method is identical to loadURI(...) except
    * that its parameter list is broken out instead of being packaged inside
    * of an nsIDocShellLoadInfo object...
    *
    * @param aURI            - The URI to load.
    * @param aReferrer       - Referring URI
    * @param aReferrerPolicy - Referrer policy
-   * @param aOwner          - Owner (security principal) 
+   * @param aOwner          - Owner (security principal)
    * @param aInheritOwner   - Flag indicating whether the owner of the current
    *                          document should be inherited if aOwner is null.
    * @param aStopActiveDoc  - Flag indicating whether loading the current
    *                          document should be stopped.
    * @param aWindowTarget   - Window target for the load.
    * @param aTypeHint       - A hint as to the content-type of the resulting
    *                          data.  May be null or empty if no hint.
    * @param aFileName       - Non-null when the link should be downloaded as
@@ -186,17 +186,17 @@ interface nsIDocShell : nsIDocShellTreeI
    * viewer.  Called by the document before initiating an out of band document.write().
    */
   void prepareForNewContentModel();
 
   /**
    * For editors and suchlike who wish to change the URI associated with the
    * document. Note if you want to get the current URI, use the read-only
    * property on nsIWebNavigation.
-   */ 
+   */
   void setCurrentURI(in nsIURI aURI);
 
   /**
    * Notify the associated content viewer and all child docshells that they are
    * about to be hidden.  If |isUnload| is true, then the document is being
    * unloaded as well.
    *
    * @param isUnload if true, fire the unload event in addition to the pagehide
@@ -319,17 +319,17 @@ interface nsIDocShell : nsIDocShellTreeI
   attribute boolean allowAuth;
 
   /**
    * Set/Get the document scale factor.  When setting this attribute, a
    * NS_ERROR_NOT_IMPLEMENTED error may be returned by implementations
    * not supporting zoom.  Implementations not supporting zoom should return
    * 1.0 all the time for the Get operation.  1.0 by the way is the default
    * of zoom.  This means 100% of normal scaling or in other words normal size
-   * no zoom. 
+   * no zoom.
    */
   attribute float zoom;
 
   /*
    * The size, in CSS pixels, of the horizontal margins for the <body> of an
    * HTML document in this docshel; used to implement the marginwidth attribute
    * on HTML <frame>/<iframe> elements.  A value smaller than zero indicates
    * that the attribute was not set.
@@ -358,26 +358,26 @@ interface nsIDocShell : nsIDocShellTreeI
    * Current busy state for DocShell
    */
   const unsigned long BUSY_FLAGS_NONE             = 0;
   const unsigned long BUSY_FLAGS_BUSY             = 1;
   const unsigned long BUSY_FLAGS_BEFORE_PAGE_LOAD = 2;
   const unsigned long BUSY_FLAGS_PAGE_LOADING     = 4;
 
   /**
-   * Load commands for the document 
+   * Load commands for the document
    */
   const unsigned long LOAD_CMD_NORMAL  = 0x1;   // Normal load
   const unsigned long LOAD_CMD_RELOAD  = 0x2;   // Reload
   const unsigned long LOAD_CMD_HISTORY = 0x4;   // Load from history
   const unsigned long LOAD_CMD_PUSHSTATE = 0x8; // History.pushState()
 
   readonly attribute unsigned long busyFlags;
 
-  /* 
+  /*
    * attribute to access the loadtype  for the document
    */
   attribute unsigned long  loadType;
 
   /*
    * Default load flags (as defined in nsIRequest) that will be set on all
    * requests made by this docShell and propagated to all child docShells and
    * to nsILoadGroup::defaultLoadFlags for the docShell's loadGroup.
@@ -504,17 +504,17 @@ interface nsIDocShell : nsIDocShellTreeI
    * Add a WebApps session storage object to the docshell.
    *
    * @param principal the principal the storage object is associated with
    * @param storage the storage object to add
    */
   void addSessionStorage(in nsIPrincipal principal, in nsIDOMStorage storage);
 
   /**
-   * Gets the channel for the currently loaded document, if any. 
+   * Gets the channel for the currently loaded document, if any.
    * For a new document load, this will be the channel of the previous document
    * until after OnLocationChange fires.
    */
   readonly attribute nsIChannel currentDocumentChannel;
 
   /**
    * Set the offset of this child in its container.
    */
@@ -592,17 +592,17 @@ interface nsIDocShell : nsIDocShellTreeI
    */
   [noscript, notxpcom] void DetachEditorFromWindow();
 
   /**
    * If true, this browser is not visible in the traditional sense, but
    * is actively being rendered to the screen (ex. painted on a canvas)
    * and should be treated accordingly.
    **/
-  attribute boolean isOffScreenBrowser;    
+  attribute boolean isOffScreenBrowser;
 
   /**
    * If the current content viewer isn't initialized for print preview,
    * it is replaced with one which is and to which an about:blank document
    * is loaded.
    */
   readonly attribute nsIWebBrowserPrint printPreview;
 
@@ -837,16 +837,22 @@ interface nsIDocShell : nsIDocShellTreeI
 
   /**
    * Like nsIDocShellTreeItem::GetSameTypeParent, except this ignores <iframe
    * mozbrowser> and <iframe mozapp> boundaries.
    */
   nsIDocShell getSameTypeParentIgnoreBrowserAndAppBoundaries();
 
   /**
+   * Like nsIDocShellTreeItem::GetSameTypeRootTreeItem, except this ignores
+   * <iframe mozbrowser> and <iframe mozapp> boundaries.
+   */
+  nsIDocShell getSameTypeRootTreeItemIgnoreBrowserAndAppBoundaries();
+
+  /**
    * True iff asynchronous panning and zooming is enabled for this
    * docshell.
    */
   readonly attribute bool asyncPanZoomEnabled;
 
   /**
    * The sandbox flags on the docshell. These reflect the value of the sandbox
    * attribute of the associated IFRAME or CSP-protectable content, if
@@ -891,68 +897,68 @@ interface nsIDocShell : nsIDocShellTreeI
    * will be false, mMixedContentChannel will remain null since blocking active content has
    * been disabled and hence mMixedContentChannel will never be set.
    */
   attribute nsIChannel mixedContentChannel;
 
   /**
    * Checks whether the channel associated with the root docShell is equal to
    * mMixedContentChannel. If they are the same, allowMixedContent is set to true.
-   * Checks if the root document has a secure connection. If it is, sets 
-   * rootHasSecureConnection to true. If the docShell is the root doc shell, 
-   * isRootDocShell is set to true. 
+   * Checks if the root document has a secure connection. If it is, sets
+   * rootHasSecureConnection to true. If the docShell is the root doc shell,
+   * isRootDocShell is set to true.
    */
   void GetAllowMixedContentAndConnectionData(out boolean rootHasSecureConnection, out boolean allowMixedContent, out boolean isRootDocShell);
 
 
   /**
    * Are plugins allowed in the current document loaded in this docshell ?
    * (if there is one). This depends on whether plugins are allowed by this
    * docshell itself or if the document is sandboxed and hence plugins should
    * not be allowed.
    */
   [noscript, notxpcom] bool pluginsAllowedInCurrentDoc();
-  
+
 
   /**
    * Attribute that determines whether fullscreen is allowed to be entered for
    * this subtree of the docshell tree. This is true when all iframes containing
    * this docshell have their "allowfullscreen" attribute set to "true".
    * fullscreenAllowed is only writable at content boundaries, where it is used
    * to propagate the value of the cross process parent's iframe's
    * "allowfullscreen" attribute to the child process. Setting
    * fullscreenAllowed on docshells which aren't content boundaries throws an
    * exception.
    */
   [infallible] readonly attribute boolean fullscreenAllowed;
-  
+
   void setFullscreenAllowed(in boolean allowed);
 
   [notxpcom] uint32_t orientationLock();
   [notxpcom] void setOrientationLock(in uint32_t orientationLock);
 
   [noscript, infallible] attribute boolean affectPrivateSessionLifetime;
 
   /**
    * Indicates whether the UI may enable the character encoding menu. The UI
    * must disable the menu when this property is false.
    */
   [infallible] readonly attribute boolean mayEnableCharacterEncodingMenu;
 
            attribute  nsIEditor editor;
   readonly attribute  boolean   editable;             /* this docShell is editable */
   readonly attribute  boolean   hasEditingSession;    /* this docShell has an editing session */
-    
+
   /**
    * Make this docShell editable, setting a flag that causes
    * an editor to get created, either immediately, or after
    * a url has been loaded.
    *      @param  inWaitForUriLoad    true to wait for a URI before
    *                                  creating the editor.
-   */     
+   */
   void makeEditable(in boolean inWaitForUriLoad);
 
   /**
    * Get the SHEntry associated with a child docshell
    */
   nsISHEntry getChildSHEntry(in long aChildOffset);
 
   /**
--- a/toolkit/devtools/gcli/commands/screenshot.js
+++ b/toolkit/devtools/gcli/commands/screenshot.js
@@ -2,17 +2,17 @@
  * 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/. */
 
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
 const l10n = require("gcli/l10n");
 const { Services } = require("resource://gre/modules/Services.jsm");
-const LayoutHelpers = require("devtools/toolkit/layout-helpers");
+const { getRect } = require("devtools/toolkit/layout/utils");
 
 loader.lazyImporter(this, "Downloads", "resource://gre/modules/Downloads.jsm");
 loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm");
 loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
 
 const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]
                            .getService(Ci.nsIStringBundleService)
                            .createBundle("chrome://branding/locale/brand.properties")
@@ -269,18 +269,17 @@ function createScreenshotData(document, 
   if (args.fullpage) {
     // Bug 961832: GCLI screenshot shows fixed position element in wrong
     // position if we don't scroll to top
     window.scrollTo(0,0);
     width = window.innerWidth + window.scrollMaxX;
     height = window.innerHeight + window.scrollMaxY;
   }
   else if (args.selector) {
-    const lh = new LayoutHelpers(window);
-    ({ top, left, width, height } = lh.getRect(args.selector, window));
+    ({ top, left, width, height } = getRect(window, args.selector, window));
   }
   else {
     left = window.scrollX;
     top = window.scrollY;
     width = window.innerWidth;
     height = window.innerHeight;
   }
 
deleted file mode 100644
--- a/toolkit/devtools/layout-helpers.js
+++ /dev/null
@@ -1,584 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript 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/. */
-
-let {Ci} = require("chrome")
-
-let LayoutHelpers = function(aTopLevelWindow) {
-  this._topDocShell = aTopLevelWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                                     .getInterface(Ci.nsIWebNavigation)
-                                     .QueryInterface(Ci.nsIDocShell);
-};
-
-module.exports = LayoutHelpers;
-
-LayoutHelpers.prototype = {
-
-  /**
-   * Get box quads adjusted for iframes and zoom level.
-   * @param {DOMNode} node The node for which we are to get the box model region
-   * quads.
-   * @param {String} region The box model region to return: "content",
-   * "padding", "border" or "margin".
-   * @return {Array} An array of objects that have the same structure as quads
-   * returned by getBoxQuads. An empty array if the node has no quads or is
-   * invalid.
-   */
-  getAdjustedQuads: function(node, region) {
-    if (!node || !node.getBoxQuads) {
-      return [];
-    }
-
-    let quads = node.getBoxQuads({
-      box: region
-    });
-
-    if (!quads.length) {
-      return [];
-    }
-
-    let [xOffset, yOffset] = this.getFrameOffsets(node);
-    let scale = LayoutHelpers.getCurrentZoom(node);
-
-    let adjustedQuads = [];
-    for (let quad of quads) {
-      adjustedQuads.push({
-        p1: {
-          w: quad.p1.w * scale,
-          x: quad.p1.x * scale + xOffset,
-          y: quad.p1.y * scale + yOffset,
-          z: quad.p1.z * scale
-        },
-        p2: {
-          w: quad.p2.w * scale,
-          x: quad.p2.x * scale + xOffset,
-          y: quad.p2.y * scale + yOffset,
-          z: quad.p2.z * scale
-        },
-        p3: {
-          w: quad.p3.w * scale,
-          x: quad.p3.x * scale + xOffset,
-          y: quad.p3.y * scale + yOffset,
-          z: quad.p3.z * scale
-        },
-        p4: {
-          w: quad.p4.w * scale,
-          x: quad.p4.x * scale + xOffset,
-          y: quad.p4.y * scale + yOffset,
-          z: quad.p4.z * scale
-        },
-        bounds: {
-          bottom: quad.bounds.bottom * scale + yOffset,
-          height: quad.bounds.height * scale,
-          left: quad.bounds.left * scale + xOffset,
-          right: quad.bounds.right * scale + xOffset,
-          top: quad.bounds.top * scale + yOffset,
-          width: quad.bounds.width * scale,
-          x: quad.bounds.x * scale + xOffset,
-          y: quad.bounds.y * scale + yOffset
-        }
-      });
-    }
-
-    return adjustedQuads;
-  },
-
-  /**
-   * Compute the absolute position and the dimensions of a node, relativalely
-   * to the root window.
-   *
-   * @param {DOMNode} aNode
-   *        a DOM element to get the bounds for
-   * @param {DOMWindow} aContentWindow
-   *        the content window holding the node
-   * @return {Object}
-   *         A rect object with the {top, left, width, height} properties
-   */
-  getRect: function(aNode, aContentWindow) {
-    let frameWin = aNode.ownerDocument.defaultView;
-    let clientRect = aNode.getBoundingClientRect();
-
-    // Go up in the tree of frames to determine the correct rectangle.
-    // clientRect is read-only, we need to be able to change properties.
-    let rect = {top: clientRect.top + aContentWindow.pageYOffset,
-            left: clientRect.left + aContentWindow.pageXOffset,
-            width: clientRect.width,
-            height: clientRect.height};
-
-    // We iterate through all the parent windows.
-    while (true) {
-      // Are we in the top-level window?
-      if (this.isTopLevelWindow(frameWin)) {
-        break;
-      }
-
-      let frameElement = this.getFrameElement(frameWin);
-      if (!frameElement) {
-        break;
-      }
-
-      // We are in an iframe.
-      // We take into account the parent iframe position and its
-      // offset (borders and padding).
-      let frameRect = frameElement.getBoundingClientRect();
-
-      let [offsetTop, offsetLeft] =
-        this.getIframeContentOffset(frameElement);
-
-      rect.top += frameRect.top + offsetTop;
-      rect.left += frameRect.left + offsetLeft;
-
-      frameWin = this.getParentWindow(frameWin);
-    }
-
-    return rect;
-  },
-
-  /**
-   * Returns iframe content offset (iframe border + padding).
-   * Note: this function shouldn't need to exist, had the platform provided a
-   * suitable API for determining the offset between the iframe's content and
-   * its bounding client rect. Bug 626359 should provide us with such an API.
-   *
-   * @param {DOMNode} aIframe
-   *        The iframe.
-   * @return {Array} [offsetTop, offsetLeft]
-   *         offsetTop is the distance from the top of the iframe and the top of
-   *         the content document.
-   *         offsetLeft is the distance from the left of the iframe and the left
-   *         of the content document.
-   */
-  getIframeContentOffset: function(aIframe) {
-    let style = aIframe.contentWindow.getComputedStyle(aIframe, null);
-
-    // In some cases, the computed style is null
-    if (!style) {
-      return [0, 0];
-    }
-
-    let paddingTop = parseInt(style.getPropertyValue("padding-top"));
-    let paddingLeft = parseInt(style.getPropertyValue("padding-left"));
-
-    let borderTop = parseInt(style.getPropertyValue("border-top-width"));
-    let borderLeft = parseInt(style.getPropertyValue("border-left-width"));
-
-    return [borderTop + paddingTop, borderLeft + paddingLeft];
-  },
-
-  /**
-   * Find an element from the given coordinates. This method descends through
-   * frames to find the element the user clicked inside frames.
-   *
-   * @param {DOMDocument} aDocument the document to look into.
-   * @param {Number} aX
-   * @param {Number} aY
-   * @return {DOMNode}
-   *         the element node found at the given coordinates, or null if no node
-   *         was found
-   */
-  getElementFromPoint: function(aDocument, aX, aY) {
-    let node = aDocument.elementFromPoint(aX, aY);
-    if (node && node.contentDocument) {
-      if (node instanceof Ci.nsIDOMHTMLIFrameElement) {
-        let rect = node.getBoundingClientRect();
-
-        // Gap between the iframe and its content window.
-        let [offsetTop, offsetLeft] = this.getIframeContentOffset(node);
-
-        aX -= rect.left + offsetLeft;
-        aY -= rect.top + offsetTop;
-
-        if (aX < 0 || aY < 0) {
-          // Didn't reach the content document, still over the iframe.
-          return node;
-        }
-      }
-      if (node instanceof Ci.nsIDOMHTMLIFrameElement ||
-          node instanceof Ci.nsIDOMHTMLFrameElement) {
-        let subnode = this.getElementFromPoint(node.contentDocument, aX, aY);
-        if (subnode) {
-          node = subnode;
-        }
-      }
-    }
-    return node;
-  },
-
-  /**
-   * Scroll the document so that the element "elem" appears vertically in
-   * the viewport.
-   *
-   * @param {DOMNode} elem
-   *        The element that needs to appear in the viewport.
-   * @param {Boolean} centered
-   *        true if you want it centered, false if you want it to appear on the
-   *        top of the viewport. True by default, and that is usually what
-   *        you want.
-   */
-  scrollIntoViewIfNeeded: function(elem, centered) {
-    // We want to default to centering the element in the page,
-    // so as to keep the context of the element.
-    centered = centered === undefined ? true: !!centered;
-
-    let win = elem.ownerDocument.defaultView;
-    let clientRect = elem.getBoundingClientRect();
-
-    // The following are always from the {top, bottom}
-    // of the viewport, to the {top, …} of the box.
-    // Think of them as geometrical vectors, it helps.
-    // The origin is at the top left.
-
-    let topToBottom = clientRect.bottom;
-    let bottomToTop = clientRect.top - win.innerHeight;
-    let yAllowed = true;  // We allow one translation on the y axis.
-
-    // Whatever `centered` is, the behavior is the same if the box is
-    // (even partially) visible.
-    if ((topToBottom > 0 || !centered) && topToBottom <= elem.offsetHeight) {
-      win.scrollBy(0, topToBottom - elem.offsetHeight);
-      yAllowed = false;
-    } else
-    if ((bottomToTop < 0 || !centered) && bottomToTop >= -elem.offsetHeight) {
-      win.scrollBy(0, bottomToTop + elem.offsetHeight);
-      yAllowed = false;
-    }
-
-    // If we want it centered, and the box is completely hidden,
-    // then we center it explicitly.
-    if (centered) {
-      if (yAllowed && (topToBottom <= 0 || bottomToTop >= 0)) {
-        win.scroll(win.scrollX,
-                   win.scrollY + clientRect.top
-                   - (win.innerHeight - elem.offsetHeight) / 2);
-      }
-    }
-  },
-
-  /**
-   * Check if a node and its document are still alive
-   * and attached to the window.
-   *
-   * @param {DOMNode} aNode
-   * @return {Boolean}
-   */
-  isNodeConnected: function(aNode) {
-    try {
-      let connected = (aNode.ownerDocument && aNode.ownerDocument.defaultView &&
-                      !(aNode.compareDocumentPosition(aNode.ownerDocument.documentElement) &
-                      aNode.DOCUMENT_POSITION_DISCONNECTED));
-      return connected;
-    } catch (e) {
-      // "can't access dead object" error
-      return false;
-    }
-  },
-
-  /**
-   * like win.parent === win, but goes through mozbrowsers and mozapps iframes.
-   *
-   * @param {DOMWindow} win
-   * @return {Boolean}
-   */
-  isTopLevelWindow: function(win) {
-    let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                   .getInterface(Ci.nsIWebNavigation)
-                   .QueryInterface(Ci.nsIDocShell);
-
-    return docShell === this._topDocShell;
-  },
-
-  /**
-   * Check a window is part of the top level window.
-   *
-   * @param {DOMWindow} win
-   * @return {Boolean}
-   */
-  isIncludedInTopLevelWindow: function LH_isIncludedInTopLevelWindow(win) {
-    if (this.isTopLevelWindow(win)) {
-      return true;
-    }
-
-    let parent = this.getParentWindow(win);
-    if (!parent || parent === win) {
-      return false;
-    }
-
-    return this.isIncludedInTopLevelWindow(parent);
-  },
-
-  /**
-   * like win.parent, but goes through mozbrowsers and mozapps iframes.
-   *
-   * @param {DOMWindow} win
-   * @return {DOMWindow}
-   */
-  getParentWindow: function(win) {
-    if (this.isTopLevelWindow(win)) {
-      return null;
-    }
-
-    let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                   .getInterface(Ci.nsIWebNavigation)
-                   .QueryInterface(Ci.nsIDocShell);
-
-    if (docShell.isBrowserOrApp) {
-      let parentDocShell = docShell.getSameTypeParentIgnoreBrowserAndAppBoundaries();
-      return parentDocShell ? parentDocShell.contentViewer.DOMDocument.defaultView : null;
-    } else {
-      return win.parent;
-    }
-  },
-
-  /**
-   * like win.frameElement, but goes through mozbrowsers and mozapps iframes.
-   *
-   * @param {DOMWindow} win
-   *        The window to get the frame for
-   * @return {DOMNode}
-   *         The element in which the window is embedded.
-   */
-  getFrameElement: function(win) {
-    if (this.isTopLevelWindow(win)) {
-      return null;
-    }
-
-    let winUtils = win.
-      QueryInterface(Ci.nsIInterfaceRequestor).
-      getInterface(Ci.nsIDOMWindowUtils);
-
-    return winUtils.containerElement;
-  },
-
-  /**
-   * Get the x/y offsets for of all the parent frames of a given node
-   *
-   * @param {DOMNode} node
-   *        The node for which we are to get the offset
-   * @return {Array}
-   *         The frame offset [x, y]
-   */
-  getFrameOffsets: function(node) {
-    let xOffset = 0;
-    let yOffset = 0;
-    let frameWin = node.ownerDocument.defaultView;
-    let scale = LayoutHelpers.getCurrentZoom(node);
-
-    while (true) {
-      // Are we in the top-level window?
-      if (this.isTopLevelWindow(frameWin)) {
-        break;
-      }
-
-      let frameElement = this.getFrameElement(frameWin);
-      if (!frameElement) {
-        break;
-      }
-
-      // We are in an iframe.
-      // We take into account the parent iframe position and its
-      // offset (borders and padding).
-      let frameRect = frameElement.getBoundingClientRect();
-
-      let [offsetTop, offsetLeft] =
-        this.getIframeContentOffset(frameElement);
-
-      xOffset += frameRect.left + offsetLeft;
-      yOffset += frameRect.top + offsetTop;
-
-      frameWin = this.getParentWindow(frameWin);
-    }
-
-    return [xOffset * scale, yOffset * scale];
-  },
-
-  /**
-   * Get the 4 bounding points for a node taking iframes into account.
-   * Note that for transformed nodes, this will return the untransformed bound.
-   *
-   * @param {DOMNode} node
-   * @return {Object}
-   *         An object with p1,p2,p3,p4 properties being {x,y} objects
-   */
-  getNodeBounds: function(node) {
-    if (!node) {
-      return;
-    }
-
-    let scale = LayoutHelpers.getCurrentZoom(node);
-
-    // Find out the offset of the node in its current frame
-    let offsetLeft = 0;
-    let offsetTop = 0;
-    let el = node;
-    while (el && el.parentNode) {
-      offsetLeft += el.offsetLeft;
-      offsetTop += el.offsetTop;
-      el = el.offsetParent;
-    }
-
-    // Also take scrolled containers into account
-    el = node;
-    while (el && el.parentNode) {
-      if (el.scrollTop) {
-        offsetTop -= el.scrollTop;
-      }
-      if (el.scrollLeft) {
-        offsetLeft -= el.scrollLeft;
-      }
-      el = el.parentNode;
-    }
-
-    // And add the potential frame offset if the node is nested
-    let [xOffset, yOffset] = this.getFrameOffsets(node);
-    xOffset += offsetLeft;
-    yOffset += offsetTop;
-
-    xOffset *= scale;
-    yOffset *= scale;
-
-    // Get the width and height
-    let width = node.offsetWidth * scale;
-    let height = node.offsetHeight * scale;
-
-    return {
-      p1: {x: xOffset, y: yOffset},
-      p2: {x: xOffset + width, y: yOffset},
-      p3: {x: xOffset + width, y: yOffset + height},
-      p4: {x: xOffset, y: yOffset + height}
-    };
-  }
-};
-
-/**
- * Traverse getBindingParent until arriving upon the bound element
- * responsible for the generation of the specified node.
- * See https://developer.mozilla.org/en-US/docs/XBL/XBL_1.0_Reference/DOM_Interfaces#getBindingParent.
- *
- * @param {DOMNode} node
- * @return {DOMNode}
- *         If node is not anonymous, this will return node. Otherwise,
- *         it will return the bound element
- *
- */
-LayoutHelpers.getRootBindingParent = function(node) {
-  let parent;
-  let doc = node.ownerDocument;
-  if (!doc) {
-    return node;
-  }
-  while ((parent = doc.getBindingParent(node))) {
-    node = parent;
-  }
-  return node;
-};
-
-LayoutHelpers.getBindingParent = function(node) {
-  let doc = node.ownerDocument;
-  if (!doc) {
-    return false;
-  }
-
-  // If there is no binding parent then it is not anonymous.
-  let parent = doc.getBindingParent(node);
-  if (!parent) {
-    return false;
-  }
-
-  return parent;
-}
-/**
- * Determine whether a node is anonymous by determining if there
- * is a bindingParent.
- *
- * @param {DOMNode} node
- * @return {Boolean}
- *
- */
-LayoutHelpers.isAnonymous = function(node) {
-  return LayoutHelpers.getRootBindingParent(node) !== node;
-};
-
-/**
- * Determine whether a node is native anonymous content (as opposed
- * to XBL anonymous or shadow DOM).
- * Native anonymous content includes elements like internals to form
- * controls and ::before/::after.
- *
- * @param {DOMNode} node
- * @return {Boolean}
- *
- */
-LayoutHelpers.isNativeAnonymous = function(node) {
-  if (!LayoutHelpers.getBindingParent(node)) {
-    return false;
-  }
-  return !LayoutHelpers.isXBLAnonymous(node) &&
-         !LayoutHelpers.isShadowAnonymous(node);
-};
-
-/**
- * Determine whether a node is XBL anonymous content (as opposed
- * to native anonymous or shadow DOM).
- * See https://developer.mozilla.org/en-US/docs/XBL/XBL_1.0_Reference/Anonymous_Content.
- *
- * @param {DOMNode} node
- * @return {Boolean}
- *
- */
-LayoutHelpers.isXBLAnonymous = function(node) {
-  let parent = LayoutHelpers.getBindingParent(node);
-  if (!parent) {
-    return false;
-  }
-
-  // Shadow nodes also show up in getAnonymousNodes, so return false.
-  if (parent.shadowRoot && parent.shadowRoot.contains(node)) {
-    return false;
-  }
-
-  let anonNodes = [...node.ownerDocument.getAnonymousNodes(parent) || []];
-  return anonNodes.indexOf(node) > -1;
-};
-
-/**
- * Determine whether a node is a child of a shadow root.
- * See https://w3c.github.io/webcomponents/spec/shadow/
- *
- * @param {DOMNode} node
- * @return {Boolean}
- */
-LayoutHelpers.isShadowAnonymous = function(node) {
-  let parent = LayoutHelpers.getBindingParent(node);
-  if (!parent) {
-    return false;
-  }
-
-  // If there is a shadowRoot and this is part of it then this
-  // is not native anonymous
-  return parent.shadowRoot && parent.shadowRoot.contains(node);
-};
-
-/**
- * Get the current zoom factor applied to the container window of a given node.
- * Container windows are used as a weakmap key to store the corresponding
- * nsIDOMWindowUtils instance to avoid querying it every time.
- *
- * @param {DOMNode|DOMWindow} The node for which the zoom factor should be
- * calculated, or its owner window.
- * @return {Number}
- */
-let windowUtils = new WeakMap;
-LayoutHelpers.getCurrentZoom = function(node) {
-  let win = node.self === node ? node : node.ownerDocument.defaultView;
-  let utils = windowUtils.get(win);
-  if (utils) {
-    return utils.fullZoom;
-  }
-
-  utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
-             .getInterface(Ci.nsIDOMWindowUtils);
-  windowUtils.set(win, utils);
-  return utils.fullZoom;
-};
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/layout/utils.js
@@ -0,0 +1,637 @@
+/* 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/. */
+
+"use strict";
+
+const { Ci } = require("chrome");
+const { memoize } = require("sdk/lang/functional");
+
+/**
+ * Returns the `DOMWindowUtils` for the window given.
+ *
+ * @param {DOMWindow} win
+ * @returns {DOMWindowUtils}
+ */
+const utilsFor = memoize(
+  (win) => win.QueryInterface(Ci.nsIInterfaceRequestor)
+              .getInterface(Ci.nsIDOMWindowUtils)
+);
+
+/**
+ * like win.top, but goes through mozbrowsers and mozapps iframes.
+ *
+ * @param {DOMWindow} win
+ * @return {DOMWindow}
+ */
+function getTopWindow(win) {
+  let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.nsIWebNavigation)
+                    .QueryInterface(Ci.nsIDocShell);
+
+  if (!docShell.isBrowserOrApp) {
+    return win.top;
+  }
+
+  let topDocShell = docShell.getSameTypeRootTreeItemIgnoreBrowserAndAppBoundaries();
+
+  return topDocShell
+          ? topDocShell.contentViewer.DOMDocument.defaultView
+          : null;
+}
+
+exports.getTopWindow = getTopWindow;
+
+/**
+ * Returns `true` is the window given is a top level window.
+ * like win.top === win, but goes through mozbrowsers and mozapps iframes.
+ *
+ * @param {DOMWindow} win
+ * @return {Boolean}
+ */
+const isTopWindow = win => win && getTopWindow(win) === win;
+exports.isTopWindow = isTopWindow;
+
+/**
+   * Check a window is part of the boundary window given.
+   *
+   * @param {DOMWindow} boundaryWindow
+   * @param {DOMWindow} win
+   * @return {Boolean}
+   */
+function isWindowIncluded(boundaryWindow, win) {
+  if (win === boundaryWindow) {
+    return true;
+  }
+
+  let parent = getParentWindow(win);
+
+  if (!parent || parent === win) {
+    return false;
+  }
+
+  return isWindowIncluded(boundaryWindow, parent);
+}
+exports.isWindowIncluded = isWindowIncluded;
+
+/**
+ * like win.parent, but goes through mozbrowsers and mozapps iframes.
+ *
+ * @param {DOMWindow} win
+ * @return {DOMWindow}
+ */
+function getParentWindow(win) {
+  if (isTopWindow(win)) {
+    return null;
+  }
+
+  let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                 .getInterface(Ci.nsIWebNavigation)
+                 .QueryInterface(Ci.nsIDocShell);
+
+  if (!docShell.isBrowserOrApp) {
+    return win.parent;
+  }
+
+  let parentDocShell = docShell.getSameTypeParentIgnoreBrowserAndAppBoundaries();
+
+  return parentDocShell
+          ? parentDocShell.contentViewer.DOMDocument.defaultView
+          : null;
+}
+
+exports.getParentWindow = getParentWindow;
+
+/**
+ * like win.frameElement, but goes through mozbrowsers and mozapps iframes.
+ *
+ * @param {DOMWindow} win
+ *        The window to get the frame for
+ * @return {DOMNode}
+ *         The element in which the window is embedded.
+ */
+const getFrameElement = (win) =>
+  isTopWindow(win) ? null : utilsFor(win).containerElement;
+exports.getFrameElement = getFrameElement;
+
+/**
+ * Get the x/y offsets for of all the parent frames of a given node, limited to
+ * the boundary window given.
+ *
+ * @param {DOMWindow} boundaryWindow
+ *        The window where to stop to iterate. If `null` is given, the top
+ *        window is used.
+ * @param {DOMNode} node
+ *        The node for which we are to get the offset
+ * @return {Array}
+ *         The frame offset [x, y]
+ */
+function getFrameOffsets(boundaryWindow, node) {
+  let xOffset = 0;
+  let yOffset = 0;
+  let frameWin = node.ownerDocument.defaultView;
+  let scale = getCurrentZoom(node);
+
+  if (boundaryWindow === null) {
+    boundaryWindow = getTopWindow(frameWin);
+  } else if (typeof boundaryWindow === "undefined") {
+    throw new Error("No `boundaryWindow` given. Use `null` for the default one.");
+  }
+
+  while (frameWin !== boundaryWindow) {
+
+    let frameElement = getFrameElement(frameWin);
+    if (!frameElement) {
+      break;
+    }
+
+    // We are in an iframe.
+    // We take into account the parent iframe position and its
+    // offset (borders and padding).
+    let frameRect = frameElement.getBoundingClientRect();
+
+    let [offsetTop, offsetLeft] =
+      getIframeContentOffset(frameElement);
+
+    xOffset += frameRect.left + offsetLeft;
+    yOffset += frameRect.top + offsetTop;
+
+    frameWin = getParentWindow(frameWin);
+  }
+
+  return [xOffset * scale, yOffset * scale];
+}
+
+/**
+ * Get box quads adjusted for iframes and zoom level.
+ *
+ * @param {DOMWindow} boundaryWindow
+ *        The window where to stop to iterate. If `null` is given, the top
+ *        window is used.
+ * @param {DOMNode} node
+ *        The node for which we are to get the box model region
+ *        quads.
+ * @param {String} region
+ *        The box model region to return: "content", "padding", "border" or
+ *        "margin".
+ * @return {Array}
+ *        An array of objects that have the same structure as quads returned by
+ *        getBoxQuads. An empty array if the node has no quads or is invalid.
+ */
+function getAdjustedQuads(boundaryWindow, node, region) {
+  if (!node || !node.getBoxQuads) {
+    return [];
+  }
+
+  let quads = node.getBoxQuads({
+    box: region
+  });
+
+  if (!quads.length) {
+    return [];
+  }
+
+  let [xOffset, yOffset] = getFrameOffsets(boundaryWindow, node);
+  let scale = getCurrentZoom(node);
+
+  let adjustedQuads = [];
+  for (let quad of quads) {
+    adjustedQuads.push({
+      p1: {
+        w: quad.p1.w * scale,
+        x: quad.p1.x * scale + xOffset,
+        y: quad.p1.y * scale + yOffset,
+        z: quad.p1.z * scale
+      },
+      p2: {
+        w: quad.p2.w * scale,
+        x: quad.p2.x * scale + xOffset,
+        y: quad.p2.y * scale + yOffset,
+        z: quad.p2.z * scale
+      },
+      p3: {
+        w: quad.p3.w * scale,
+        x: quad.p3.x * scale + xOffset,
+        y: quad.p3.y * scale + yOffset,
+        z: quad.p3.z * scale
+      },
+      p4: {
+        w: quad.p4.w * scale,
+        x: quad.p4.x * scale + xOffset,
+        y: quad.p4.y * scale + yOffset,
+        z: quad.p4.z * scale
+      },
+      bounds: {
+        bottom: quad.bounds.bottom * scale + yOffset,
+        height: quad.bounds.height * scale,
+        left: quad.bounds.left * scale + xOffset,
+        right: quad.bounds.right * scale + xOffset,
+        top: quad.bounds.top * scale + yOffset,
+        width: quad.bounds.width * scale,
+        x: quad.bounds.x * scale + xOffset,
+        y: quad.bounds.y * scale + yOffset
+      }
+    });
+  }
+
+  return adjustedQuads;
+}
+exports.getAdjustedQuads = getAdjustedQuads;
+
+/**
+ * Compute the absolute position and the dimensions of a node, relativalely
+ * to the root window.
+
+ * @param {DOMWindow} boundaryWindow
+ *        The window where to stop to iterate. If `null` is given, the top
+ *        window is used.
+ * @param {DOMNode} aNode
+ *        a DOM element to get the bounds for
+ * @param {DOMWindow} aContentWindow
+ *        the content window holding the node
+ * @return {Object}
+ *         A rect object with the {top, left, width, height} properties
+ */
+function getRect(boundaryWindow, aNode, aContentWindow) {
+  let frameWin = aNode.ownerDocument.defaultView;
+  let clientRect = aNode.getBoundingClientRect();
+
+  if (boundaryWindow === null) {
+    boundaryWindow = getTopWindow(frameWin);
+  } else if (typeof boundaryWindow === "undefined") {
+    throw new Error("No `boundaryWindow` given. Use `null` for the default one.");
+  }
+
+  // Go up in the tree of frames to determine the correct rectangle.
+  // clientRect is read-only, we need to be able to change properties.
+  let rect = {
+    top: clientRect.top + aContentWindow.pageYOffset,
+    left: clientRect.left + aContentWindow.pageXOffset,
+    width: clientRect.width,
+    height: clientRect.height
+  };
+
+  // We iterate through all the parent windows.
+  while (frameWin !== boundaryWindow) {
+    let frameElement = getFrameElement(frameWin);
+    if (!frameElement) {
+      break;
+    }
+
+    // We are in an iframe.
+    // We take into account the parent iframe position and its
+    // offset (borders and padding).
+    let frameRect = frameElement.getBoundingClientRect();
+
+    let [offsetTop, offsetLeft] =
+      getIframeContentOffset(frameElement);
+
+    rect.top += frameRect.top + offsetTop;
+    rect.left += frameRect.left + offsetLeft;
+
+    frameWin = getParentWindow(frameWin);
+  }
+
+  return rect;
+};
+exports.getRect = getRect;
+
+/**
+ * Get the 4 bounding points for a node taking iframes into account.
+ * Note that for transformed nodes, this will return the untransformed bound.
+ *
+ * @param {DOMWindow} boundaryWindow
+ *        The window where to stop to iterate. If `null` is given, the top
+ *        window is used.
+ * @param {DOMNode} node
+ * @return {Object}
+ *         An object with p1,p2,p3,p4 properties being {x,y} objects
+ */
+function getNodeBounds(boundaryWindow, node) {
+  if (!node) {
+    return;
+  }
+
+  let scale = getCurrentZoom(node);
+
+  // Find out the offset of the node in its current frame
+  let offsetLeft = 0;
+  let offsetTop = 0;
+  let el = node;
+  while (el && el.parentNode) {
+    offsetLeft += el.offsetLeft;
+    offsetTop += el.offsetTop;
+    el = el.offsetParent;
+  }
+
+  // Also take scrolled containers into account
+  el = node;
+  while (el && el.parentNode) {
+    if (el.scrollTop) {
+      offsetTop -= el.scrollTop;
+    }
+    if (el.scrollLeft) {
+      offsetLeft -= el.scrollLeft;
+    }
+    el = el.parentNode;
+  }
+
+  // And add the potential frame offset if the node is nested
+  let [xOffset, yOffset] = getFrameOffsets(boundaryWindow, node);
+  xOffset += offsetLeft;
+  yOffset += offsetTop;
+
+  xOffset *= scale;
+  yOffset *= scale;
+
+  // Get the width and height
+  let width = node.offsetWidth * scale;
+  let height = node.offsetHeight * scale;
+
+  return {
+    p1: {x: xOffset, y: yOffset},
+    p2: {x: xOffset + width, y: yOffset},
+    p3: {x: xOffset + width, y: yOffset + height},
+    p4: {x: xOffset, y: yOffset + height}
+  };
+}
+exports.getNodeBounds = getNodeBounds;
+
+/**
+ * Returns iframe content offset (iframe border + padding).
+ * Note: this function shouldn't need to exist, had the platform provided a
+ * suitable API for determining the offset between the iframe's content and
+ * its bounding client rect. Bug 626359 should provide us with such an API.
+ *
+ * @param {DOMNode} aIframe
+ *        The iframe.
+ * @return {Array} [offsetTop, offsetLeft]
+ *         offsetTop is the distance from the top of the iframe and the top of
+ *         the content document.
+ *         offsetLeft is the distance from the left of the iframe and the left
+ *         of the content document.
+ */
+function getIframeContentOffset(aIframe) {
+  let style = aIframe.contentWindow.getComputedStyle(aIframe, null);
+
+  // In some cases, the computed style is null
+  if (!style) {
+    return [0, 0];
+  }
+
+  let paddingTop = parseInt(style.getPropertyValue("padding-top"));
+  let paddingLeft = parseInt(style.getPropertyValue("padding-left"));
+
+  let borderTop = parseInt(style.getPropertyValue("border-top-width"));
+  let borderLeft = parseInt(style.getPropertyValue("border-left-width"));
+
+  return [borderTop + paddingTop, borderLeft + paddingLeft];
+}
+exports.getIframeContentOffset = getIframeContentOffset;
+
+/**
+ * Find an element from the given coordinates. This method descends through
+ * frames to find the element the user clicked inside frames.
+ *
+ * @param {DOMDocument} aDocument
+ *        The document to look into.
+ * @param {Number} aX
+ * @param {Number} aY
+ * @return {DOMNode}
+ *         the element node found at the given coordinates, or null if no node
+ *         was found
+ */
+function getElementFromPoint(aDocument, aX, aY) {
+  let node = aDocument.elementFromPoint(aX, aY);
+  if (node && node.contentDocument) {
+    if (node instanceof Ci.nsIDOMHTMLIFrameElement) {
+      let rect = node.getBoundingClientRect();
+
+      // Gap between the iframe and its content window.
+      let [offsetTop, offsetLeft] = getIframeContentOffset(node);
+
+      aX -= rect.left + offsetLeft;
+      aY -= rect.top + offsetTop;
+
+      if (aX < 0 || aY < 0) {
+        // Didn't reach the content document, still over the iframe.
+        return node;
+      }
+    }
+    if (node instanceof Ci.nsIDOMHTMLIFrameElement ||
+        node instanceof Ci.nsIDOMHTMLFrameElement) {
+      let subnode = getElementFromPoint(node.contentDocument, aX, aY);
+      if (subnode) {
+        node = subnode;
+      }
+    }
+  }
+  return node;
+}
+exports.getElementFromPoint = getElementFromPoint;
+
+/**
+ * Scroll the document so that the element "elem" appears in the viewport.
+ *
+ * @param {DOMNode} elem
+ *        The element that needs to appear in the viewport.
+ * @param {Boolean} centered
+ *        true if you want it centered, false if you want it to appear on the
+ *        top of the viewport. It is true by default, and that is usually what
+ *        you want.
+ */
+function scrollIntoViewIfNeeded(elem, centered=true) {
+  let win = elem.ownerDocument.defaultView;
+  let clientRect = elem.getBoundingClientRect();
+
+  // The following are always from the {top, bottom}
+  // of the viewport, to the {top, …} of the box.
+  // Think of them as geometrical vectors, it helps.
+  // The origin is at the top left.
+
+  let topToBottom = clientRect.bottom;
+  let bottomToTop = clientRect.top - win.innerHeight;
+  let yAllowed = true;  // We allow one translation on the y axis.
+
+  // Whatever `centered` is, the behavior is the same if the box is
+  // (even partially) visible.
+  if ((topToBottom > 0 || !centered) && topToBottom <= elem.offsetHeight) {
+    win.scrollBy(0, topToBottom - elem.offsetHeight);
+    yAllowed = false;
+  } else if ((bottomToTop < 0 || !centered) && bottomToTop >= -elem.offsetHeight) {
+    win.scrollBy(0, bottomToTop + elem.offsetHeight);
+    yAllowed = false;
+  }
+
+  // If we want it centered, and the box is completely hidden,
+  // then we center it explicitly.
+  if (centered) {
+    if (yAllowed && (topToBottom <= 0 || bottomToTop >= 0)) {
+      win.scroll(win.scrollX,
+                 win.scrollY + clientRect.top
+                 - (win.innerHeight - elem.offsetHeight) / 2);
+    }
+  }
+}
+exports.scrollIntoViewIfNeeded = scrollIntoViewIfNeeded;
+
+/**
+ * Check if a node and its document are still alive
+ * and attached to the window.
+ *
+ * @param {DOMNode} aNode
+ * @return {Boolean}
+ */
+function isNodeConnected(aNode) {
+  try {
+    let connected = (aNode.ownerDocument && aNode.ownerDocument.defaultView &&
+                    !(aNode.compareDocumentPosition(aNode.ownerDocument.documentElement) &
+                    aNode.DOCUMENT_POSITION_DISCONNECTED));
+    return connected;
+  } catch (e) {
+    // "can't access dead object" error
+    return false;
+  }
+}
+exports.isNodeConnected = isNodeConnected;
+
+/**
+ * Traverse getBindingParent until arriving upon the bound element
+ * responsible for the generation of the specified node.
+ * See https://developer.mozilla.org/en-US/docs/XBL/XBL_1.0_Reference/DOM_Interfaces#getBindingParent.
+ *
+ * @param {DOMNode} node
+ * @return {DOMNode}
+ *         If node is not anonymous, this will return node. Otherwise,
+ *         it will return the bound element
+ *
+ */
+function getRootBindingParent(node) {
+  let parent;
+  let doc = node.ownerDocument;
+  if (!doc) {
+    return node;
+  }
+  while ((parent = doc.getBindingParent(node))) {
+    node = parent;
+  }
+  return node;
+}
+exports.getRootBindingParent = getRootBindingParent;
+
+function getBindingParent(node) {
+  let doc = node.ownerDocument;
+  if (!doc) {
+    return null;
+  }
+
+  // If there is no binding parent then it is not anonymous.
+  let parent = doc.getBindingParent(node);
+  if (!parent) {
+    return null;
+  }
+
+  return parent;
+}
+exports.getBindingParent = getBindingParent;
+
+/**
+ * Determine whether a node is anonymous by determining if there
+ * is a bindingParent.
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ *
+ */
+const isAnonymous = (node) => getRootBindingParent(node) !== node;
+exports.isAnonymous = isAnonymous;
+
+/**
+ * Determine whether a node has a bindingParent.
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ *
+ */
+const hasBindingParent = (node) => !!getBindingParent(node);
+
+/**
+ * Determine whether a node is native anonymous content (as opposed
+ * to XBL anonymous or shadow DOM).
+ * Native anonymous content includes elements like internals to form
+ * controls and ::before/::after.
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ *
+ */
+const isNativeAnonymous = (node) =>
+  hasBindingParent(node) && !(isXBLAnonymous(node) || isShadowAnonymous(node));
+
+exports.isNativeAnonymous = isNativeAnonymous;
+
+/**
+ * Determine whether a node is XBL anonymous content (as opposed
+ * to native anonymous or shadow DOM).
+ * See https://developer.mozilla.org/en-US/docs/XBL/XBL_1.0_Reference/Anonymous_Content.
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ *
+ */
+function isXBLAnonymous(node) {
+  let parent = getBindingParent(node);
+  if (!parent) {
+    return false;
+  }
+
+  // Shadow nodes also show up in getAnonymousNodes, so return false.
+  if (parent.shadowRoot && parent.shadowRoot.contains(node)) {
+    return false;
+  }
+
+  let anonNodes = [...node.ownerDocument.getAnonymousNodes(parent) || []];
+  return anonNodes.indexOf(node) > -1;
+}
+exports.isXBLAnonymous = isXBLAnonymous;
+
+/**
+ * Determine whether a node is a child of a shadow root.
+ * See https://w3c.github.io/webcomponents/spec/shadow/
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ */
+function isShadowAnonymous(node) {
+  let parent = getBindingParent(node);
+  if (!parent) {
+    return false;
+  }
+
+  // If there is a shadowRoot and this is part of it then this
+  // is not native anonymous
+  return parent.shadowRoot && parent.shadowRoot.contains(node);
+}
+exports.isShadowAnonymous = isShadowAnonymous;
+
+/**
+ * Get the current zoom factor applied to the container window of a given node.
+ * Container windows are used as a weakmap key to store the corresponding
+ * nsIDOMWindowUtils instance to avoid querying it every time.
+ *
+ * @param {DOMNode|DOMWindow}
+ *        The node for which the zoom factor should be calculated, or its
+ *        owner window.
+ * @return {Number}
+ */
+function getCurrentZoom(node) {
+  let win = node instanceof Ci.nsIDOMNode ? node.ownerDocument.defaultView :
+            node instanceof Ci.nsIDOMWindow ? node : null;
+
+  if (!win) {
+    throw new Error("Unable to get the zoom from the given argument.");
+  }
+
+  return utilsFor(win).fullZoom;
+}
+exports.getCurrentZoom = getCurrentZoom;
--- a/toolkit/devtools/moz.build
+++ b/toolkit/devtools/moz.build
@@ -32,22 +32,25 @@ XPCSHELL_TESTS_MANIFESTS += ['tests/unit
 EXTRA_JS_MODULES.devtools += [
     'async-utils.js',
     'content-observer.js',
     'css-color.js',
     'deprecated-sync-thenables.js',
     'DevToolsUtils.js',
     'event-emitter.js',
     'event-parsers.js',
-    'layout-helpers.js',
     'output-parser.js',
     'path.js',
     'worker-loader.js',
 ]
 
 EXTRA_JS_MODULES.devtools += [
     'Console.jsm',
     'Loader.jsm',
 ]
 
 EXTRA_JS_MODULES.devtools.server.actors += [
     'server/actors/highlighter.css'
 ]
+
+EXTRA_JS_MODULES.devtools.layout += [
+    'layout/utils.js'
+]
--- a/toolkit/devtools/server/actors/highlighter.js
+++ b/toolkit/devtools/server/actors/highlighter.js
@@ -1,22 +1,23 @@
 /* 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 LayoutHelpers, DOMUtils, CssLogic, setIgnoreLayoutChanges */
+/* globals DOMUtils, CssLogic, setIgnoreLayoutChanges */
 
 "use strict";
 
-const {Cu, Cc, Ci} = require("chrome");
-const protocol = require("devtools/server/protocol");
-const {Arg, Option, method, RetVal} = protocol;
+const { Cu, Cc, Ci } = require("chrome");
+const { extend } = require("sdk/core/heritage");
+const { getCurrentZoom, getAdjustedQuads, getNodeBounds, getRootBindingParent,
+  isWindowIncluded } = require("devtools/toolkit/layout/utils");
+const EventEmitter = require("devtools/toolkit/event-emitter");
 const events = require("sdk/event/core");
-const Heritage = require("sdk/core/heritage");
-const EventEmitter = require("devtools/toolkit/event-emitter");
-const LayoutHelpers = require("devtools/toolkit/layout-helpers");
+const protocol = require("devtools/server/protocol");
+const { Arg, Option, method, RetVal } = protocol;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 loader.lazyRequireGetter(this, "CssLogic",
   "devtools/styleinspector/css-logic", true);
 loader.lazyRequireGetter(this, "setIgnoreLayoutChanges",
   "devtools/server/actors/layout", true);
 loader.lazyGetter(this, "DOMUtils", function() {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
@@ -140,17 +141,16 @@ let HighlighterActor = exports.Highlight
     this._tabActor = this._inspector.tabActor;
     this._highlighterEnv = new HighlighterEnvironment();
     this._highlighterEnv.initFromTabActor(this._tabActor);
 
     this._highlighterReady = this._highlighterReady.bind(this);
     this._highlighterHidden = this._highlighterHidden.bind(this);
     this._onNavigate = this._onNavigate.bind(this);
 
-    this._layoutHelpers = new LayoutHelpers(this._tabActor.window);
     this._createHighlighter();
 
     // Listen to navigation events to switch from the BoxModelHighlighter to the
     // SimpleOutlineHighlighter, and back, if the top level window changes.
     events.on(this._tabActor, "navigate", this._onNavigate);
   },
 
   get conn() {
@@ -213,17 +213,16 @@ let HighlighterActor = exports.Highlight
 
     this._highlighterEnv.destroy();
     this._highlighterEnv = null;
 
     this._autohide = null;
     this._inspector = null;
     this._walker = null;
     this._tabActor = null;
-    this._layoutHelpers = null;
   },
 
   /**
    * Display the box model highlighting on a given NodeActor.
    * There is only one instance of the box model highlighter, so calling this
    * method several times won't display several highlighters, it will just move
    * the highlighter instance to these nodes.
    *
@@ -253,16 +252,36 @@ let HighlighterActor = exports.Highlight
    */
   hideBoxModel: method(function() {
     this._highlighter.hide();
   }, {
     request: {}
   }),
 
   /**
+   * Returns `true` if the event was dispatched from a window included in
+   * the current highlighter environment; or if the highlighter environment has
+   * chrome privileges
+   *
+   * The method is specifically useful on B2G, where we do not want the events
+   * from the app or main process to be processed when we're inspecting the
+   * content.
+   *
+   * @param {Event} event
+   *          The event to allow
+   * @return {Boolean}
+   */
+  _isEventAllowed: function({view}) {
+    let { window } = this._highlighterEnv;
+
+    return window instanceof Ci.nsIDOMChromeWindow ||
+          isWindowIncluded(window, view);
+  },
+
+  /**
    * Pick a node on click, and highlight hovered nodes in the process.
    *
    * This method doesn't respond anything interesting, however, it starts
    * mousemove, and click listeners on the content document to fire
    * events and let connected clients know when nodes are hovered over or
    * clicked.
    *
    * Once a node is picked, events will cease, and listeners will be removed.
@@ -279,45 +298,60 @@ let HighlighterActor = exports.Highlight
 
     this._preventContentEvent = event => {
       event.stopPropagation();
       event.preventDefault();
     };
 
     this._onPick = event => {
       this._preventContentEvent(event);
+
+      if (!this._isEventAllowed(event)) {
+        return;
+      }
+
       this._stopPickerListeners();
       this._isPicking = false;
       if (this._autohide) {
         this._tabActor.window.setTimeout(() => {
           this._highlighter.hide();
         }, HIGHLIGHTER_PICKED_TIMER);
       }
       if (!this._currentNode) {
         this._currentNode = this._findAndAttachElement(event);
       }
       events.emit(this._walker, "picker-node-picked", this._currentNode);
     };
 
     this._onHovered = event => {
       this._preventContentEvent(event);
+
+      if (!this._isEventAllowed(event)) {
+        return;
+      }
+
       this._currentNode = this._findAndAttachElement(event);
       if (this._hoveredNode !== this._currentNode.node) {
         this._highlighter.show(this._currentNode.node.rawNode);
         events.emit(this._walker, "picker-node-hovered", this._currentNode);
         this._hoveredNode = this._currentNode.node;
       }
     };
 
     this._onKey = event => {
       if (!this._currentNode || !this._isPicking) {
         return;
       }
 
       this._preventContentEvent(event);
+
+      if (!this._isEventAllowed(event)) {
+        return;
+      }
+
       let currentNode = this._currentNode.node.rawNode;
 
       /**
        * KEY: Action/scope
        * LEFT_KEY: wider or parent
        * RIGHT_KEY: narrower or child
        * ENTER/CARRIAGE_RETURN: Picks currentNode
        * ESC: Cancels picker, picks currentNode
@@ -837,17 +871,17 @@ CanvasFrameAnonymousContentHelper.protot
    * Note that if the matching element already has an inline style attribute, it
    * *won't* be preserved.
    *
    * @param {DOMNode} node This node is used to determine which container window
    * should be used to read the current zoom value.
    * @param {String} id The ID of the root element inserted with this API.
    */
   scaleRootElement: function(node, id) {
-    let zoom = LayoutHelpers.getCurrentZoom(node);
+    let zoom = getCurrentZoom(node);
     let value = "position:absolute;width:100%;height:100%;";
 
     if (zoom !== 1) {
       value = "position:absolute;";
       value += "transform-origin:top left;transform:scale(" + (1 / zoom) + ");";
       value += "width:" + (100 * zoom) + "%;height:" + (100 * zoom) + "%;";
     }
 
@@ -879,18 +913,16 @@ function AutoRefreshHighlighter(highligh
   EventEmitter.decorate(this);
 
   this.highlighterEnv = highlighterEnv;
   this.win = highlighterEnv.window;
 
   this.currentNode = null;
   this.currentQuads = {};
 
-  this.layoutHelpers = new LayoutHelpers(this.win);
-
   this.update = this.update.bind(this);
 }
 
 AutoRefreshHighlighter.prototype = {
   /**
    * Show the highlighter on a given node
    * @param {DOMNode} node
    * @param {Object} options
@@ -959,17 +991,18 @@ AutoRefreshHighlighter.prototype = {
     return true;
   },
 
   /**
    * Update the stored box quads by reading the current node's box quads.
    */
   _updateAdjustedQuads: function() {
     for (let region of BOX_MODEL_REGIONS) {
-      this.currentQuads[region] = this.layoutHelpers.getAdjustedQuads(
+      this.currentQuads[region] = getAdjustedQuads(
+        this.win,
         this.currentNode, region);
     }
   },
 
   /**
    * Update the knowledge we have of the current node's boxquads and return true
    * if any of the points x/y or bounds have change since.
    * @return {Boolean}
@@ -1029,17 +1062,16 @@ AutoRefreshHighlighter.prototype = {
   },
 
   destroy: function() {
     this.hide();
 
     this.highlighterEnv = null;
     this.win = null;
     this.currentNode = null;
-    this.layoutHelpers = null;
   }
 };
 
 /**
  * The BoxModelHighlighter draws the box model regions on top of a node.
  * If the node is a block box, then each region will be displayed as 1 polygon.
  * If the node is an inline box though, each region may be represented by 1 or
  * more polygons, depending on how many line boxes the inline element has.
@@ -1109,17 +1141,17 @@ function BoxModelHighlighter(highlighter
    * Optionally customize each region's fill color by adding an entry to the
    * regionFill property: `highlighter.regionFill.margin = "red";
    */
   this.regionFill = {};
 
   this._currentNode = null;
 }
 
-BoxModelHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype, {
+BoxModelHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
   typeName: "BoxModelHighlighter",
 
   ID_CLASS_PREFIX: "box-model-",
 
   get currentNode() {
     return this._currentNode;
   },
 
@@ -1701,18 +1733,18 @@ BoxModelHighlighter.prototype = Heritage
     this._moveInfobar();
   },
 
   /**
    * Move the Infobar to the right place in the highlighter.
    */
   _moveInfobar: function() {
     let bounds = this._getOuterBounds();
-    let winHeight = this.win.innerHeight * LayoutHelpers.getCurrentZoom(this.win);
-    let winWidth = this.win.innerWidth * LayoutHelpers.getCurrentZoom(this.win);
+    let winHeight = this.win.innerHeight * getCurrentZoom(this.win);
+    let winWidth = this.win.innerWidth * getCurrentZoom(this.win);
 
     // Ensure that containerBottom and containerTop are at least zero to avoid
     // showing tooltips outside the viewport.
     let containerBottom = Math.max(0, bounds.bottom) + NODE_INFOBAR_ARROW_SIZE;
     let containerTop = Math.min(winHeight, bounds.top);
     let container = this.getElement("nodeinfobar-container");
 
     // Can the bar be above the node?
@@ -1764,17 +1796,17 @@ function CssTransformHighlighter(highlig
   AutoRefreshHighlighter.call(this, highlighterEnv);
 
   this.markup = new CanvasFrameAnonymousContentHelper(this.highlighterEnv,
     this._buildMarkup.bind(this));
 }
 
 let MARKER_COUNTER = 1;
 
-CssTransformHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype, {
+CssTransformHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
   typeName: "CssTransformHighlighter",
 
   ID_CLASS_PREFIX: "css-transform-",
 
   _buildMarkup: function() {
     let container = createNode(this.win, {
       attributes: {
         "class": "highlighter-container"
@@ -1941,17 +1973,17 @@ CssTransformHighlighter.prototype = Heri
         quads[0].bounds.width <= 0 || quads[0].bounds.height <= 0) {
       this._hideShapes();
       return false;
     }
 
     let [quad] = quads;
 
     // Getting the points for the untransformed shape
-    let untransformedQuad = this.layoutHelpers.getNodeBounds(this.currentNode);
+    let untransformedQuad = getNodeBounds(this.win, this.currentNode);
 
     this._setPolygonPoints(quad, "transformed");
     this._setPolygonPoints(untransformedQuad, "untransformed");
     for (let nb of ["1", "2", "3", "4"]) {
       this._setLinePoints(untransformedQuad["p" + nb], quad["p" + nb], "line" + nb);
     }
 
     // Adapt to the current zoom
@@ -2059,17 +2091,16 @@ exports.SelectorHighlighter = SelectorHi
  * The RectHighlighter is a class that draws a rectangle highlighter at specific
  * coordinates.
  * It does *not* highlight DOM nodes, but rects.
  * It also does *not* update dynamically, it only highlights a rect and remains
  * there as long as it is shown.
  */
 function RectHighlighter(highlighterEnv) {
   this.win = highlighterEnv.window;
-  this.layoutHelpers = new LayoutHelpers(this.win);
   this.markup = new CanvasFrameAnonymousContentHelper(highlighterEnv,
     this._buildMarkup.bind(this));
 }
 
 RectHighlighter.prototype = {
   typeName: "RectHighlighter",
 
   _buildMarkup: function() {
@@ -2080,17 +2111,16 @@ RectHighlighter.prototype = {
     container.innerHTML = "<div id=\"highlighted-rect\" " +
                           "class=\"highlighted-rect\" hidden=\"true\">";
 
     return container;
   },
 
   destroy: function() {
     this.win = null;
-    this.layoutHelpers = null;
     this.markup.destroy();
   },
 
   getElement: function(id) {
     return this.markup.getElement(id);
   },
 
   _hasValidOptions: function(options) {
@@ -2116,17 +2146,17 @@ RectHighlighter.prototype = {
     if (!this._hasValidOptions(options) || !node || !node.ownerDocument) {
       this.hide();
       return false;
     }
 
     let contextNode = node.ownerDocument.documentElement;
 
     // Caculate the absolute rect based on the context node's adjusted quads.
-    let quads = this.layoutHelpers.getAdjustedQuads(contextNode);
+    let quads = getAdjustedQuads(this.win, contextNode);
     if (!quads.length) {
       this.hide();
       return false;
     }
 
     let {bounds} = quads[0];
     let x = "left:" + (bounds.x + options.rect.x) + "px;";
     let y = "top:" + (bounds.y + options.rect.y) + "px;";
@@ -2237,17 +2267,17 @@ function GeometryEditorHighlighter(highl
 
   // The list of element geometry properties that can be set.
   this.definedProperties = new Map();
 
   this.markup = new CanvasFrameAnonymousContentHelper(highlighterEnv,
     this._buildMarkup.bind(this));
 }
 
-GeometryEditorHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype, {
+GeometryEditorHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
   typeName: "GeometryEditorHighlighter",
 
   ID_CLASS_PREFIX: "geometry-editor-",
 
   _buildMarkup: function() {
     let container = createNode(this.win, {
       attributes: {"class": "highlighter-container"}
     });
@@ -2532,18 +2562,18 @@ GeometryEditorHighlighter.prototype = He
    *   is calculated from),
    * - the node has no offset parent at all: the offsetParent rectangle is
    *   hidden.
    */
   updateOffsetParent: function() {
     // Get the offsetParent, if any.
     this.offsetParent = getOffsetParent(this.currentNode);
     // And the offsetParent quads.
-    this.parentQuads = this.layoutHelpers
-                      .getAdjustedQuads(this.offsetParent.element, "padding");
+    this.parentQuads = getAdjustedQuads(
+        this.win, this.offsetParent.element, "padding");
 
     let el = this.getElement("offset-parent");
 
     let isPositioned = this.computedStyle.position === "absolute" ||
                        this.computedStyle.position === "fixed";
     let isRelative = this.computedStyle.position === "relative";
     let isHighlighted = false;
 
@@ -2934,17 +2964,17 @@ RulersHighlighter.prototype = {
                         .setAttribute("transform", `translate(0, ${-scrollY})`);
   },
 
   _update: function() {
     let { window } = this.env;
 
     setIgnoreLayoutChanges(true);
 
-    let zoom = LayoutHelpers.getCurrentZoom(window);
+    let zoom = getCurrentZoom(window);
     let isZoomChanged = zoom !== this._zoom;
 
     if (isZoomChanged) {
       this._zoom = zoom;
       this.updateViewport();
     }
 
     setIgnoreLayoutChanges(false, window.document.documentElement);
@@ -3066,17 +3096,17 @@ function isNodeValid(node) {
   // Is the document inaccessible?
   let doc = node.ownerDocument;
   if (!doc || !doc.defaultView) {
     return false;
   }
 
   // Is the node connected to the document? Using getBindingParent adds
   // support for anonymous elements generated by a node in the document.
-  let bindingParent = LayoutHelpers.getRootBindingParent(node);
+  let bindingParent = getRootBindingParent(node);
   if (!doc.documentElement.contains(bindingParent)) {
     return false;
   }
 
   return true;
 }
 
 /**
--- a/toolkit/devtools/server/actors/inspector.js
+++ b/toolkit/devtools/server/actors/inspector.js
@@ -63,19 +63,25 @@ const events = require("sdk/event/core")
 const {Unknown} = require("sdk/platform/xpcom");
 const {Class} = require("sdk/core/heritage");
 const {PageStyleActor, getFontPreviewData} = require("devtools/server/actors/styles");
 const {
   HighlighterActor,
   CustomHighlighterActor,
   isTypeRegistered,
 } = require("devtools/server/actors/highlighter");
+const {
+  isAnonymous,
+  isNativeAnonymous,
+  isXBLAnonymous,
+  isShadowAnonymous,
+  getFrameElement
+} = require("devtools/toolkit/layout/utils");
 const {getLayoutChangesObserver, releaseLayoutChangesObserver} =
   require("devtools/server/actors/layout");
-const LayoutHelpers = require("devtools/toolkit/layout-helpers");
 
 const {EventParsers} = require("devtools/toolkit/event-parsers");
 
 const FONT_FAMILY_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog";
 const FONT_FAMILY_PREVIEW_TEXT_SIZE = 20;
 const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
 const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
@@ -259,20 +265,20 @@ var NodeActor = exports.NodeActor = prot
       // doctype attributes
       name: this.rawNode.name,
       publicId: this.rawNode.publicId,
       systemId: this.rawNode.systemId,
 
       attrs: this.writeAttrs(),
       isBeforePseudoElement: this.isBeforePseudoElement,
       isAfterPseudoElement: this.isAfterPseudoElement,
-      isAnonymous: LayoutHelpers.isAnonymous(this.rawNode),
-      isNativeAnonymous: LayoutHelpers.isNativeAnonymous(this.rawNode),
-      isXBLAnonymous: LayoutHelpers.isXBLAnonymous(this.rawNode),
-      isShadowAnonymous: LayoutHelpers.isShadowAnonymous(this.rawNode),
+      isAnonymous: isAnonymous(this.rawNode),
+      isNativeAnonymous: isNativeAnonymous(this.rawNode),
+      isXBLAnonymous: isXBLAnonymous(this.rawNode),
+      isShadowAnonymous: isShadowAnonymous(this.rawNode),
       pseudoClassLocks: this.writePseudoClassLocks(),
 
       isDisplayed: this.isDisplayed,
 
       hasEventListeners: this._hasEventListeners,
     };
 
     if (this.isDocumentElement()) {
@@ -1285,18 +1291,16 @@ var WalkerActor = protocol.ActorClass({
     this.tabActor = tabActor;
     this.rootWin = tabActor.window;
     this.rootDoc = this.rootWin.document;
     this._refMap = new Map();
     this._pendingMutations = [];
     this._activePseudoClassLocks = new Set();
     this.showAllAnonymousContent = options.showAllAnonymousContent;
 
-    this.layoutHelpers = new LayoutHelpers(this.rootWin);
-
     // Nodes which have been removed from the client's known
     // ownership tree are considered "orphaned", and stored in
     // this set.
     this._orphaned = new Set();
 
     // The client can tell the walker that it is interested in a node
     // even when it is orphaned with the `retainNode` method.  This
     // list contains orphaned nodes that were so retained.
@@ -1486,17 +1490,17 @@ var WalkerActor = protocol.ActorClass({
   attachElements: function(nodes) {
     let nodeActors = [];
     let newParents = new Set();
     for (let node of nodes) {
       if (!(node instanceof NodeActor)) {
         // If an anonymous node was passed in and we aren't supposed to know
         // about it, then consult with the document walker as the source of
         // truth about which elements exist.
-        if (!this.showAllAnonymousContent && LayoutHelpers.isAnonymous(node)) {
+        if (!this.showAllAnonymousContent && isAnonymous(node)) {
           node = this.getDocumentWalker(node).currentNode;
         }
 
         node = this._ref(node);
       }
 
       this.ensurePathToRoot(node, newParents);
       // If nodes may be an array of raw nodes, we're sure to only have
@@ -2873,17 +2877,17 @@ var WalkerActor = protocol.ActorClass({
       this.rootDoc = window.document;
       this.rootNode = this.document();
       this.queueMutation({
         type: "newRoot",
         target: this.rootNode.form()
       });
       return;
     }
-    let frame = this.layoutHelpers.getFrameElement(window);
+    let frame = getFrameElement(window);
     let frameActor = this._refMap.get(frame);
     if (!frameActor) {
       return;
     }
 
     this.queueMutation({
       type: "frameLoad",
       target: frameActor.actorID,
@@ -2900,17 +2904,17 @@ var WalkerActor = protocol.ActorClass({
 
   // Returns true if domNode is in window or a subframe.
   _childOfWindow: function(window, domNode) {
     let win = nodeDocument(domNode).defaultView;
     while (win) {
       if (win === window) {
         return true;
       }
-      win = this.layoutHelpers.getFrameElement(win);
+      win = getFrameElement(win);
     }
     return false;
   },
 
   onFrameUnload: function({ window }) {
     // Any retained orphans that belong to this document
     // or its children need to be released, and a mutation sent
     // to notify of that.
@@ -3901,18 +3905,18 @@ function standardTreeWalkerFilter(aNode)
 
   // Ignore empty whitespace text nodes.
   if (aNode.nodeType == Ci.nsIDOMNode.TEXT_NODE &&
       !/[^\s]/.exec(aNode.nodeValue)) {
     return Ci.nsIDOMNodeFilter.FILTER_SKIP;
   }
 
   // Ignore all native and XBL anonymous content inside a non-XUL document
-  if (!isInXULDocument(aNode) && (LayoutHelpers.isXBLAnonymous(aNode) ||
-                                  LayoutHelpers.isNativeAnonymous(aNode))) {
+  if (!isInXULDocument(aNode) && (isXBLAnonymous(aNode) ||
+                                  isNativeAnonymous(aNode))) {
     // Note: this will skip inspecting the contents of feedSubscribeLine since
     // that's XUL content injected in an HTML document, but we need to because
     // this also skips many other elements that need to be skipped - like form
     // controls, scrollbars, video controls, etc (see bug 1187482).
     return Ci.nsIDOMNodeFilter.FILTER_SKIP;
   }
 
   return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
--- a/toolkit/devtools/server/actors/storage.js
+++ b/toolkit/devtools/server/actors/storage.js
@@ -8,17 +8,17 @@ const {Cc, Ci} = require("chrome");
 const events = require("sdk/event/core");
 const protocol = require("devtools/server/protocol");
 const {async} = require("devtools/async-utils");
 const {Arg, method, RetVal, types} = protocol;
 const {LongStringActor} = require("devtools/server/actors/string");
 const {DebuggerServer} = require("devtools/server/main");
 const Services = require("Services");
 const promise = require("promise");
-const LayoutHelpers = require("devtools/toolkit/layout-helpers");
+const {isWindowIncluded} = require("devtools/toolkit/layout/utils");
 const { setTimeout, clearTimeout } = require("sdk/timers");
 
 loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
 loader.lazyImporter(this, "Sqlite", "resource://gre/modules/Sqlite.jsm");
 
 let gTrackedMessageManager = new Map();
 
 // Maximum number of cookies/local storage key-value-pairs that can be sent
@@ -1688,26 +1688,21 @@ let StorageActor = exports.StorageActor 
     this.onPageChange = this.onPageChange.bind(this);
 
     let handler = tabActor.chromeEventHandler;
     handler.addEventListener("pageshow", this.onPageChange, true);
     handler.addEventListener("pagehide", this.onPageChange, true);
 
     this.destroyed = false;
     this.boundUpdate = {};
-
-    // Layout helper for window.parent and window.top helper methods that work
-    // accross devices.
-    this.layoutHelper = new LayoutHelpers(this.window);
   },
 
   destroy: function() {
     clearTimeout(this.batchTimer);
     this.batchTimer = null;
-    this.layoutHelper = null;
     // Remove observers
     Services.obs.removeObserver(this, "content-document-global-created", false);
     Services.obs.removeObserver(this, "inner-window-destroyed", false);
     this.destroyed = true;
     if (this.parentActor.browser) {
       this.parentActor.browser.removeEventListener(
         "pageshow", this.onPageChange, true);
       this.parentActor.browser.removeEventListener(
@@ -1746,17 +1741,17 @@ let StorageActor = exports.StorageActor 
     for (let i = 0; i < docShell.childCount; i++) {
       let child = docShell.getChildAt(i);
       this.fetchChildWindows(child);
     }
     return null;
   },
 
   isIncludedInTopLevelWindow: function(window) {
-    return this.layoutHelper.isIncludedInTopLevelWindow(window);
+    return isWindowIncluded(this.window, window);
   },
 
   getWindowFromInnerWindowID: function(innerID) {
     innerID = innerID.QueryInterface(Ci.nsISupportsPRUint64).data;
     for (let win of this.childWindowPool.values()) {
       let id = win.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIDOMWindowUtils)
                    .currentInnerWindowID;
--- a/toolkit/devtools/styleinspector/css-logic.js
+++ b/toolkit/devtools/styleinspector/css-logic.js
@@ -36,17 +36,17 @@
  * - why their expectations may not have been fulfilled
  * - how browsers process CSS
  * @constructor
  */
 
 const { Cc, Ci, Cu } = require("chrome");
 const Services = require("Services");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
-const LayoutHelpers = require("devtools/toolkit/layout-helpers");
+const { getRootBindingParent } = require("devtools/toolkit/layout/utils");
 
 let pseudos = new Set([
   ":after",
   ":before",
   ":first-letter",
   ":first-line",
   ":selection",
   ":-moz-color-swatch",
@@ -71,17 +71,17 @@ let pseudos = new Set([
 ]);
 
 const PSEUDO_ELEMENT_SET = pseudos;
 exports.PSEUDO_ELEMENT_SET = PSEUDO_ELEMENT_SET;
 
 // This should be ok because none of the functions that use this should be used
 // on the worker thread, where Cu is not available.
 if (Cu) {
-  Cu.importGlobalProperties(['CSS']);
+  Cu.importGlobalProperties(["CSS"]);
 }
 
 function CssLogic()
 {
   // The cache of examined CSS properties.
   _propertyInfos: {};
 }
 
@@ -940,17 +940,17 @@ function positionInNodeList(element, nod
 }
 
 /**
  * Find a unique CSS selector for a given element
  * @returns a string such that ele.ownerDocument.querySelector(reply) === ele
  * and ele.ownerDocument.querySelectorAll(reply).length === 1
  */
 CssLogic.findCssSelector = function CssLogic_findCssSelector(ele) {
-  ele = LayoutHelpers.getRootBindingParent(ele);
+  ele = getRootBindingParent(ele);
   var document = ele.ownerDocument;
   if (!document || !document.contains(ele)) {
     throw new Error('findCssSelector received element not inside document');
   }
 
   // document.querySelectorAll("#id") returns multiple if elements share an ID
   if (ele.id && document.querySelectorAll('#' + CSS.escape(ele.id)).length === 1) {
     return '#' + CSS.escape(ele.id);
--- a/toolkit/devtools/webconsole/utils.js
+++ b/toolkit/devtools/webconsole/utils.js
@@ -2,26 +2,26 @@
 /* vim: set ft=javascript 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/. */
 
 "use strict";
 
 const {Cc, Ci, Cu, components} = require("chrome");
+const {isWindowIncluded} = require("devtools/toolkit/layout/utils");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
 
 // TODO: Bug 842672 - browser/ imports modules from toolkit/.
 // Note that these are only used in WebConsoleCommands, see $0 and pprint().
 loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
-const LayoutHelpers = require("devtools/toolkit/layout-helpers");
 
 // Match the function name from the result of toString() or toSource().
 //
 // Examples:
 // (function foobar(a, b) { ...
 // function foobar2(a) { ...
 // function() { ...
 const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
@@ -1196,19 +1196,16 @@ exports.JSPropertyProvider = DevToolsUti
  *        The listener object must have one method:
  *        - onConsoleServiceMessage(). This method is invoked with one argument,
  *        the nsIConsoleMessage, whenever a relevant message is received.
  */
 function ConsoleServiceListener(aWindow, aListener)
 {
   this.window = aWindow;
   this.listener = aListener;
-  if (this.window) {
-    this.layoutHelpers = new LayoutHelpers(this.window);
-  }
 }
 exports.ConsoleServiceListener = ConsoleServiceListener;
 
 ConsoleServiceListener.prototype =
 {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]),
 
   /**
@@ -1247,18 +1244,18 @@ ConsoleServiceListener.prototype =
 
     if (this.window) {
       if (!(aMessage instanceof Ci.nsIScriptError) ||
           !aMessage.outerWindowID ||
           !this.isCategoryAllowed(aMessage.category)) {
         return;
       }
 
-      let errorWindow = Services.wm.getOuterWindowWithId(aMessage.outerWindowID);
-      if (!errorWindow || !this.layoutHelpers.isIncludedInTopLevelWindow(errorWindow)) {
+      let errorWindow = Services.wm.getOuterWindowWithId(aMessage .outerWindowID);
+      if (!errorWindow || !isWindowIncluded(this.window, errorWindow)) {
         return;
       }
     }
 
     this.listener.onConsoleServiceMessage(aMessage);
   },
 
   /**
@@ -1373,19 +1370,16 @@ ConsoleServiceListener.prototype =
  * @param string aConsoleID
  *        Options - The consoleID that this listener should listen to
  */
 function ConsoleAPIListener(aWindow, aOwner, aConsoleID)
 {
   this.window = aWindow;
   this.owner = aOwner;
   this.consoleID = aConsoleID;
-  if (this.window) {
-    this.layoutHelpers = new LayoutHelpers(this.window);
-  }
 }
 exports.ConsoleAPIListener = ConsoleAPIListener;
 
 ConsoleAPIListener.prototype =
 {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
   /**
@@ -1436,17 +1430,17 @@ ConsoleAPIListener.prototype =
     }
 
     // Here, wrappedJSObject is not a security wrapper but a property defined
     // by the XPCOM component which allows us to unwrap the XPCOM interface and
     // access the underlying JSObject.
     let apiMessage = aMessage.wrappedJSObject;
     if (this.window && CONSOLE_WORKER_IDS.indexOf(apiMessage.innerID) == -1) {
       let msgWindow = Services.wm.getCurrentInnerWindowWithId(apiMessage.innerID);
-      if (!msgWindow || !this.layoutHelpers.isIncludedInTopLevelWindow(msgWindow)) {
+      if (!msgWindow || !isWindowIncluded(this.window, msgWindow)) {
         // Not the same window!
         return;
       }
     }
     if (this.consoleID && apiMessage.consoleID != this.consoleID) {
       return;
     }