Backed out changeset f6bde67d0e05 (bug 1349275) for devtools test failures.
authorRyan VanderMeulen <ryanvm@gmail.com>
Sat, 01 Apr 2017 16:31:43 -0400
changeset 379381 4c6f403862d369eb0f3f9eff423c6458f36ec4bf
parent 379380 e82d8ab7e1614f1126ce2497314fb89d70c7b821
child 379382 6f897e237eb98ad48675a77a7f3ef3e8d5c4e612
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1349275
milestone53.0
backs outf6bde67d0e05340f1d67c71eb7f7c6e3dd1760f7
Backed out changeset f6bde67d0e05 (bug 1349275) for devtools test failures.
devtools/client/inspector/test/browser_inspector_highlighter-zoom.js
devtools/client/inspector/test/browser_inspector_infobar_01.js
devtools/client/inspector/test/browser_inspector_infobar_03.js
devtools/server/actors/highlighters.css
devtools/server/actors/highlighters/auto-refresh.js
devtools/server/actors/highlighters/box-model.js
devtools/server/actors/highlighters/utils/markup.js
devtools/shared/layout/utils.js
--- a/devtools/client/inspector/test/browser_inspector_highlighter-zoom.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-zoom.js
@@ -7,18 +7,18 @@
 // Test that the highlighter stays correctly positioned and has the right aspect
 // ratio even when the page is zoomed in or out.
 
 const TEST_URL = "data:text/html;charset=utf-8,<div>zoom me</div>";
 
 // TEST_LEVELS entries should contain the zoom level to test.
 const TEST_LEVELS = [2, 1, .5];
 
-// Returns the expected style attribute value to check for on the highlighter's elements
-// node, for the values given.
+// Returns the expected style attribute value to check for on the root highlighter
+// element, for the values given.
 const expectedStyle = (w, h, z) =>
         (z !== 1 ? `transform-origin:top left; transform:scale(${1 / z}); ` : "") +
         `position:absolute; width:${w * z}px;height:${h * z}px; ` +
         "overflow:hidden";
 
 add_task(function* () {
   let {inspector, testActor} = yield openInspectorForURL(TEST_URL);
 
@@ -35,17 +35,17 @@ add_task(function* () {
     yield testActor.zoomPageTo(level);
     isVisible = yield testActor.isHighlighting();
     ok(isVisible, "The highlighter is still visible at zoom level " + level);
 
     yield testActor.isNodeCorrectlyHighlighted("div", is);
 
     info("Check that the highlighter root wrapper node was scaled down");
 
-    let style = yield getElementsNodeStyle(testActor);
+    let style = yield getRootNodeStyle(testActor);
     let { width, height } = yield testActor.getWindowDimensions();
     is(style, expectedStyle(width, height, level),
       "The style attribute of the root element is correct");
   }
 });
 
 function* hoverElement(selector, inspector) {
   info("Hovering node " + selector + " in the markup view");
@@ -55,13 +55,13 @@ function* hoverElement(selector, inspect
 
 function* hoverContainer(container, inspector) {
   let onHighlight = inspector.toolbox.once("node-highlight");
   EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"},
       inspector.markup.doc.defaultView);
   yield onHighlight;
 }
 
-function* getElementsNodeStyle(testActor) {
+function* getRootNodeStyle(testActor) {
   let value = yield testActor.getHighlighterNodeAttribute(
-    "box-model-elements", "style");
+    "box-model-root", "style");
   return value;
 }
--- a/devtools/client/inspector/test/browser_inspector_infobar_01.js
+++ b/devtools/client/inspector/test/browser_inspector_infobar_01.js
@@ -13,52 +13,47 @@ add_task(function* () {
 
   let testData = [
     {
       selector: "#top",
       position: "bottom",
       tag: "div",
       id: "top",
       classes: ".class1.class2",
-      dims: "500" + " \u00D7 " + "100",
-      arrowed: true
+      dims: "500" + " \u00D7 " + "100"
     },
     {
       selector: "#vertical",
-      position: "top",
+      position: "overlap",
       tag: "div",
       id: "vertical",
-      classes: "",
-      arrowed: false
+      classes: ""
       // No dims as they will vary between computers
     },
     {
       selector: "#bottom",
       position: "top",
       tag: "div",
       id: "bottom",
       classes: "",
-      dims: "500" + " \u00D7 " + "100",
-      arrowed: true
+      dims: "500" + " \u00D7 " + "100"
     },
     {
       selector: "body",
       position: "bottom",
       tag: "body",
-      classes: "",
-      arrowed: true
+      classes: ""
       // No dims as they will vary between computers
     },
     {
       selector: "clipPath",
       position: "bottom",
       tag: "clipPath",
       id: "clip",
-      classes: "",
-      arrowed: false
+      classes: ""
       // No dims as element is not displayed and we just want to test tag name
     },
   ];
 
   for (let currTest of testData) {
     yield testPosition(currTest, inspector, testActor);
   }
 });
@@ -81,19 +76,14 @@ function* testPosition(test, inspector, 
       "box-model-infobar-id");
     is(id, "#" + test.id, "node " + test.selector + ": id matches.");
   }
 
   let classes = yield testActor.getHighlighterNodeTextContent(
     "box-model-infobar-classes");
   is(classes, test.classes, "node " + test.selector + ": classes match.");
 
-  let arrowed = !(yield testActor.getHighlighterNodeAttribute(
-    "box-model-infobar-container", "hide-arrow"));
-
-  is(arrowed, test.arrowed, "node " + test.selector + ": arrow visibility match.");
-
   if (test.dims) {
     let dims = yield testActor.getHighlighterNodeTextContent(
       "box-model-infobar-dimensions");
     is(dims, test.dims, "node " + test.selector + ": dims match.");
   }
 }
