Bug 1317102 - Display multiple grid containers in the CSS Grid Inspector. r=pbro
MozReview-Commit-ID: 6LN6QE7k2yX
--- a/devtools/client/inspector/grids/grid-inspector.js
+++ b/devtools/client/inspector/grids/grid-inspector.js
@@ -175,25 +175,25 @@ class GridInspector {
* The color fetched from the custom palette, if it exists.
* @param {String} fallbackColor
* The color to use if no color could be found for the node front.
* @return {String} color
* The color to use.
*/
getInitialGridColor(nodeFront, customColor, fallbackColor) {
let highlighted = this._highlighters &&
- nodeFront == this.highlighters.gridHighlighterShown;
+ this.highlighters.gridHighlightersShown.includes(nodeFront);
let color;
if (customColor) {
color = customColor;
- } else if (highlighted && this.highlighters.state.grid.options) {
+ } else if (highlighted && this.highlighters.state.grids.has(nodeFront.actorID)) {
// If the node front is currently highlighted, use the color from the highlighter
// options.
- color = this.highlighters.state.grid.options.color;
+ color = this.highlighters.state.grids.get(nodeFront).options.color;
} else {
// Otherwise use the color defined in the store for this node front.
color = this.getGridColorForNodeFront(nodeFront);
}
return color || fallbackColor;
}
@@ -219,31 +219,31 @@ class GridInspector {
/**
* Retrieve the shared SwatchColorPicker instance.
*/
getSwatchColorPickerTooltip() {
return this.swatchColorPickerTooltip;
}
/**
- * Given a list of new grid fronts, and if we have a currently highlighted grid, check
+ * Given a list of new grid fronts, and if there are highlighted grids, check
* if its fragments have changed.
*
* @param {Array} newGridFronts
* A list of GridFront objects.
* @return {Boolean}
*/
haveCurrentFragmentsChanged(newGridFronts) {
- const currentNode = this.highlighters.gridHighlighterShown;
- if (!currentNode) {
+ if (!this.highlighters.gridHighlightersShown) {
return false;
}
- const newGridFront = newGridFronts.find(g => g.containerNodeFront === currentNode);
- if (!newGridFront) {
+ const gridFronts = newGridFronts.find(g =>
+ this.highlighters.gridHighlightersShown.includes(g.containerNodeFront));
+ if (!gridFronts.length) {
return false;
}
const { grids } = this.store.getState();
const oldFragments = grids.find(g => g.nodeFront === currentNode).gridFragments;
const newFragments = newGridFront.gridFragments;
return !compareFragmentsGeometry(oldFragments, newFragments);
@@ -448,19 +448,18 @@ class GridInspector {
// Otherwise, continue comparing with the new grids.
const newNodeFronts = newGridFronts.filter(grid => grid.containerNodeFront)
.map(grid => grid.containerNodeFront.actorID);
if (grids.length === newGridFronts.length &&
oldNodeFronts.sort().join(",") == newNodeFronts.sort().join(",")) {
// Same list of containers, but let's check if the geometry of the current grid has
// changed, if it hasn't we can safely abort.
if (!this._highlighters ||
- !this.highlighters.gridHighlighterShown ||
- (this.highlighters.gridHighlighterShown &&
- !this.haveCurrentFragmentsChanged(newGridFronts))) {
+ !this.highlighters.gridHighlightersShown.length ||
+ !this.haveCurrentFragmentsChanged(newGridFronts)) {
return;
}
}
// Either the list of containers or the current fragments have changed, do update.
this.updateGridPanel(newGridFronts);
}
--- a/devtools/client/inspector/grids/reducers/grids.js
+++ b/devtools/client/inspector/grids/reducers/grids.js
@@ -23,21 +23,21 @@ let reducers = {
return g;
});
return newGrids;
},
[UPDATE_GRID_HIGHLIGHTED](grids, { nodeFront, highlighted }) {
return grids.map(g => {
- let isUpdatedNode = g.nodeFront === nodeFront;
+ if (g.nodeFront == nodeFront) {
+ g = Object.assign({}, g, { highlighted });
+ }
- return Object.assign({}, g, {
- highlighted: isUpdatedNode && highlighted
- });
+ return g;
});
},
[UPDATE_GRIDS](_, { grids }) {
return grids;
},
};
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -527,18 +527,18 @@ TextPropertyEditor.prototype = {
this.ruleView.inspector.selection.nodeFront) {
flexToggle.classList.add("active");
}
}
let gridToggle = this.valueSpan.querySelector(".ruleview-grid");
if (gridToggle) {
gridToggle.setAttribute("title", l10n("rule.gridToggle.tooltip"));
- if (this.ruleView.highlighters.gridHighlighterShown ===
- this.ruleView.inspector.selection.nodeFront) {
+ if (this.ruleView.highlighters.gridHighlightersShown.includes(
+ this.ruleView.inspector.selection.nodeFront)) {
gridToggle.classList.add("active");
}
}
let shapeToggle = this.valueSpan.querySelector(".ruleview-shapeswatch");
if (shapeToggle) {
let mode = "css" + name.split("-").map(s => {
return s[0].toUpperCase() + s.slice(1);
--- a/devtools/client/inspector/shared/highlighters-overlay.js
+++ b/devtools/client/inspector/shared/highlighters-overlay.js
@@ -28,48 +28,53 @@ const SHOW_INFINITE_LINES_PREF = "devtoo
* Highlighters overlay is a singleton managing all highlighters in the Inspector.
*/
class HighlightersOverlay {
/**
* @param {Inspector} inspector
* Inspector toolbox panel.
*/
constructor(inspector) {
- /*
- * Collection of instantiated highlighter actors like FlexboxHighlighter,
- * CssGridHighlighter, ShapesHighlighter and GeometryEditorHighlighter.
- */
+ /**
+ * Collection of instantiated highlighter actors like FlexboxHighlighter,
+ * ShapesHighlighter and GeometryEditorHighlighter.
+ */
this.highlighters = {};
- /*
- * Collection of instantiated in-context editors, like ShapesInContextEditor, which
- * behave like highlighters but with added editing capabilities that need to map value
- * changes to properties in the Rule view.
- */
+ // Collection of instantiated grid highlighter actors.
+ this.gridHighlighters = new Map();
+ /**
+ * Collection of instantiated in-context editors, like ShapesInContextEditor, which
+ * behave like highlighters but with added editing capabilities that need to map value
+ * changes to properties in the Rule view.
+ */
this.editors = {};
this.inspector = inspector;
this.highlighterUtils = this.inspector.toolbox.highlighterUtils;
this.store = this.inspector.store;
this.telemetry = inspector.telemetry;
// NodeFront of the flexbox container that is highlighted.
this.flexboxHighlighterShown = null;
// NodeFront of element that is highlighted by the geometry editor.
this.geometryEditorHighlighterShown = null;
- // NodeFront of the grid container that is highlighted.
- this.gridHighlighterShown = null;
// Name of the highlighter shown on mouse hover.
this.hoveredHighlighterShown = null;
// Name of the selector highlighter shown.
this.selectorHighlighterShown = null;
// NodeFront of the shape that is highlighted
this.shapesHighlighterShown = null;
+
+ // Array of grid containers that are highlighted.
+ this.gridHighlightersShown = [];
+
// Saved state to be restore on page navigation.
this.state = {
flexbox: {},
- grid: {},
+ // Map of grid NodeFront actorID to the their stored grid options
+ grids: new Map(),
shapes: {},
};
this.onClick = this.onClick.bind(this);
this.onMarkupMutation = this.onMarkupMutation.bind(this);
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseOut = this.onMouseOut.bind(this);
this.onWillNavigate = this.onWillNavigate.bind(this);
@@ -144,17 +149,17 @@ class HighlightersOverlay {
dispatch(updateShowGridAreas(showGridAreas));
dispatch(updateShowGridLineNumbers(showGridLineNumbers));
dispatch(updateShowInfiniteLines(showInfinteLines));
}
/**
* Toggle the shapes highlighter for the given node.
-
+ *
* @param {NodeFront} node
* The NodeFront of the element with a shape to highlight.
* @param {Object} options
* Object used for passing options to the shapes highlighter.
* @param {TextProperty} textProperty
* TextProperty where to write changes.
*/
async toggleShapesHighlighter(node, options, textProperty) {
@@ -338,17 +343,17 @@ class HighlightersOverlay {
* @param {NodeFront} node
* The NodeFront of the grid container element to highlight.
* @param. {String|null} trigger
* String name matching "grid" or "rule" to indicate where the
* grid highlighter was toggled on from. "grid" represents the grid view
* "rule" represents the rule view.
*/
async toggleGridHighlighter(node, trigger) {
- if (node == this.gridHighlighterShown) {
+ if (this.gridHighlighters.has(node)) {
await this.hideGridHighlighter(node);
return;
}
await this.showGridHighlighter(node, {}, trigger);
}
/**
@@ -359,17 +364,17 @@ class HighlightersOverlay {
* @param {Object} options
* Object used for passing options to the grid highlighter.
* @param. {String|null} trigger
* String name matching "grid" or "rule" to indicate where the
* grid highlighter was toggled on from. "grid" represents the grid view
* "rule" represents the rule view.
*/
async showGridHighlighter(node, options, trigger) {
- let highlighter = await this._getHighlighter("CssGridHighlighter");
+ let highlighter = await this._getGridHighlighter(node);
if (!highlighter) {
return;
}
options = Object.assign({}, options, this.getGridHighlighterSettings(node));
let isShown = await highlighter.show(node, options);
if (!isShown) {
@@ -383,49 +388,52 @@ class HighlightersOverlay {
} else if (trigger == "rule") {
this.telemetry.scalarAdd("devtools.rules.gridinspector.opened", 1);
}
try {
// Save grid highlighter state.
let { url } = this.inspector.target;
let selector = await node.getUniqueSelector();
- this.state.grid = { selector, options, url };
- this.gridHighlighterShown = node;
+ this.state.grids.set(node.actorID, { selector, options, url });
+ this.gridHighlightersShown.push(node);
+
// Emit the NodeFront of the grid container element that the grid highlighter was
// shown for.
this.emit("grid-highlighter-shown", node, options);
} catch (e) {
this._handleRejection(e);
}
}
/**
* Hide the grid highlighter for the given grid container element.
*
* @param {NodeFront} node
* The NodeFront of the grid container element to unhighlight.
*/
async hideGridHighlighter(node) {
- if (!this.gridHighlighterShown || !this.highlighters.CssGridHighlighter) {
+ if (!this.gridHighlighters.has(node)) {
return;
}
this._toggleRuleViewIcon(node, false, ".ruleview-grid");
- await this.highlighters.CssGridHighlighter.hide();
+ const highlighter = this.gridHighlighters.get(node);
+ await highlighter.finalize();
+
+ this.gridHighlightersShown = this.gridHighlightersShown.filter(item => item != node);
+ this.gridHighlighters.delete(node);
// Emit the NodeFront of the grid container element that the grid highlighter was
// hidden for.
- const nodeFront = this.gridHighlighterShown;
- this.gridHighlighterShown = null;
- this.emit("grid-highlighter-hidden", nodeFront, this.state.grid.options);
+ this.emit("grid-highlighter-hidden", node,
+ this.state.grids.get(node.actorID).options);
- // Erase grid highlighter state.
- this.state.grid = {};
+ this.state.grids.delete(node.actorID);
}
/**
* Show the box model highlighter for the given node.
*
* @param {NodeFront} node
* The NodeFront of the element to highlight.
* @param {Object} options
@@ -519,17 +527,19 @@ class HighlightersOverlay {
}
}
/**
* Restores the saved grid highlighter state.
*/
async restoreGridState() {
try {
- await this.restoreState("grid", this.state.grid, this.showGridHighlighter);
+ for (let gridState of this.state.grids.values()) {
+ await this.restoreState("grid", gridState, this.showGridHighlighter);
+ }
} catch (e) {
this._handleRejection(e);
}
}
/**
* Helper function called by restoreFlexboxState, restoreGridState.
* Restores the saved highlighter state for the given highlighter
@@ -565,28 +575,28 @@ class HighlightersOverlay {
await showFunction(nodeFront, options);
this.emit(`${name}-state-restored`, { restored: true });
}
this.emit(`${name}-state-restored`, { restored: false });
}
/**
- * Get an instance of an in-context editor for the given type.
- *
- * In-context editors behave like highlighters but with added editing capabilities which
- * need to write value changes back to something, like to properties in the Rule view.
- * They typically exist in the context of the page, like the ShapesInContextEditor.
- *
- * @param {String} type
- * Type of in-context editor. Currently supported: "shapesEditor"
- *
- * @return {Object|null}
- * Reference to instance for given type of in-context editor or null.
- */
+ * Get an instance of an in-context editor for the given type.
+ *
+ * In-context editors behave like highlighters but with added editing capabilities which
+ * need to write value changes back to something, like to properties in the Rule view.
+ * They typically exist in the context of the page, like the ShapesInContextEditor.
+ *
+ * @param {String} type
+ * Type of in-context editor. Currently supported: "shapesEditor"
+ *
+ * @return {Object|null}
+ * Reference to instance for given type of in-context editor or null.
+ */
async getInContextEditor(type) {
if (this.editors[type]) {
return this.editors[type];
}
let editor;
switch (type) {
@@ -632,16 +642,48 @@ class HighlightersOverlay {
// Ignore any error
}
if (!highlighter) {
return null;
}
this.highlighters[type] = highlighter;
+
+ return highlighter;
+ }
+
+ /**
+ * Get a grid highlighter front for a given node. It will initialize a new grid
+ * highlighter for every unique node.
+ *
+ * @param {NodeFront} node
+ * The NodeFront of the grid container element to highlight.
+ * @return {Promise} that resolves to the grid highlighter front.
+ */
+ async _getGridHighlighter(node) {
+ if (this.gridHighlighters.has(node)) {
+ return this.gridHighlighters.get(node);
+ }
+
+ let utils = this.highlighterUtils;
+ let highlighter;
+
+ try {
+ highlighter = await utils.getHighlighterByType("CssGridHighlighter");
+ } catch (e) {
+ // Ignore any error
+ }
+
+ if (!highlighter) {
+ return null;
+ }
+
+ this.gridHighlighters.set(node, highlighter);
+
return highlighter;
}
_handleRejection(error) {
if (!this.destroyed) {
console.error(error);
}
}
@@ -914,88 +956,104 @@ class HighlightersOverlay {
async onMarkupMutation(mutations) {
let hasInterestingMutation = mutations.some(mut => mut.type === "childList");
if (!hasInterestingMutation) {
// Bail out if the mutations did not remove nodes, or if no grid highlighter is
// displayed.
return;
}
+ for (let grid of this.gridHighlightersShown) {
+ this._hideHighlighterIfDeadNode(grid, this.hideGridHighlighter);
+ }
+
this._hideHighlighterIfDeadNode(this.flexboxHighlighterShown,
this.hideFlexboxHighlighter);
- this._hideHighlighterIfDeadNode(this.gridHighlighterShown,
- this.hideGridHighlighter);
this._hideHighlighterIfDeadNode(this.shapesHighlighterShown,
this.hideShapesHighlighter);
}
/**
* Clear saved highlighter shown properties on will-navigate.
*/
onWillNavigate() {
+ this.destroyEditors();
+
+ this.gridHighlightersShown = [];
+
this.boxModelHighlighterShown = null;
this.flexboxHighlighterShown = null;
this.geometryEditorHighlighterShown = null;
- this.gridHighlighterShown = null;
this.hoveredHighlighterShown = null;
this.selectorHighlighterShown = null;
this.shapesHighlighterShown = null;
- this.destroyEditors();
}
/**
- * Destroy and clean-up all instances of in-context editors.
- */
+ * Destroy and clean-up all instances of in-context editors.
+ */
destroyEditors() {
for (let type in this.editors) {
this.editors[type].off("show");
this.editors[type].off("hide");
this.editors[type].destroy();
}
- this.editors = {};
+ this.editors = null;
}
/**
- * Destroy and clean-up all instances of highlighters.
- */
+ *
+ */
+ destroyGridHighlighters() {
+ for (let highlighter of this.gridHighlighters.values()) {
+ highlighter.finalize();
+ }
+
+ this.gridHighlighters.clear();
+ this.gridHighlighters = null;
+ }
+
+ /**
+ * Destroy and clean-up all instances of highlighters.
+ */
destroyHighlighters() {
for (let type in this.highlighters) {
if (this.highlighters[type]) {
this.highlighters[type].finalize();
this.highlighters[type] = null;
}
}
this.highlighters = null;
}
/**
* Destroy this overlay instance, removing it from the view and destroying
* all initialized highlighters.
*/
destroy() {
- this.destroyHighlighters();
- this.destroyEditors();
-
- // Remove inspector events.
this.inspector.off("markupmutation", this.onMarkupMutation);
this.inspector.target.off("will-navigate", this.onWillNavigate);
+ this.destroyEditors();
+ this.destroyGridHighlighters();
+ this.destroyHighlighters();
+
this._lastHovered = null;
this.inspector = null;
this.highlighterUtils = null;
this.state = null;
this.store = null;
this.boxModelHighlighterShown = null;
this.flexboxHighlighterShown = null;
this.geometryEditorHighlighterShown = null;
- this.gridHighlighterShown = null;
+ this.gridHighlightersShown = null;
this.hoveredHighlighterShown = null;
this.selectorHighlighterShown = null;
this.shapesHighlighterShown = null;
this.destroyed = true;
}
}