Bug 1348005 - update grid panel on reflows;r=gl
authorJulian Descottes <jdescottes@mozilla.com>
Wed, 29 Mar 2017 20:51:27 +0200
changeset 350639 d7947c28ab4f500a7da3faeb808c0c307dc7c4a2
parent 350638 79eeca3860563e089c3163fb5137b1be90fb31a6
child 350640 0896cb720b9194abc66fecfc4d9f124a206c7e38
push id88685
push usercbook@mozilla.com
push dateFri, 31 Mar 2017 12:48:11 +0000
treeherdermozilla-inbound@aece477a513e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgl
bugs1348005
milestone55.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 1348005 - update grid panel on reflows;r=gl Extracted the reflow tracking logic from the box-model to a dedicated util in inspector/shared/reflow-tracker.js to use it in both box-model and grid-inspector. MozReview-Commit-ID: DZCOH3RDY6
devtools/client/inspector/boxmodel/box-model.js
devtools/client/inspector/grids/grid-inspector.js
devtools/client/inspector/inspector.js
devtools/client/inspector/shared/moz.build
devtools/client/inspector/shared/reflow-tracker.js
--- a/devtools/client/inspector/boxmodel/box-model.js
+++ b/devtools/client/inspector/boxmodel/box-model.js
@@ -1,17 +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 { Task } = require("devtools/shared/task");
 const { getCssProperties } = require("devtools/shared/fronts/css-properties");
-const { ReflowFront } = require("devtools/shared/fronts/reflow");
 
 const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
 
 const {
   updateGeometryEditorEnabled,
   updateLayout,
   updateOffsetParent,
 } = require("./actions/box-model");
