763 lines
24 KiB
JavaScript
763 lines
24 KiB
JavaScript
/* 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 flags = require("resource://devtools/shared/flags.js");
|
|
const { throttle } = require("resource://devtools/shared/throttle.js");
|
|
|
|
const gridsReducer = require("resource://devtools/client/inspector/grids/reducers/grids.js");
|
|
const highlighterSettingsReducer = require("resource://devtools/client/inspector/grids/reducers/highlighter-settings.js");
|
|
const {
|
|
updateGridColor,
|
|
updateGridHighlighted,
|
|
updateGrids,
|
|
} = require("resource://devtools/client/inspector/grids/actions/grids.js");
|
|
const {
|
|
updateShowGridAreas,
|
|
updateShowGridLineNumbers,
|
|
updateShowInfiniteLines,
|
|
} = require("resource://devtools/client/inspector/grids/actions/highlighter-settings.js");
|
|
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"compareFragmentsGeometry",
|
|
"resource://devtools/client/inspector/grids/utils/utils.js",
|
|
true
|
|
);
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"parseURL",
|
|
"resource://devtools/client/shared/source-utils.js",
|
|
true
|
|
);
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"asyncStorage",
|
|
"resource://devtools/shared/async-storage.js"
|
|
);
|
|
|
|
const SHOW_GRID_AREAS = "devtools.gridinspector.showGridAreas";
|
|
const SHOW_GRID_LINE_NUMBERS = "devtools.gridinspector.showGridLineNumbers";
|
|
const SHOW_INFINITE_LINES_PREF = "devtools.gridinspector.showInfiniteLines";
|
|
|
|
// Default grid colors.
|
|
const GRID_COLORS = [
|
|
"#9400FF",
|
|
"#DF00A9",
|
|
"#0A84FF",
|
|
"#12BC00",
|
|
"#EA8000",
|
|
"#00B0BD",
|
|
"#D70022",
|
|
"#4B42FF",
|
|
"#B5007F",
|
|
"#058B00",
|
|
"#A47F00",
|
|
"#005A71",
|
|
];
|
|
|
|
class GridInspector {
|
|
constructor(inspector, window) {
|
|
this.document = window.document;
|
|
this.inspector = inspector;
|
|
this.store = inspector.store;
|
|
this.telemetry = inspector.telemetry;
|
|
|
|
// Maximum number of grid highlighters that can be displayed.
|
|
this.maxHighlighters = Services.prefs.getIntPref(
|
|
"devtools.gridinspector.maxHighlighters"
|
|
);
|
|
|
|
this.store.injectReducer("grids", gridsReducer);
|
|
this.store.injectReducer("highlighterSettings", highlighterSettingsReducer);
|
|
|
|
this.onHighlighterShown = this.onHighlighterShown.bind(this);
|
|
this.onHighlighterHidden = this.onHighlighterHidden.bind(this);
|
|
this.onNavigate = this.onNavigate.bind(this);
|
|
this.onReflow = throttle(this.onReflow, 500, this);
|
|
this.onSetGridOverlayColor = this.onSetGridOverlayColor.bind(this);
|
|
this.onSidebarSelect = this.onSidebarSelect.bind(this);
|
|
this.onToggleGridHighlighter = this.onToggleGridHighlighter.bind(this);
|
|
this.onToggleShowGridAreas = this.onToggleShowGridAreas.bind(this);
|
|
this.onToggleShowGridLineNumbers =
|
|
this.onToggleShowGridLineNumbers.bind(this);
|
|
this.onToggleShowInfiniteLines = this.onToggleShowInfiniteLines.bind(this);
|
|
this.updateGridPanel = this.updateGridPanel.bind(this);
|
|
this.listenForGridHighlighterEvents =
|
|
this.listenForGridHighlighterEvents.bind(this);
|
|
|
|
this.init();
|
|
}
|
|
|
|
get highlighters() {
|
|
if (!this._highlighters) {
|
|
this._highlighters = this.inspector.highlighters;
|
|
}
|
|
|
|
return this._highlighters;
|
|
}
|
|
|
|
/**
|
|
* Initializes the grid inspector by fetching the LayoutFront from the walker and
|
|
* loading the highlighter settings.
|
|
*/
|
|
async init() {
|
|
if (!this.inspector) {
|
|
return;
|
|
}
|
|
|
|
if (flags.testing) {
|
|
// In tests, we start listening immediately to avoid having to simulate a mousemove.
|
|
this.listenForGridHighlighterEvents();
|
|
} else {
|
|
this.document.addEventListener(
|
|
"mousemove",
|
|
this.listenForGridHighlighterEvents,
|
|
{
|
|
once: true,
|
|
}
|
|
);
|
|
}
|
|
|
|
this.inspector.sidebar.on("select", this.onSidebarSelect);
|
|
this.inspector.on("new-root", this.onNavigate);
|
|
|
|
this.onSidebarSelect();
|
|
}
|
|
|
|
listenForGridHighlighterEvents() {
|
|
this.highlighters.on("grid-highlighter-hidden", this.onHighlighterHidden);
|
|
this.highlighters.on("grid-highlighter-shown", this.onHighlighterShown);
|
|
}
|
|
|
|
/**
|
|
* Get the LayoutActor fronts for all interesting targets where we have inspectors.
|
|
*
|
|
* @return {Array} The list of LayoutActor fronts
|
|
*/
|
|
async getLayoutFronts() {
|
|
const inspectorFronts = await this.inspector.getAllInspectorFronts();
|
|
const layoutFronts = await Promise.all(
|
|
inspectorFronts.map(({ walker }) => walker.getLayoutInspector())
|
|
);
|
|
return layoutFronts.filter(front => !front.isDestroyed());
|
|
}
|
|
|
|
/**
|
|
* Destruction function called when the inspector is destroyed. Removes event listeners
|
|
* and cleans up references.
|
|
*/
|
|
destroy() {
|
|
if (this._highlighters) {
|
|
this.highlighters.off(
|
|
"grid-highlighter-hidden",
|
|
this.onHighlighterHidden
|
|
);
|
|
this.highlighters.off("grid-highlighter-shown", this.onHighlighterShown);
|
|
}
|
|
this.document.removeEventListener(
|
|
"mousemove",
|
|
this.listenForGridHighlighterEvents
|
|
);
|
|
|
|
this.inspector.sidebar.off("select", this.onSidebarSelect);
|
|
this.inspector.off("new-root", this.onNavigate);
|
|
|
|
this.inspector.off("reflow", this.onReflow);
|
|
|
|
this._highlighters = null;
|
|
this.document = null;
|
|
this.inspector = null;
|
|
this.store = null;
|
|
}
|
|
|
|
getComponentProps() {
|
|
return {
|
|
onSetGridOverlayColor: this.onSetGridOverlayColor,
|
|
onToggleGridHighlighter: this.onToggleGridHighlighter,
|
|
onToggleShowGridAreas: this.onToggleShowGridAreas,
|
|
onToggleShowGridLineNumbers: this.onToggleShowGridLineNumbers,
|
|
onToggleShowInfiniteLines: this.onToggleShowInfiniteLines,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns the initial color linked to a grid container. Will attempt to check the
|
|
* current grid highlighter state and the store.
|
|
*
|
|
* @param {NodeFront} nodeFront
|
|
* The NodeFront for which we need the color.
|
|
* @param {String} customColor
|
|
* 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) {
|
|
const highlighted = this.highlighters.gridHighlighters.has(nodeFront);
|
|
|
|
let color;
|
|
if (customColor) {
|
|
color = customColor;
|
|
} 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.grids.get(nodeFront.actorID).options
|
|
.color;
|
|
} else {
|
|
// Otherwise use the color defined in the store for this node front.
|
|
color = this.getGridColorForNodeFront(nodeFront);
|
|
}
|
|
|
|
return color || fallbackColor;
|
|
}
|
|
|
|
/**
|
|
* Returns the color set for the grid highlighter associated with the provided
|
|
* nodeFront.
|
|
*
|
|
* @param {NodeFront} nodeFront
|
|
* The NodeFront for which we need the color.
|
|
*/
|
|
getGridColorForNodeFront(nodeFront) {
|
|
const { grids } = this.store.getState();
|
|
|
|
for (const grid of grids) {
|
|
if (grid.nodeFront === nodeFront) {
|
|
return grid.color;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Given a list of new grid fronts, and if there are highlighted grids, check
|
|
* if their fragments have changed.
|
|
*
|
|
* @param {Array} newGridFronts
|
|
* A list of GridFront objects.
|
|
* @return {Boolean}
|
|
*/
|
|
haveCurrentFragmentsChanged(newGridFronts) {
|
|
const gridHighlighters = this.highlighters.gridHighlighters;
|
|
|
|
if (!gridHighlighters.size) {
|
|
return false;
|
|
}
|
|
|
|
const gridFronts = newGridFronts.filter(g =>
|
|
gridHighlighters.has(g.containerNodeFront)
|
|
);
|
|
if (!gridFronts.length) {
|
|
return false;
|
|
}
|
|
|
|
const { grids } = this.store.getState();
|
|
|
|
for (const node of gridHighlighters.keys()) {
|
|
const oldFragments = grids.find(g => g.nodeFront === node).gridFragments;
|
|
const newFragments = newGridFronts.find(
|
|
g => g.containerNodeFront === node
|
|
).gridFragments;
|
|
|
|
if (!compareFragmentsGeometry(oldFragments, newFragments)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the layout panel is visible, and false otherwise.
|
|
*/
|
|
isPanelVisible() {
|
|
return (
|
|
this.inspector &&
|
|
this.inspector.toolbox &&
|
|
this.inspector.sidebar &&
|
|
this.inspector.toolbox.currentToolId === "inspector" &&
|
|
this.inspector.sidebar.getCurrentTabID() === "layoutview"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Updates the grid panel by dispatching the new grid data. This is called when the
|
|
* layout view becomes visible or the view needs to be updated with new grid data.
|
|
*/
|
|
async updateGridPanel() {
|
|
// Stop refreshing if the inspector or store is already destroyed.
|
|
if (!this.inspector || !this.store) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await this._updateGridPanel();
|
|
} catch (e) {
|
|
this._throwUnlessDestroyed(
|
|
e,
|
|
"Inspector destroyed while executing updateGridPanel"
|
|
);
|
|
}
|
|
}
|
|
|
|
async _updateGridPanel() {
|
|
const gridFronts = await this.getGrids();
|
|
|
|
if (!gridFronts.length) {
|
|
try {
|
|
this.store.dispatch(updateGrids([]));
|
|
this.inspector.emit("grid-panel-updated");
|
|
return;
|
|
} catch (e) {
|
|
// This call might fail if called asynchrously after the toolbox is finished
|
|
// closing.
|
|
return;
|
|
}
|
|
}
|
|
|
|
const currentUrl = this.inspector.currentTarget.url;
|
|
|
|
// Log how many CSS Grid elements DevTools sees.
|
|
if (currentUrl != this.inspector.previousURL) {
|
|
Glean.devtoolsInspector.numberOfCssGridsInAPage.accumulateSingleSample(
|
|
gridFronts.length
|
|
);
|
|
this.inspector.previousURL = currentUrl;
|
|
}
|
|
|
|
// Get the hostname, if there is no hostname, fall back on protocol
|
|
// ex: `data:` uri, and `about:` pages
|
|
const hostname =
|
|
parseURL(currentUrl).hostname || parseURL(currentUrl).protocol;
|
|
const customColors =
|
|
(await asyncStorage.getItem("gridInspectorHostColors")) || {};
|
|
|
|
const grids = [];
|
|
for (let i = 0; i < gridFronts.length; i++) {
|
|
const grid = gridFronts[i];
|
|
let nodeFront = grid.containerNodeFront;
|
|
|
|
// If the GridFront didn't yet have access to the NodeFront for its container, then
|
|
// get it from the walker. This happens when the walker hasn't yet seen this
|
|
// particular DOM Node in the tree yet, or when we are connected to an older server.
|
|
if (!nodeFront) {
|
|
try {
|
|
nodeFront = await grid.walkerFront.getNodeFromActor(grid.actorID, [
|
|
"containerEl",
|
|
]);
|
|
} catch (e) {
|
|
// This call might fail if called asynchrously after the toolbox is finished
|
|
// closing.
|
|
return;
|
|
}
|
|
}
|
|
|
|
const colorForHost = customColors[hostname]
|
|
? customColors[hostname][i]
|
|
: null;
|
|
const fallbackColor = GRID_COLORS[i % GRID_COLORS.length];
|
|
const color = this.getInitialGridColor(
|
|
nodeFront,
|
|
colorForHost,
|
|
fallbackColor
|
|
);
|
|
const highlighted = this.highlighters.gridHighlighters.has(nodeFront);
|
|
const disabled =
|
|
!highlighted &&
|
|
this.maxHighlighters > 1 &&
|
|
this.highlighters.gridHighlighters.size === this.maxHighlighters;
|
|
const isSubgrid = grid.isSubgrid;
|
|
const gridData = {
|
|
id: i,
|
|
actorID: grid.actorID,
|
|
color,
|
|
disabled,
|
|
direction: grid.direction,
|
|
gridFragments: grid.gridFragments,
|
|
highlighted,
|
|
isSubgrid,
|
|
nodeFront,
|
|
parentNodeActorID: null,
|
|
subgrids: [],
|
|
writingMode: grid.writingMode,
|
|
};
|
|
|
|
if (isSubgrid) {
|
|
let parentGridNodeFront;
|
|
|
|
try {
|
|
parentGridNodeFront =
|
|
await nodeFront.walkerFront.getParentGridNode(nodeFront);
|
|
} catch (e) {
|
|
// This call might fail if called asynchrously after the toolbox is finished
|
|
// closing.
|
|
return;
|
|
}
|
|
|
|
if (!parentGridNodeFront) {
|
|
return;
|
|
}
|
|
|
|
const parentIndex = grids.findIndex(
|
|
g => g.nodeFront.actorID === parentGridNodeFront.actorID
|
|
);
|
|
gridData.parentNodeActorID = parentGridNodeFront.actorID;
|
|
grids[parentIndex].subgrids.push(gridData.id);
|
|
}
|
|
|
|
grids.push(gridData);
|
|
}
|
|
|
|
// We need to make sure that nested subgrids are displayed above their parent grid
|
|
// containers, so update the z-index of each grid before rendering them.
|
|
for (const root of grids.filter(g => !g.parentNodeActorID)) {
|
|
this._updateZOrder(grids, root);
|
|
}
|
|
|
|
this.store.dispatch(updateGrids(grids));
|
|
this.inspector.emit("grid-panel-updated");
|
|
}
|
|
|
|
/**
|
|
* Get all GridFront instances from the server(s).
|
|
*
|
|
*
|
|
* @return {Array} The list of GridFronts
|
|
*/
|
|
async getGrids() {
|
|
const promises = [];
|
|
try {
|
|
const layoutFronts = await this.getLayoutFronts();
|
|
for (const layoutFront of layoutFronts) {
|
|
promises.push(layoutFront.getAllGrids());
|
|
}
|
|
} catch (e) {
|
|
// This call might fail if called asynchrously after the toolbox is finished closing
|
|
}
|
|
|
|
const gridFronts = (await Promise.all(promises)).flat();
|
|
return gridFronts;
|
|
}
|
|
|
|
/**
|
|
* Handler for "grid-highlighter-shown" events emitted from the
|
|
* HighlightersOverlay. Passes nodefront and event name to handleHighlighterChange.
|
|
* Required since on and off events need the same reference object.
|
|
*
|
|
* @param {NodeFront} nodeFront
|
|
* The NodeFront of the grid container element for which the grid
|
|
* highlighter is shown for.
|
|
*/
|
|
onHighlighterShown(nodeFront) {
|
|
this.onHighlighterChange(nodeFront, true);
|
|
}
|
|
|
|
/**
|
|
* Handler for "grid-highlighter-hidden" events emitted from the
|
|
* HighlightersOverlay. Passes nodefront and event name to handleHighlighterChange.
|
|
* Required since on and off events need the same reference object.
|
|
*
|
|
* @param {NodeFront} nodeFront
|
|
* The NodeFront of the grid container element for which the grid highlighter
|
|
* is hidden for.
|
|
*/
|
|
onHighlighterHidden(nodeFront) {
|
|
this.onHighlighterChange(nodeFront, false);
|
|
}
|
|
|
|
/**
|
|
* Handler for "grid-highlighter-shown" and "grid-highlighter-hidden" events emitted
|
|
* from the HighlightersOverlay. Updates the NodeFront's grid highlighted state.
|
|
*
|
|
* @param {NodeFront} nodeFront
|
|
* The NodeFront of the grid container element for which the grid highlighter
|
|
* is shown for.
|
|
* @param {Boolean} highlighted
|
|
* If the grid should be updated to highlight or hide.
|
|
*/
|
|
onHighlighterChange(nodeFront, highlighted) {
|
|
if (!this.isPanelVisible()) {
|
|
return;
|
|
}
|
|
|
|
const { grids } = this.store.getState();
|
|
const grid = grids.find(g => g.nodeFront === nodeFront);
|
|
|
|
if (!grid || grid.highlighted === highlighted) {
|
|
return;
|
|
}
|
|
|
|
this.store.dispatch(updateGridHighlighted(nodeFront, highlighted));
|
|
}
|
|
|
|
/**
|
|
* Handler for "new-root" event fired by the inspector, which indicates a page
|
|
* navigation. Updates grid panel contents.
|
|
*/
|
|
onNavigate() {
|
|
if (this.isPanelVisible()) {
|
|
this.updateGridPanel();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handler for reflow events fired by the inspector when a node is selected. On reflows,
|
|
* update the grid panel content, because the shape or number of grids on the page may
|
|
* have changed.
|
|
*
|
|
* Note that there may be frequent reflows on the page and that not all of them actually
|
|
* cause the grids to change. So, we want to limit how many times we update the grid
|
|
* panel to only reflows that actually either change the list of grids, or those that
|
|
* change the current outlined grid.
|
|
* To achieve this, this function compares the list of grid containers from before and
|
|
* after the reflow, as well as the grid fragment data on the currently highlighted
|
|
* grid.
|
|
*/
|
|
async onReflow() {
|
|
try {
|
|
if (!this.isPanelVisible()) {
|
|
return;
|
|
}
|
|
|
|
// The list of grids currently displayed.
|
|
const { grids } = this.store.getState();
|
|
|
|
// The new list of grids from the server.
|
|
const newGridFronts = await this.getGrids();
|
|
|
|
// In some cases, the nodes for current grids may have been removed from the DOM in
|
|
// which case we need to update.
|
|
if (grids.length && grids.some(grid => !grid.nodeFront.actorID)) {
|
|
await this.updateGridPanel(newGridFronts);
|
|
return;
|
|
}
|
|
|
|
// Get the node front(s) from the current grid(s) so we can compare them to them to
|
|
// the node(s) of the new grids.
|
|
const oldNodeFronts = grids.map(grid => grid.nodeFront.actorID);
|
|
const newNodeFronts = newGridFronts
|
|
.filter(grid => grid.containerNode)
|
|
.map(grid => grid.containerNodeFront.actorID);
|
|
|
|
if (
|
|
grids.length === newGridFronts.length &&
|
|
oldNodeFronts.sort().join(",") == newNodeFronts.sort().join(",") &&
|
|
!this.haveCurrentFragmentsChanged(newGridFronts)
|
|
) {
|
|
// Same list of containers and the geometry of all the displayed grids remained the
|
|
// same, we can safely abort.
|
|
return;
|
|
}
|
|
|
|
// Either the list of containers or the current fragments have changed, do update.
|
|
await this.updateGridPanel(newGridFronts);
|
|
} catch (e) {
|
|
this._throwUnlessDestroyed(
|
|
e,
|
|
"Inspector destroyed while executing onReflow callback"
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
async onSetGridOverlayColor(node, color) {
|
|
this.store.dispatch(updateGridColor(node, color));
|
|
|
|
const { grids } = this.store.getState();
|
|
const currentUrl = this.inspector.currentTarget.url;
|
|
// Get the hostname, if there is no hostname, fall back on protocol
|
|
// ex: `data:` uri, and `about:` pages
|
|
const hostname =
|
|
parseURL(currentUrl).hostname || parseURL(currentUrl).protocol;
|
|
const customGridColors =
|
|
(await asyncStorage.getItem("gridInspectorHostColors")) || {};
|
|
|
|
for (const grid of grids) {
|
|
if (grid.nodeFront !== node) {
|
|
continue;
|
|
}
|
|
|
|
if (!customGridColors[hostname]) {
|
|
customGridColors[hostname] = [];
|
|
}
|
|
// Update the custom color for the grid in this position.
|
|
customGridColors[hostname][grid.id] = color;
|
|
await asyncStorage.setItem("gridInspectorHostColors", customGridColors);
|
|
|
|
if (!this.isPanelVisible()) {
|
|
// This call might fail if called asynchrously after the toolbox is finished
|
|
// closing.
|
|
return;
|
|
}
|
|
|
|
// If the grid for which the color was updated currently has a highlighter, update
|
|
// the color.
|
|
if (this.highlighters.gridHighlighters.has(node)) {
|
|
this.highlighters.showGridHighlighter(node);
|
|
continue;
|
|
}
|
|
|
|
// If the node is not explicitly highlighted, but is a parent grid which has an
|
|
// highlighted subgrid, we also want to update the color.
|
|
const subGrid = grids.find(({ id }) => grid.subgrids.includes(id));
|
|
if (subGrid?.highlighted) {
|
|
this.highlighters.showParentGridHighlighter(node);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handler for the inspector sidebar "select" event. Starts tracking reflows
|
|
* if the layout panel is visible. Otherwise, stop tracking reflows.
|
|
* Finally, refresh the layout view if it is visible.
|
|
*/
|
|
onSidebarSelect() {
|
|
if (!this.isPanelVisible()) {
|
|
this.inspector.off("reflow", this.onReflow);
|
|
return;
|
|
}
|
|
|
|
// The panel shows grids from all debugged document, so we need to listen for the
|
|
// `reflow` event (and not `reflow-in-selected-target`).
|
|
this.inspector.on("reflow", this.onReflow);
|
|
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.
|
|
*
|
|
* @param {NodeFront} node
|
|
* The NodeFront of the grid container element for which the grid
|
|
* highlighter is toggled on/off for.
|
|
*/
|
|
onToggleGridHighlighter(node) {
|
|
const { grids } = this.store.getState();
|
|
const grid = grids.find(g => g.nodeFront === node);
|
|
this.store.dispatch(updateGridHighlighted(node, !grid.highlighted));
|
|
this.highlighters.toggleGridHighlighter(node, "grid");
|
|
}
|
|
|
|
/**
|
|
* Handler for a change in the show grid areas checkbox in the GridDisplaySettings
|
|
* component. Toggles on/off the option to show the grid areas in the grid highlighter.
|
|
* Refreshes the shown grid highlighter for the grids currently highlighted.
|
|
*
|
|
* @param {Boolean} enabled
|
|
* Whether or not the grid highlighter should show the grid areas.
|
|
*/
|
|
onToggleShowGridAreas(enabled) {
|
|
this.store.dispatch(updateShowGridAreas(enabled));
|
|
Services.prefs.setBoolPref(SHOW_GRID_AREAS, enabled);
|
|
|
|
const { grids } = this.store.getState();
|
|
|
|
for (const grid of grids) {
|
|
if (grid.highlighted) {
|
|
this.highlighters.showGridHighlighter(grid.nodeFront);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handler for a change in the show grid line numbers checkbox in the
|
|
* GridDisplaySettings component. Toggles on/off the option to show the grid line
|
|
* numbers in the grid highlighter. Refreshes the shown grid highlighter for the
|
|
* grids currently highlighted.
|
|
*
|
|
* @param {Boolean} enabled
|
|
* Whether or not the grid highlighter should show the grid line numbers.
|
|
*/
|
|
onToggleShowGridLineNumbers(enabled) {
|
|
this.store.dispatch(updateShowGridLineNumbers(enabled));
|
|
Services.prefs.setBoolPref(SHOW_GRID_LINE_NUMBERS, enabled);
|
|
|
|
const { grids } = this.store.getState();
|
|
|
|
for (const grid of grids) {
|
|
if (grid.highlighted) {
|
|
this.highlighters.showGridHighlighter(grid.nodeFront);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handler for a change in the extend grid lines infinitely checkbox in the
|
|
* GridDisplaySettings component. Toggles on/off the option to extend the grid
|
|
* lines infinitely in the grid highlighter. Refreshes the shown grid highlighter
|
|
* for grids currently highlighted.
|
|
*
|
|
* @param {Boolean} enabled
|
|
* Whether or not the grid highlighter should extend grid lines infinitely.
|
|
*/
|
|
onToggleShowInfiniteLines(enabled) {
|
|
this.store.dispatch(updateShowInfiniteLines(enabled));
|
|
Services.prefs.setBoolPref(SHOW_INFINITE_LINES_PREF, enabled);
|
|
|
|
const { grids } = this.store.getState();
|
|
|
|
for (const grid of grids) {
|
|
if (grid.highlighted) {
|
|
this.highlighters.showGridHighlighter(grid.nodeFront);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Some grid-inspector methods are highly asynchronous and might still run
|
|
* after the inspector was destroyed. Swallow errors if the grid inspector is
|
|
* already destroyed, throw otherwise.
|
|
*
|
|
* @param {Error} error
|
|
* The original error object.
|
|
* @param {String} message
|
|
* The message to log in case the inspector is already destroyed and
|
|
* the error is swallowed.
|
|
*/
|
|
_throwUnlessDestroyed(error, message) {
|
|
if (!this.inspector) {
|
|
console.warn(message);
|
|
} else {
|
|
// If the grid inspector was not destroyed, this is an unexpected error.
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set z-index of each grids so that nested subgrids are always above their parent grid
|
|
* container.
|
|
*
|
|
* @param {Array} grids
|
|
* A list of grid data.
|
|
* @param {Object} parent
|
|
* A grid data of parent.
|
|
* @param {Number} zIndex
|
|
* z-index for the parent.
|
|
*/
|
|
_updateZOrder(grids, parent, zIndex = 0) {
|
|
parent.zIndex = zIndex;
|
|
|
|
for (const childIndex of parent.subgrids) {
|
|
// Recurse into children grids.
|
|
this._updateZOrder(grids, grids[childIndex], zIndex + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = GridInspector;
|