From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- .../server/actors/highlighters/auto-refresh.js | 215 ++++++ devtools/server/actors/highlighters/box-model.js | 712 ++++++++++++++++++++ devtools/server/actors/highlighters/css-grid.js | 737 +++++++++++++++++++++ .../server/actors/highlighters/css-transform.js | 243 +++++++ devtools/server/actors/highlighters/eye-dropper.js | 534 +++++++++++++++ .../server/actors/highlighters/geometry-editor.js | 704 ++++++++++++++++++++ .../server/actors/highlighters/measuring-tool.js | 563 ++++++++++++++++ devtools/server/actors/highlighters/moz.build | 23 + devtools/server/actors/highlighters/rect.js | 102 +++ devtools/server/actors/highlighters/rulers.js | 294 ++++++++ devtools/server/actors/highlighters/selector.js | 83 +++ .../server/actors/highlighters/simple-outline.js | 67 ++ .../server/actors/highlighters/utils/markup.js | 609 +++++++++++++++++ .../server/actors/highlighters/utils/moz.build | 9 + 14 files changed, 4895 insertions(+) create mode 100644 devtools/server/actors/highlighters/auto-refresh.js create mode 100644 devtools/server/actors/highlighters/box-model.js create mode 100644 devtools/server/actors/highlighters/css-grid.js create mode 100644 devtools/server/actors/highlighters/css-transform.js create mode 100644 devtools/server/actors/highlighters/eye-dropper.js create mode 100644 devtools/server/actors/highlighters/geometry-editor.js create mode 100644 devtools/server/actors/highlighters/measuring-tool.js create mode 100644 devtools/server/actors/highlighters/moz.build create mode 100644 devtools/server/actors/highlighters/rect.js create mode 100644 devtools/server/actors/highlighters/rulers.js create mode 100644 devtools/server/actors/highlighters/selector.js create mode 100644 devtools/server/actors/highlighters/simple-outline.js create mode 100644 devtools/server/actors/highlighters/utils/markup.js create mode 100644 devtools/server/actors/highlighters/utils/moz.build (limited to 'devtools/server/actors/highlighters') 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; diff --git a/devtools/server/actors/highlighters/box-model.js b/devtools/server/actors/highlighters/box-model.js new file mode 100644 index 000000000..35f201a04 --- /dev/null +++ b/devtools/server/actors/highlighters/box-model.js @@ -0,0 +1,712 @@ + /* 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 { extend } = require("sdk/core/heritage"); +const { AutoRefreshHighlighter } = require("./auto-refresh"); +const { + CanvasFrameAnonymousContentHelper, + createNode, + createSVGNode, + getBindingElementAndPseudo, + hasPseudoClassLock, + isNodeValid, + moveInfobar, +} = require("./utils/markup"); +const { setIgnoreLayoutChanges } = require("devtools/shared/layout/utils"); +const inspector = require("devtools/server/actors/inspector"); +const nodeConstants = require("devtools/shared/dom-node-constants"); + +// 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"]; +const BOX_MODEL_SIDES = ["top", "right", "bottom", "left"]; +// Width of boxmodelhighlighter guides +const GUIDE_STROKE_WIDTH = 1; +// FIXME: add ":visited" and ":link" after bug 713106 is fixed +const PSEUDO_CLASSES = [":hover", ":active", ":focus"]; + +/** + * 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. + * + * Usage example: + * + * let h = new BoxModelHighlighter(env); + * h.show(node, options); + * h.hide(); + * h.destroy(); + * + * Available options: + * - region {String} + * "content", "padding", "border" or "margin" + * This specifies the region that the guides should outline. + * Defaults to "content" + * - hideGuides {Boolean} + * Defaults to false + * - hideInfoBar {Boolean} + * Defaults to false + * - showOnly {String} + * "content", "padding", "border" or "margin" + * If set, only this region will be highlighted. Use with onlyRegionArea to + * only highlight the area of the region. + * - onlyRegionArea {Boolean} + * This can be set to true to make each region's box only highlight the area + * of the corresponding region rather than the area of nested regions too. + * This is useful when used with showOnly. + * + * Structure: + *
+ *
+ * + *
+ *
+ *
+ *
+ * Node name + * Node id + * .someClass + * :hover + *
+ *
+ *
+ *
+ *
+ *
+ */ +function BoxModelHighlighter(highlighterEnv) { + AutoRefreshHighlighter.call(this, highlighterEnv); + + this.markup = new CanvasFrameAnonymousContentHelper(this.highlighterEnv, + this._buildMarkup.bind(this)); + + /** + * Optionally customize each region's fill color by adding an entry to the + * regionFill property: `highlighter.regionFill.margin = "red"; + */ + this.regionFill = {}; + + this.onWillNavigate = this.onWillNavigate.bind(this); + + this.highlighterEnv.on("will-navigate", this.onWillNavigate); +} + +BoxModelHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, { + typeName: "BoxModelHighlighter", + + ID_CLASS_PREFIX: "box-model-", + + _buildMarkup: function () { + let doc = this.win.document; + + let highlighterContainer = doc.createElement("div"); + highlighterContainer.className = "highlighter-container box-model"; + + // Build the root wrapper, used to adapt to the page zoom. + let rootWrapper = createNode(this.win, { + parent: highlighterContainer, + attributes: { + "id": "root", + "class": "root" + }, + prefix: this.ID_CLASS_PREFIX + }); + + // Building the SVG element with its polygons and lines + + let svg = createSVGNode(this.win, { + nodeType: "svg", + parent: rootWrapper, + attributes: { + "id": "elements", + "width": "100%", + "height": "100%", + "hidden": "true" + }, + prefix: this.ID_CLASS_PREFIX + }); + + let regions = createSVGNode(this.win, { + nodeType: "g", + parent: svg, + attributes: { + "class": "regions" + }, + prefix: this.ID_CLASS_PREFIX + }); + + for (let region of BOX_MODEL_REGIONS) { + createSVGNode(this.win, { + nodeType: "path", + parent: regions, + attributes: { + "class": region, + "id": region + }, + prefix: this.ID_CLASS_PREFIX + }); + } + + for (let side of BOX_MODEL_SIDES) { + createSVGNode(this.win, { + nodeType: "line", + parent: svg, + attributes: { + "class": "guide-" + side, + "id": "guide-" + side, + "stroke-width": GUIDE_STROKE_WIDTH + }, + prefix: this.ID_CLASS_PREFIX + }); + } + + // Building the nodeinfo bar markup + + let infobarContainer = createNode(this.win, { + parent: rootWrapper, + attributes: { + "class": "infobar-container", + "id": "infobar-container", + "position": "top", + "hidden": "true" + }, + prefix: this.ID_CLASS_PREFIX + }); + + let infobar = createNode(this.win, { + parent: infobarContainer, + attributes: { + "class": "infobar" + }, + prefix: this.ID_CLASS_PREFIX + }); + + let texthbox = createNode(this.win, { + parent: infobar, + attributes: { + "class": "infobar-text" + }, + prefix: this.ID_CLASS_PREFIX + }); + createNode(this.win, { + nodeType: "span", + parent: texthbox, + attributes: { + "class": "infobar-tagname", + "id": "infobar-tagname" + }, + prefix: this.ID_CLASS_PREFIX + }); + createNode(this.win, { + nodeType: "span", + parent: texthbox, + attributes: { + "class": "infobar-id", + "id": "infobar-id" + }, + prefix: this.ID_CLASS_PREFIX + }); + createNode(this.win, { + nodeType: "span", + parent: texthbox, + attributes: { + "class": "infobar-classes", + "id": "infobar-classes" + }, + prefix: this.ID_CLASS_PREFIX + }); + createNode(this.win, { + nodeType: "span", + parent: texthbox, + attributes: { + "class": "infobar-pseudo-classes", + "id": "infobar-pseudo-classes" + }, + prefix: this.ID_CLASS_PREFIX + }); + createNode(this.win, { + nodeType: "span", + parent: texthbox, + attributes: { + "class": "infobar-dimensions", + "id": "infobar-dimensions" + }, + prefix: this.ID_CLASS_PREFIX + }); + + return highlighterContainer; + }, + + /** + * Destroy the nodes. Remove listeners. + */ + destroy: function () { + this.highlighterEnv.off("will-navigate", this.onWillNavigate); + this.markup.destroy(); + AutoRefreshHighlighter.prototype.destroy.call(this); + }, + + getElement: function (id) { + return this.markup.getElement(this.ID_CLASS_PREFIX + id); + }, + + /** + * Override the AutoRefreshHighlighter's _isNodeValid method to also return true for + * text nodes since these can also be highlighted. + * @param {DOMNode} node + * @return {Boolean} + */ + _isNodeValid: function (node) { + return node && (isNodeValid(node) || isNodeValid(node, nodeConstants.TEXT_NODE)); + }, + + /** + * Show the highlighter on a given node + */ + _show: function () { + if (BOX_MODEL_REGIONS.indexOf(this.options.region) == -1) { + this.options.region = "content"; + } + + let shown = this._update(); + this._trackMutations(); + this.emit("ready"); + return shown; + }, + + /** + * Track the current node markup mutations so that the node info bar can be + * updated to reflects the node's attributes + */ + _trackMutations: function () { + if (isNodeValid(this.currentNode)) { + let win = this.currentNode.ownerDocument.defaultView; + this.currentNodeObserver = new win.MutationObserver(this.update); + this.currentNodeObserver.observe(this.currentNode, {attributes: true}); + } + }, + + _untrackMutations: function () { + if (isNodeValid(this.currentNode) && this.currentNodeObserver) { + this.currentNodeObserver.disconnect(); + this.currentNodeObserver = null; + } + }, + + /** + * Update the highlighter on the current highlighted node (the one that was + * passed as an argument to show(node)). + * Should be called whenever node size or attributes change + */ + _update: function () { + let shown = false; + setIgnoreLayoutChanges(true); + + if (this._updateBoxModel()) { + // Show the infobar only if configured to do so and the node is an element or a text + // node. + if (!this.options.hideInfoBar && ( + this.currentNode.nodeType === this.currentNode.ELEMENT_NODE || + this.currentNode.nodeType === this.currentNode.TEXT_NODE)) { + this._showInfobar(); + } else { + this._hideInfobar(); + } + this._showBoxModel(); + shown = true; + } else { + // Nothing to highlight (0px rectangle like a