summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/highlighters/auto-refresh.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/highlighters/auto-refresh.js')
-rw-r--r--devtools/server/actors/highlighters/auto-refresh.js215
1 files changed, 215 insertions, 0 deletions
diff --git a/devtools/server/actors/highlighters/auto-refresh.js b/devtools/server/actors/highlighters/auto-refresh.js
new file mode 100644
index 000000000..31f89de20
--- /dev/null
+++ b/devtools/server/actors/highlighters/auto-refresh.js
@@ -0,0 +1,215 @@
+/* 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 } = require("chrome");
+const EventEmitter = require("devtools/shared/event-emitter");
+const { isNodeValid } = require("./utils/markup");
+const { getAdjustedQuads } = require("devtools/shared/layout/utils");
+
+// Note that the order of items in this array is important because it is used
+// for drawing the BoxModelHighlighter's path elements correctly.
+const BOX_MODEL_REGIONS = ["margin", "border", "padding", "content"];
+
+/**
+ * Base class for auto-refresh-on-change highlighters. Sub classes will have a
+ * chance to update whenever the current node's geometry changes.
+ *
+ * Sub classes must implement the following methods:
+ * _show: called when the highlighter should be shown,
+ * _hide: called when the highlighter should be hidden,
+ * _update: called while the highlighter is shown and the geometry of the
+ * current node changes.
+ *
+ * Sub classes will have access to the following properties:
+ * - this.currentNode: the node to be shown
+ * - this.currentQuads: all of the node's box model region quads
+ * - this.win: the current window
+ *
+ * Emits the following events:
+ * - shown
+ * - hidden
+ * - updated
+ */
+function AutoRefreshHighlighter(highlighterEnv) {
+ EventEmitter.decorate(this);
+
+ this.highlighterEnv = highlighterEnv;
+
+ this.currentNode = null;
+ this.currentQuads = {};
+
+ this.update = this.update.bind(this);
+}
+
+AutoRefreshHighlighter.prototype = {
+ /**
+ * Window corresponding to the current highlighterEnv
+ */
+ get win() {
+ if (!this.highlighterEnv) {
+ return null;
+ }
+ return this.highlighterEnv.window;
+ },
+
+ /**
+ * Show the highlighter on a given node
+ * @param {DOMNode} node
+ * @param {Object} options
+ * Object used for passing options
+ */
+ show: function (node, options = {}) {
+ let isSameNode = node === this.currentNode;
+ let isSameOptions = this._isSameOptions(options);
+
+ if (!this._isNodeValid(node) || (isSameNode && isSameOptions)) {
+ return false;
+ }
+
+ this.options = options;
+
+ this._stopRefreshLoop();
+ this.currentNode = node;
+ this._updateAdjustedQuads();
+ this._startRefreshLoop();
+
+ let shown = this._show();
+ if (shown) {
+ this.emit("shown");
+ }
+ return shown;
+ },
+
+ /**
+ * Hide the highlighter
+ */
+ hide: function () {
+ if (!this._isNodeValid(this.currentNode)) {
+ return;
+ }
+
+ this._hide();
+ this._stopRefreshLoop();
+ this.currentNode = null;
+ this.currentQuads = {};
+ this.options = null;
+
+ this.emit("hidden");
+ },
+
+ /**
+ * Whether the current node is valid for this highlighter type.
+ * This is implemented by default to check if the node is an element node. Highlighter
+ * sub-classes should override this method if they want to highlight other node types.
+ * @param {DOMNode} node
+ * @return {Boolean}
+ */
+ _isNodeValid: function (node) {
+ return isNodeValid(node);
+ },
+
+ /**
+ * Are the provided options the same as the currently stored options?
+ * Returns false if there are no options stored currently.
+ */
+ _isSameOptions: function (options) {
+ if (!this.options) {
+ return false;
+ }
+
+ let keys = Object.keys(options);
+
+ if (keys.length !== Object.keys(this.options).length) {
+ return false;
+ }
+
+ for (let key of keys) {
+ if (this.options[key] !== options[key]) {
+ return false;
+ }
+ }
+
+ 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] = 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}
+ */
+ _hasMoved: function () {
+ let oldQuads = JSON.stringify(this.currentQuads);
+ this._updateAdjustedQuads();
+ let newQuads = JSON.stringify(this.currentQuads);
+ return oldQuads !== newQuads;
+ },
+
+ /**
+ * Update the highlighter if the node has moved since the last update.
+ */
+ update: function () {
+ if (!this._isNodeValid(this.currentNode) || !this._hasMoved()) {
+ return;
+ }
+
+ this._update();
+ this.emit("updated");
+ },
+
+ _show: function () {
+ // To be implemented by sub classes
+ // When called, sub classes should actually show the highlighter for
+ // 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 scroll, zoom or repaint
+ throw new Error("Custom highlighter class had to implement _update method");
+ },
+
+ _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;
+ this.rafID = win.requestAnimationFrame(this._startRefreshLoop.bind(this));
+ this.rafWin = win;
+ this.update();
+ },
+
+ _stopRefreshLoop: function () {
+ if (this.rafID && !Cu.isDeadWrapper(this.rafWin)) {
+ this.rafWin.cancelAnimationFrame(this.rafID);
+ }
+ this.rafID = this.rafWin = null;
+ },
+
+ destroy: function () {
+ this.hide();
+
+ this.highlighterEnv = null;
+ this.currentNode = null;
+ }
+};
+exports.AutoRefreshHighlighter = AutoRefreshHighlighter;