Bug 1348005 - update grid panel on reflows;r=gl draft
authorJulian Descottes <jdescottes@mozilla.com>
Wed, 29 Mar 2017 20:51:27 +0200
changeset 553532 490befd89eb1011a02d8a99ac965b077da324067
parent 553172 6ea713ccc9abea93126423fefb855d0e051c95e2
child 622099 d09918ffc1a293a9642a0363847c42a3b58e7dca
push id51674
push userjdescottes@mozilla.com
push dateThu, 30 Mar 2017 08:28:28 +0000
reviewersgl
bugs1348005
milestone55.0a1
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;