--- a/devtools/client/inspector/test/browser_inspector_infobar_03.js
+++ b/devtools/client/inspector/test/browser_inspector_infobar_03.js
@@ -9,33 +9,33 @@
 const TEST_URI = URL_ROOT + "doc_inspector_infobar_03.html";
 
 add_task(function* () {
   let {inspector, testActor} = yield openInspectorForURL(TEST_URI);
 
   let testData = {
     selector: "body",
     position: "overlap",
-    style: "position:fixed",
+    style: "top:0px",
   };
 
   yield testPositionAndStyle(testData, inspector, testActor);
 });
 
 function* testPositionAndStyle(test, inspector, testActor) {
   info("Testing " + test.selector);
 
   yield selectAndHighlightNode(test.selector, inspector);
 
   let style = yield testActor.getHighlighterNodeAttribute(
     "box-model-infobar-container", "style");
 
-  is(style.split(";")[0].trim(), test.style,
+  is(style.split(";")[0], test.style,
     "Infobar shows on top of the page when page isn't scrolled");
 
   yield testActor.scrollWindow(0, 500);
 
   style = yield testActor.getHighlighterNodeAttribute(
     "box-model-infobar-container", "style");
 
-  is(style.split(";")[0].trim(), test.style,
+  is(style.split(";")[0], test.style,
     "Infobar shows on top of the page even if the page is scrolled");
 }
--- a/devtools/server/actors/highlighters.css
+++ b/devtools/server/actors/highlighters.css
@@ -28,17 +28,16 @@
 }
 
 :-moz-native-anonymous .highlighter-container {
   --highlighter-guide-color: #08c;
   --highlighter-content-color: #87ceeb;
   --highlighter-bubble-text-color: hsl(216, 33%, 97%);
   --highlighter-bubble-background-color: hsl(214, 13%, 24%);
   --highlighter-bubble-border-color: rgba(255, 255, 255, 0.2);
-  --highlighter-bubble-arrow-size: 8px;
 }
 
 /**
  * Highlighters are asbolute positioned in the page by default.
  * A single highlighter can have fixed position in its css class if needed (see below the
  * eye dropper or rulers highlighter, for example); but if it has to handle the
  * document's scrolling (as rulers does), it would lag a bit behind due the APZ (Async
  * Pan/Zoom module), that performs asynchronously panning and zooming on the compositor
@@ -137,21 +136,25 @@
   background: var(--highlighter-bubble-background-color) no-repeat padding-box;
 
   color: var(--highlighter-bubble-text-color);
   text-shadow: none;
 
   border: 1px solid var(--highlighter-bubble-border-color);
 }
 
+:-moz-native-anonymous [class$=infobar-container][hide-arrow] > [class$=infobar] {
+  margin: 7px 0;
+}
+
 /* Arrows */
 
 :-moz-native-anonymous [class$=infobar-container] > [class$=infobar]:before {
-  left: calc(50% - var(--highlighter-bubble-arrow-size));
-  border: var(--highlighter-bubble-arrow-size) solid var(--highlighter-bubble-border-color);
+  left: calc(50% - 8px);
+  border: 8px solid var(--highlighter-bubble-border-color);
 }
 
 :-moz-native-anonymous [class$=infobar-container] > [class$=infobar]:after {
   left: calc(50% - 7px);
   border: 7px solid var(--highlighter-bubble-background-color);
 }
 
 :-moz-native-anonymous [class$=infobar-container] > [class$=infobar]:before,
--- a/devtools/server/actors/highlighters/auto-refresh.js
+++ b/devtools/server/actors/highlighters/auto-refresh.js
@@ -67,17 +67,16 @@ function AutoRefreshHighlighter(highligh
   EventEmitter.decorate(this);
 
   this.highlighterEnv = highlighterEnv;
 
   this.currentNode = null;
   this.currentQuads = {};
 
   this._winDimensions = getWindowDimensions(this.win);
-  this._scroll = { x: this.win.pageXOffset, y: this.win.pageYOffset };
 
   this.update = this.update.bind(this);
 }
 
 AutoRefreshHighlighter.prototype = {
   /**
    * Window corresponding to the current highlighterEnv
    */
@@ -189,31 +188,16 @@ AutoRefreshHighlighter.prototype = {
   _hasMoved: function () {
     let oldQuads = this.currentQuads;
     this._updateAdjustedQuads();
 
     return areQuadsDifferent(oldQuads, this.currentQuads, getCurrentZoom(this.win));
   },
 
   /**
-   * Update the knowledge we have of the current window's scrolling offset, both
-   * horizontal and vertical, and return `true` if they have changed since.
-   * @return {Boolean}
-   */
-  _hasWindowScrolled: function () {
-    let { pageXOffset, pageYOffset } = this.win;
-    let hasChanged = this._scroll.x !== pageXOffset ||
-                     this._scroll.y !== pageYOffset;
-
-    this._scroll = { x: pageXOffset, y: pageYOffset };
-
-    return hasChanged;
-  },
-
-  /**
    * Update the knowledge we have of the current window's dimensions and return `true`
    * if they have changed since.
    * @return {Boolean}
    */
   _haveWindowDimensionsChanged: function () {
     let { width, height } = getWindowDimensions(this.win);
     let haveChanged = (this._winDimensions.width !== width ||
                       this._winDimensions.height !== height);
@@ -223,22 +207,16 @@ AutoRefreshHighlighter.prototype = {
   },
 
   /**
    * Update the highlighter if the node has moved since the last update.
    */
   update: function () {
     if (!this._isNodeValid(this.currentNode) ||
        (!this._hasMoved() && !this._haveWindowDimensionsChanged())) {
-      // At this point we're not calling the `_update` method. However, if the window has
-      // scrolled, we want to invoke `_scrollUpdate`.
-      if (this._hasWindowScrolled()) {
-        this._scrollUpdate();
-      }
-
       return;
     }
 
     this._update();
     this.emit("updated");
   },
 
   _show: function () {
@@ -247,27 +225,20 @@ AutoRefreshHighlighter.prototype = {
     // this.currentNode, potentially using options in this.options
     throw new Error("Custom highlighter class had to implement _show method");
   },
 
   _update: function () {
     // To be implemented by sub classes
     // When called, sub classes should update the highlighter shown for
     // this.currentNode
-    // This is called as a result of a page zoom or repaint
+    // This is called as a result of a page scroll, zoom or repaint
     throw new Error("Custom highlighter class had to implement _update method");
   },
 
-  _scrollUpdate: function () {
-    // Can be implemented by sub classes
-    // When called, sub classes can upate the highlighter shown for
-    // this.currentNode
-    // This is called as a result of a page scroll
-  },
-
   _hide: function () {
     // To be implemented by sub classes
     // When called, sub classes should actually hide the highlighter
     throw new Error("Custom highlighter class had to implement _hide method");
   },
 
   _startRefreshLoop: function () {
     let win = this.currentNode.ownerDocument.defaultView;
--- a/devtools/server/actors/highlighters/box-model.js
+++ b/devtools/server/actors/highlighters/box-model.js
@@ -334,20 +334,16 @@ BoxModelHighlighter.prototype = extend(A
       this._hide();
     }
 
     setIgnoreLayoutChanges(false, this.highlighterEnv.window.document.documentElement);
 
     return shown;
   },
 
-  _scrollUpdate: function () {
-    this._moveInfobar();
-  },
-
   /**
    * Hide the highlighter, the outline and the infobar.
    */
   _hide: function () {
     setIgnoreLayoutChanges(true);
 
     this._untrackMutations();
     this._hideBoxModel();
@@ -494,17 +490,17 @@ BoxModelHighlighter.prototype = extend(A
       if (boxType === options.region && !options.hideGuides) {
         this._showGuides(boxType);
       } else if (options.hideGuides) {
         this._hideGuides();
       }
     }
 
     // Un-zoom the root wrapper if the page was zoomed.
-    let rootId = this.ID_CLASS_PREFIX + "elements";
+    let rootId = this.ID_CLASS_PREFIX + "root";
     this.markup.scaleRootElement(this.currentNode, rootId);
 
     return true;
   },
 
   _getBoxPathCoordinates: function (boxQuad, nextBoxQuad) {
     let {p1, p2, p3, p4} = boxQuad;
 
--- a/devtools/server/actors/highlighters/utils/markup.js
+++ b/devtools/server/actors/highlighters/utils/markup.js
@@ -1,16 +1,16 @@
 /* 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 { getCurrentZoom, getWindowDimensions, getViewportDimensions,
+const { getCurrentZoom, getWindowDimensions,
   getRootBindingParent } = require("devtools/shared/layout/utils");
 const { on, emit } = require("sdk/event/core");
 
 const lazyContainer = {};
 
 loader.lazyRequireGetter(lazyContainer, "CssLogic",
   "devtools/server/css-logic", true);
 exports.getComputedStyle = (node) =>
@@ -33,16 +33,20 @@ exports.removePseudoClassLock = (...args
 exports.getCSSStyleRules = (...args) =>
   lazyContainer.DOMUtils.getCSSStyleRules(...args);
 
 const SVG_NS = "http://www.w3.org/2000/svg";
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const STYLESHEET_URI = "resource://devtools/server/actors/" +
                        "highlighters.css";
+// How high is the infobar (px).
+const INFOBAR_HEIGHT = 34;
+// What's the size of the infobar arrow (px).
+const INFOBAR_ARROW_SIZE = 9;
 
 const _tokens = Symbol("classList/tokens");
 
 /**
  * Shims the element's `classList` for anonymous content elements; used
  * internally by `CanvasFrameAnonymousContentHelper.getElement()` method.
  */
 function ClassList(className) {
@@ -240,32 +244,30 @@ function CanvasFrameAnonymousContentHelp
   if (doc.documentElement && doc.readyState != "uninitialized") {
     this._insert();
   }
 
   this._onWindowReady = this._onWindowReady.bind(this);
   this.highlighterEnv.on("window-ready", this._onWindowReady);
 
   this.listeners = new Map();
-  this.elements = new Map();
 }
 
 CanvasFrameAnonymousContentHelper.prototype = {
-  destroy() {
+  destroy: function () {
     this._remove();
     this.highlighterEnv.off("window-ready", this._onWindowReady);
     this.highlighterEnv = this.nodeBuilder = this._content = null;
     this.anonymousContentDocument = null;
     this.anonymousContentGlobal = null;
 
     this._removeAllListeners();
-    this.elements.clear();
   },
 
-  _insert() {
+  _insert: function () {
     let doc = this.highlighterEnv.document;
     // Wait for DOMContentLoaded before injecting the anonymous content.
     if (doc.readyState != "interactive" && doc.readyState != "complete") {
       doc.addEventListener("DOMContentLoaded", this._insert.bind(this),
                            { once: true });
       return;
     }
     // Reject XUL documents. Check that after DOMContentLoaded as we query
@@ -310,62 +312,63 @@ CanvasFrameAnonymousContentHelper.protot
 
   /**
    * The "window-ready" event can be triggered when:
    *   - a new window is created
    *   - a window is unfrozen from bfcache
    *   - when first attaching to a page
    *   - when swapping frame loaders (moving tabs, toggling RDM)
    */
-  _onWindowReady(e, {isTopLevel}) {
+  _onWindowReady: function (e, {isTopLevel}) {
     if (isTopLevel) {
       this._remove();
       this._removeAllListeners();
-      this.elements.clear();
       this._insert();
       this.anonymousContentDocument = this.highlighterEnv.document;
     }
   },
 
-  getComputedStylePropertyValue(id, property) {
-    return this.content && this.content.getComputedStylePropertyValue(id, property);
+  getTextContentForElement: function (id) {
+    if (!this.content) {
+      return null;
+    }
+    return this.content.getTextContentForElement(id);
   },
 
-  getTextContentForElement(id) {
-    return this.content && this.content.getTextContentForElement(id);
-  },
-
-  setTextContentForElement(id, text) {
+  setTextContentForElement: function (id, text) {
     if (this.content) {
       this.content.setTextContentForElement(id, text);
     }
   },
 
-  setAttributeForElement(id, name, value) {
+  setAttributeForElement: function (id, name, value) {
     if (this.content) {
       this.content.setAttributeForElement(id, name, value);
     }
   },
 
-  getAttributeForElement(id, name) {
-    return this.content && this.content.getAttributeForElement(id, name);
+  getAttributeForElement: function (id, name) {
+    if (!this.content) {
+      return null;
+    }
+    return this.content.getAttributeForElement(id, name);
   },
 
-  removeAttributeForElement(id, name) {
+  removeAttributeForElement: function (id, name) {
     if (this.content) {
       this.content.removeAttributeForElement(id, name);
     }
   },
 
-  hasAttributeForElement(id, name) {
+  hasAttributeForElement: function (id, name) {
     return typeof this.getAttributeForElement(id, name) === "string";
   },
 
-  getCanvasContext(id, type = "2d") {
-    return this.content && this.content.getCanvasContext(id, type);
+  getCanvasContext: function (id, type = "2d") {
+    return this.content ? this.content.getCanvasContext(id, type) : null;
   },
 
   /**
    * Add an event listener to one of the elements inserted in the canvasFrame
    * native anonymous container.
    * Like other methods in this helper, this requires the ID of the element to
    * be passed in.
    *
@@ -394,17 +397,17 @@ CanvasFrameAnonymousContentHelper.protot
    * browser level and if the event originalTarget is found to have the provided
    * ID, the callback is executed (and then IDs of parent nodes of the
    * originalTarget are checked too).
    *
    * @param {String} id
    * @param {String} type
    * @param {Function} handler
    */
-  addEventListenerForElement(id, type, handler) {
+  addEventListenerForElement: function (id, type, handler) {
     if (typeof id !== "string") {
       throw new Error("Expected a string ID in addEventListenerForElement but" +
         " got: " + id);
     }
 
     // If no one is listening for this type of event yet, add one listener.
     if (!this.listeners.has(type)) {
       let target = this.highlighterEnv.pageListenerTarget;
@@ -418,31 +421,31 @@ CanvasFrameAnonymousContentHelper.protot
   },
 
   /**
    * Remove an event listener from one of the elements inserted in the
    * canvasFrame native anonymous container.
    * @param {String} id
    * @param {String} type
    */
-  removeEventListenerForElement(id, type) {
+  removeEventListenerForElement: function (id, type) {
     let listeners = this.listeners.get(type);
     if (!listeners) {
       return;
     }
     listeners.delete(id);
 
     // If no one is listening for event type anymore, remove the listener.
     if (!this.listeners.has(type)) {
       let target = this.highlighterEnv.pageListenerTarget;
       target.removeEventListener(type, this, true);
     }
   },
 
-  handleEvent(event) {
+  handleEvent: function (event) {
     let listeners = this.listeners.get(event.type);
     if (!listeners) {
       return;
     }
 
     // Hide the originalTarget property to avoid exposing references to native
     // anonymous elements. See addEventListenerForElement's comment.
     let isPropagationStopped = false;
@@ -469,60 +472,49 @@ CanvasFrameAnonymousContentHelper.protot
         if (isPropagationStopped) {
           break;
         }
       }
       node = node.parentNode;
     }
   },
 
-  _removeAllListeners() {
+  _removeAllListeners: function () {
     if (this.highlighterEnv) {
       let target = this.highlighterEnv.pageListenerTarget;
       for (let [type] of this.listeners) {
         target.removeEventListener(type, this, true);
       }
     }
     this.listeners.clear();
   },
 
-  getElement(id) {
-    if (this.elements.has(id)) {
-      return this.elements.get(id);
-    }
-
+  getElement: function (id) {
     let classList = new ClassList(this.getAttributeForElement(id, "class"));
 
     on(classList, "update", () => {
       this.setAttributeForElement(id, "class", classList.toString());
     });
 
-    let element = {
+    return {
       getTextContent: () => this.getTextContentForElement(id),
       setTextContent: text => this.setTextContentForElement(id, text),
       setAttribute: (name, val) => this.setAttributeForElement(id, name, val),
       getAttribute: name => this.getAttributeForElement(id, name),
       removeAttribute: name => this.removeAttributeForElement(id, name),
       hasAttribute: name => this.hasAttributeForElement(id, name),
       getCanvasContext: type => this.getCanvasContext(id, type),
       addEventListener: (type, handler) => {
         return this.addEventListenerForElement(id, type, handler);
       },
       removeEventListener: (type, handler) => {
         return this.removeEventListenerForElement(id, type, handler);
       },
-      computedStyle: {
-        getPropertyValue: property => this.getComputedStylePropertyValue(id, property)
-      },
       classList
     };
-
-    this.elements.set(id, element);
-
-    return element;
   },
 
   get content() {
     if (!this._content || Cu.isDeadWrapper(this._content)) {
       return null;
     }
     return this._content;
   },
@@ -543,17 +535,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(node, id) {
+  scaleRootElement: function (node, id) {
     let boundaryWindow = this.highlighterEnv.window;
     let zoom = getCurrentZoom(node);
     // Hide the root element and force the reflow in order to get the proper window's
     // dimensions without increasing them.
     this.setAttributeForElement(id, "style", "display: none");
     node.offsetWidth;
 
     let { width, height } = getWindowDimensions(boundaryWindow);
@@ -585,96 +577,60 @@ exports.CanvasFrameAnonymousContentHelpe
  * @param  {DOMNode} container
  *         The container element which will be used to position the infobar.
  * @param  {Object} bounds
  *         The content bounds of the container element.
  * @param  {Window} win
  *         The window object.
  */
 function moveInfobar(container, bounds, win) {
-  let zoom = getCurrentZoom(win);
-  let viewport = getViewportDimensions(win);
-
-  let { computedStyle } = container;
+  let winHeight = win.innerHeight * getCurrentZoom(win);
+  let winWidth = win.innerWidth * getCurrentZoom(win);
+  let winScrollY = win.scrollY;
 
-  // To simplify, we use the same arrow's size value as margin's value for all four sides.
-  let margin = parseFloat(computedStyle
-                              .getPropertyValue("--highlighter-bubble-arrow-size"));
-  let containerHeight = parseFloat(computedStyle.getPropertyValue("height"));
-  let containerWidth = parseFloat(computedStyle.getPropertyValue("width"));
-  let containerHalfWidth = containerWidth / 2;
-
-  let viewportWidth = viewport.width * zoom;
-  let viewportHeight = viewport.height * zoom;
-  let { pageXOffset, pageYOffset } = win;
-
-  pageYOffset *= zoom;
-  pageXOffset *= zoom;
-  containerHeight += margin;
+  // Ensure that containerBottom and containerTop are at least zero to avoid
+  // showing tooltips outside the viewport.
+  let containerBottom = Math.max(0, bounds.bottom) + INFOBAR_ARROW_SIZE;
+  let containerTop = Math.min(winHeight, bounds.top);
 
-  // Defines the boundaries for the infobar.
-  let topBoundary = margin;
-  let bottomBoundary = viewportHeight - containerHeight;
-  let leftBoundary = containerHalfWidth + margin;
-  let rightBoundary = viewportWidth - containerHalfWidth - margin;
-
-  // Set the default values.
-  let top = bounds.y - containerHeight;
-  let bottom = bounds.bottom + margin;
-  let left = bounds.x + bounds.width / 2;
-  let isOverlapTheNode = false;
-  let positionAttribute = "top";
-  let position = "absolute";
-
-  // Here we start the math.
-  // We basically want to position absolutely the infobar, except when is pointing to a
-  // node that is offscreen or partially offscreen, in a way that the infobar can't
-  // be placed neither on top nor on bottom.
-  // In such cases, the infobar will overlap the node, and to limit the latency given
-  // by APZ (See Bug 1312103) it will be positioned as "fixed".
-  // It's a sort of "position: sticky" (but positioned as absolute instead of relative).
-  let canBePlacedOnTop = top >= pageYOffset;
-  let canBePlacedOnBottom = bottomBoundary + pageYOffset - bottom > 0;
-
-  if (!canBePlacedOnTop && canBePlacedOnBottom) {
-    top = bottom;
-    positionAttribute = "bottom";
+  // Can the bar be above the node?
+  let top;
+  if (containerTop < INFOBAR_HEIGHT) {
+    // No. Can we move the bar under the node?
+    if (containerBottom + INFOBAR_HEIGHT > winHeight) {
+      // No. Let's move it inside. Can we show it at the top of the element?
+      if (containerTop < winScrollY) {
+        // No. Window is scrolled past the top of the element.
+        top = 0;
+      } else {
+        // Yes. Show it at the top of the element
+        top = containerTop;
+      }
+      container.setAttribute("position", "overlap");
+    } else {
+      // Yes. Let's move it under the node.
+      top = containerBottom;
+      container.setAttribute("position", "bottom");
+    }
+  } else {
+    // Yes. Let's move it on top of the node.
+    top = containerTop - INFOBAR_HEIGHT;
+    container.setAttribute("position", "top");
   }
 
-  let isOffscreenOnTop = top < topBoundary + pageYOffset;
-  let isOffscreenOnBottom = top > bottomBoundary + pageYOffset;
-  let isOffscreenOnLeft = left < leftBoundary + pageXOffset;
-  let isOffscreenOnRight = left > rightBoundary + pageXOffset;
-
-  if (isOffscreenOnTop) {
-    top = topBoundary;
-    isOverlapTheNode = true;
-  } else if (isOffscreenOnBottom) {
-    top = bottomBoundary;
-    isOverlapTheNode = true;
-  } else if (isOffscreenOnLeft || isOffscreenOnRight) {
-    isOverlapTheNode = true;
-    top -= pageYOffset;
-  }
-
-  if (isOverlapTheNode) {
-    left = Math.min(Math.max(leftBoundary, left - pageXOffset), rightBoundary);
-
-    position = "fixed";
+  // Align the bar with the box's center if possible.
+  let left = bounds.right - bounds.width / 2;
+  // Make sure the while infobar is visible.
+  let buffer = 100;
+  if (left < buffer) {
+    left = buffer;
+    container.setAttribute("hide-arrow", "true");
+  } else if (left > winWidth - buffer) {
+    left = winWidth - buffer;
     container.setAttribute("hide-arrow", "true");
   } else {
-    position = "absolute";
     container.removeAttribute("hide-arrow");
   }
 
-  // We need to scale the infobar Independently from the highlighter's container;
-  // otherwise the `position: fixed` won't work, since "any value other than `none` for
-  // the transform, results in the creation of both a stacking context and a containing
-  // block. The object acts as a containing block for fixed positioned descendants."
-  // (See https://www.w3.org/TR/css-transforms-1/#transform-rendering)
-  container.setAttribute("style", `
-    position:${position};
-    transform-origin: 0 0;
-    transform: scale(${1 / zoom}) translate(${left}px, ${top}px)`);
-
-  container.setAttribute("position", positionAttribute);
+  let style = "top:" + top + "px;left:" + left + "px;";
+  container.setAttribute("style", style);
 }
 exports.moveInfobar = moveInfobar;
--- a/devtools/shared/layout/utils.js
+++ b/devtools/shared/layout/utils.js
@@ -655,36 +655,16 @@ function getWindowDimensions(window) {
     height -= scrollbarHeight.value;
   }
 
   return { width, height };
 }
 exports.getWindowDimensions = getWindowDimensions;
 
 /**
- * Returns the viewport's dimensions for the `window` given.
- *
- * @return {Object} An object with `width` and `height` properties, representing the
- * number of pixels for the viewport's size.
- */
-function getViewportDimensions(window) {
-  let windowUtils = utilsFor(window);
-
-  let scrollbarHeight = {};
-  let scrollbarWidth = {};
-  windowUtils.getScrollbarSize(false, scrollbarWidth, scrollbarHeight);
-
-  let width = window.innerWidth - scrollbarWidth.value;
-  let height = window.innerHeight - scrollbarHeight.value;
-
-  return { width, height };
-}
-exports.getViewportDimensions = getViewportDimensions;
-
-/**
  * Returns the max size allowed for a surface like textures or canvas.
  * If no `webgl` context is available, DEFAULT_MAX_SURFACE_SIZE is returned instead.
  *
  * @param {DOMNode|DOMWindow|DOMDocument} node The node to get the window for.
  * @return {Number} the max size allowed
  */
 const DEFAULT_MAX_SURFACE_SIZE = 4096;
 function getMaxSurfaceSize(node) {