@@ -55,21 +54,17 @@ BoxModel.prototype = {
   /**
    * Destruction function called when the inspector is destroyed. Removes event listeners
    * and cleans up references.
    */
   destroy() {
     this.inspector.selection.off("new-node-front", this.onNewSelection);
     this.inspector.sidebar.off("select", this.onSidebarSelect);
 
-    if (this.reflowFront) {
-      this.untrackReflows();
-      this.reflowFront.destroy();
-      this.reflowFront = null;
-    }
+    this.untrackReflows();
 
     this.document = null;
     this.highlighters = null;
     this.inspector = null;
     this.walker = null;
   },
 
   /**
@@ -104,40 +99,24 @@ BoxModel.prototype = {
            this.inspector.selection.isConnected() &&
            this.inspector.selection.isElementNode();
   },
 
   /**
    * Starts listening to reflows in the current tab.
    */
   trackReflows() {
-    if (!this.reflowFront) {
-      let { target } = this.inspector;
-      if (target.form.reflowActor) {
-        this.reflowFront = ReflowFront(target.client,
-                                       target.form);
-      } else {
-        return;
-      }
-    }
-
-    this.reflowFront.on("reflows", this.updateBoxModel);
-    this.reflowFront.start();
+    this.inspector.reflowTracker.trackReflows(this, this.updateBoxModel);
   },
 
   /**
    * Stops listening to reflows in the current tab.
    */
   untrackReflows() {
-    if (!this.reflowFront) {
-      return;
-    }
-
-    this.reflowFront.off("reflows", this.updateBoxModel);
-    this.reflowFront.stop();
+    this.inspector.reflowTracker.untrackReflows(this, this.updateBoxModel);
   },
 
   /**
    * Updates the box model panel by dispatching the new layout data.
    *
    * @param  {String} reason
    *         Optional string describing the reason why the boxmodel is updated.
    */
--- a/devtools/client/inspector/grids/grid-inspector.js
+++ b/devtools/client/inspector/grids/grid-inspector.js
@@ -42,16 +42,17 @@ function GridInspector(inspector, window
   this.walker = this.inspector.walker;
 
   this.getSwatchColorPickerTooltip = this.getSwatchColorPickerTooltip.bind(this);
   this.updateGridPanel = this.updateGridPanel.bind(this);
 
   this.onGridLayoutChange = this.onGridLayoutChange.bind(this);
   this.onHighlighterChange = this.onHighlighterChange.bind(this);
   this.onMarkupMutation = this.onMarkupMutation.bind(this);
+  this.onReflow = this.onReflow.bind(this);
   this.onSetGridOverlayColor = this.onSetGridOverlayColor.bind(this);
   this.onShowGridAreaHighlight = this.onShowGridAreaHighlight.bind(this);
   this.onShowGridCellHighlight = this.onShowGridCellHighlight.bind(this);
   this.onSidebarSelect = this.onSidebarSelect.bind(this);
   this.onToggleGridHighlighter = this.onToggleGridHighlighter.bind(this);
   this.onToggleShowGridLineNumbers = this.onToggleShowGridLineNumbers.bind(this);
   this.onToggleShowInfiniteLines = this.onToggleShowInfiniteLines.bind(this);
 
@@ -96,16 +97,18 @@ GridInspector.prototype = {
    */
   destroy() {
     this.highlighters.off("grid-highlighter-hidden", this.onHighlighterChange);
     this.highlighters.off("grid-highlighter-shown", this.onHighlighterChange);
     this.inspector.off("markupmutation", this.onMarkupMutation);
     this.inspector.sidebar.off("select", this.onSidebarSelect);
     this.layoutInspector.off("grid-layout-changed", this.onGridLayoutChange);
 
+    this.inspector.reflowTracker.untrackReflows(this, this.onReflow);
+
     this.swatchColorPickerTooltip.destroy();
 
     this.document = null;
     this.highlighters = null;
     this.inspector = null;
     this.layoutInspector = null;
     this.store = null;
     this.swatchColorPickerTooltip = null;
@@ -290,16 +293,24 @@ GridInspector.prototype = {
    * Handler for the "markupmutation" event fired by the inspector. On markup mutations,
    * update the grid panel content.
    */
   onMarkupMutation() {
     this.updateGridPanel();
   },
 
   /**
+   * Handler for the "reflow" event fired by the inspector's reflow tracker. On reflows,
+   * update the grid panel content.
+   */
+  onReflow() {
+    this.updateGridPanel();
+  },
+
+  /**
    * Handler for a change in the grid overlay color picker for a grid container.
    *
    * @param  {NodeFront} node
    *         The NodeFront of the grid container element for which the grid color is
    *         being updated.
    * @param  {String} color
    *         A hex string representing the color to use.
    */
@@ -371,19 +382,21 @@ GridInspector.prototype = {
    * Handler for the inspector sidebar select event. Starts listening for
    * "grid-layout-changed" if the layout panel is visible. Otherwise, stop
    * listening for grid layout changes. Finally, refresh the layout view if
    * it is visible.
    */
   onSidebarSelect() {
     if (!this.isPanelVisible()) {
       this.layoutInspector.off("grid-layout-changed", this.onGridLayoutChange);
+      this.inspector.reflowTracker.untrackReflows(this, this.onReflow);
       return;
     }
 
+    this.inspector.reflowTracker.trackReflows(this, this.onReflow);
     this.layoutInspector.on("grid-layout-changed", this.onGridLayoutChange);
     this.updateGridPanel();
   },
 
   /**
    * Handler for a change in the input checkboxes in the GridList component.
    * Toggles on/off the grid highlighter for the provided grid container element.
    *
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -24,16 +24,17 @@ const MenuItem = require("devtools/clien
 
 const {HTMLBreadcrumbs} = require("devtools/client/inspector/breadcrumbs");
 const BoxModel = require("devtools/client/inspector/boxmodel/box-model");
 const {FontInspector} = require("devtools/client/inspector/fonts/fonts");
 const GridInspector = require("devtools/client/inspector/grids/grid-inspector");
 const {InspectorSearch} = require("devtools/client/inspector/inspector-search");
 const {RuleViewTool} = require("devtools/client/inspector/rules/rules");
 const HighlightersOverlay = require("devtools/client/inspector/shared/highlighters-overlay");
+const ReflowTracker = require("devtools/client/inspector/shared/reflow-tracker");
 const {ToolSidebar} = require("devtools/client/inspector/toolsidebar");
 const MarkupView = require("devtools/client/inspector/markup/markup");
 const {CommandUtils} = require("devtools/client/shared/developer-toolbar");
 const {ViewHelpers} = require("devtools/client/shared/widgets/view-helpers");
 const clipboardHelper = require("devtools/shared/platform/clipboard");
 
 const Store = require("devtools/client/inspector/store");
 
@@ -90,16 +91,17 @@ function Inspector(toolbox) {
 
   this._toolbox = toolbox;
   this._target = toolbox.target;
   this.panelDoc = window.document;
   this.panelWin = window;
   this.panelWin.inspector = this;
 
   this.highlighters = new HighlightersOverlay(this);
+  this.reflowTracker = new ReflowTracker(this._target);
   this.store = Store();
   this.telemetry = new Telemetry();
 
   this.nodeMenuTriggerInfo = null;
 
   this._handleRejectionIfNotDestroyed = this._handleRejectionIfNotDestroyed.bind(this);
   this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
   this.onNewRoot = this.onNewRoot.bind(this);
@@ -962,16 +964,17 @@ Inspector.prototype = {
     this.teardownToolbar();
     this.breadcrumbs.destroy();
     this.selection.off("new-node-front", this.onNewSelection);
     this.selection.off("detached-front", this.onDetached);
 
     let markupDestroyer = this._destroyMarkup();
 
     this.highlighters.destroy();
+    this.reflowTracker.destroy();
     this.search.destroy();
 
     this._toolbox = null;
     this.breadcrumbs = null;
     this.panelDoc = null;
     this.panelWin.inspector = null;
     this.panelWin = null;
     this.sidebar = null;
--- a/devtools/client/inspector/shared/moz.build
+++ b/devtools/client/inspector/shared/moz.build
@@ -3,14 +3,15 @@
 # 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/.
 
 DevToolsModules(
     'dom-node-preview.js',
     'highlighters-overlay.js',
     'node-types.js',
+    'reflow-tracker.js',
     'style-inspector-menu.js',
     'tooltips-overlay.js',
     'utils.js'
 )
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/shared/reflow-tracker.js
@@ -0,0 +1,113 @@
+/* -*- 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 { ReflowFront } = require("devtools/shared/fronts/reflow");
+
+/**
+ * Simple utility class that listens to reflows on a given target if and only if a
+ * listener is actively listening to reflows.
+ *
+ * @param {Object} target
+ *        The current target (as in toolbox target)
+ */
+function ReflowTracker(target) {
+  this.target = target;
+
+  // Hold a Map of all the listeners interested in reflows.
+  this.listeners = new Map();
+
+  this.reflowFront = null;
+
+  this.onReflow = this.onReflow.bind(this);
+}
+
+ReflowTracker.prototype = {
+
+  destroy() {
+    if (this.reflowFront) {
+      this.stopTracking();
+      this.reflowFront.destroy();
+      this.reflowFront = null;
+    }
+
+    this.listeners.clear();
+  },
+
+  startTracking() {
+    // Initialize reflow front if necessary.
+    if (!this.reflowFront && this.target.form.reflowActor) {
+      let { client, form } = this.target;
+      this.reflowFront = ReflowFront(client, form);
+    }
+
+    if (this.reflowFront) {
+      this.reflowFront.on("reflows", this.onReflow);
+      this.reflowFront.start();
+    }
+  },
+
+  stopTracking() {
+    if (this.reflowFront) {
+      this.reflowFront.off("reflows", this.onReflow);
+      this.reflowFront.stop();
+    }
+  },
+
+  /**
+   * Add a listener for reflows.
+   *
+   * @param {Object} listener
+   *        Object/instance listening to reflows.
+   * @param {Function} callback
+   *        The associated callback.
+   */
+  trackReflows(listener, callback) {
+    if (this.listeners.get(listener) === callback) {
+      return;
+    }
+
+    // No listener interested in reflows yet, start tracking.
+    if (this.listeners.size === 0) {
+      this.startTracking();
+    }
+
+    this.listeners.set(listener, callback);
+  },
+
+  /**
+   * Remove a listener for reflows.
+   *
+   * @param {Object} listener
+   *        Object/instance listening to reflows.
+   * @param {Function} callback
+   *        The associated callback.
+   */
+  untrackReflows(listener, callback) {
+    if (this.listeners.get(listener) !== callback) {
+      return;
+    }
+
+    this.listeners.delete(listener);
+
+    // No listener interested in reflows anymore, stop tracking.
+    if (this.listeners.size === 0) {
+      this.stopTracking();
+    }
+  },
+
+  /**
+   * Handler called when a reflow happened.
+   */
+  onReflow() {
+    for (let [, callback] of this.listeners) {
+      callback();
+    }
+  },
+};
+
+module.exports = ReflowTracker;