diff --git a/devtools/client/inspector/grids/grid-inspector.js b/devtools/client/inspector/grids/grid-inspector.js
index 8efa464c1695..9117b4ce04a0 100644
--- a/devtools/client/inspector/grids/grid-inspector.js
+++ b/devtools/client/inspector/grids/grid-inspector.js
@@ -8,6 +8,8 @@ const Services = require("Services");
const { Task } = require("devtools/shared/task");
const SwatchColorPickerTooltip = require("devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip");
+const { throttle } = require("devtools/client/inspector/shared/utils");
+const { compareFragmentsGeometry } = require("devtools/client/inspector/grids/utils/utils");
const {
updateGridColor,
@@ -51,9 +53,9 @@ function GridInspector(inspector, window) {
this.getSwatchColorPickerTooltip = this.getSwatchColorPickerTooltip.bind(this);
this.updateGridPanel = this.updateGridPanel.bind(this);
- this.onGridLayoutChange = this.onGridLayoutChange.bind(this);
+ this.onNavigate = this.onNavigate.bind(this);
this.onHighlighterChange = this.onHighlighterChange.bind(this);
- this.onReflow = this.onReflow.bind(this);
+ this.onReflow = throttle(this.onReflow, 500, this);
this.onSetGridOverlayColor = this.onSetGridOverlayColor.bind(this);
this.onShowGridAreaHighlight = this.onShowGridAreaHighlight.bind(this);
this.onShowGridCellHighlight = this.onShowGridCellHighlight.bind(this);
@@ -94,7 +96,7 @@ GridInspector.prototype = {
this.highlighters.on("grid-highlighter-hidden", this.onHighlighterChange);
this.highlighters.on("grid-highlighter-shown", this.onHighlighterChange);
this.inspector.sidebar.on("select", this.onSidebarSelect);
- this.inspector.target.on("navigate", this.onGridLayoutChange);
+ this.inspector.on("new-root", this.onNavigate);
this.onSidebarSelect();
}),
@@ -107,7 +109,7 @@ GridInspector.prototype = {
this.highlighters.off("grid-highlighter-hidden", this.onHighlighterChange);
this.highlighters.off("grid-highlighter-shown", this.onHighlighterChange);
this.inspector.sidebar.off("select", this.onSidebarSelect);
- this.inspector.target.off("navigate", this.onGridLayoutChange);
+ this.inspector.off("new-root", this.onNavigate);
this.inspector.reflowTracker.untrackReflows(this, this.onReflow);
@@ -211,7 +213,7 @@ GridInspector.prototype = {
* Returns true if the layout panel is visible, and false otherwise.
*/
isPanelVisible() {
- return this.inspector.toolbox && this.inspector.sidebar &&
+ return this.inspector && this.inspector.toolbox && this.inspector.sidebar &&
this.inspector.toolbox.currentToolId === "inspector" &&
this.inspector.sidebar.getCurrentTabID() === "layoutview";
},
@@ -278,13 +280,19 @@ GridInspector.prototype = {
for (let i = 0; i < gridFronts.length; i++) {
let grid = gridFronts[i];
- let nodeFront;
- try {
- nodeFront = yield this.walker.getNodeFromActor(grid.actorID, ["containerEl"]);
- } catch (e) {
- // This call might fail if called asynchrously after the toolbox is finished
- // closing.
- return;
+ 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 = yield this.walker.getNodeFromActor(grid.actorID, ["containerEl"]);
+ } catch (e) {
+ // This call might fail if called asynchrously after the toolbox is finished
+ // closing.
+ return;
+ }
}
let fallbackColor = GRID_COLORS[i % GRID_COLORS.length];
@@ -303,9 +311,10 @@ GridInspector.prototype = {
}),
/**
- * Handler for "navigate" event fired by the tab target. Updates grid panel contents.
+ * Handler for "new-root" event fired by the inspector, which indicates a page
+ * navigation. Updates grid panel contents.
*/
- onGridLayoutChange() {
+ onNavigate() {
if (this.isPanelVisible()) {
this.updateGridPanel();
}
@@ -345,15 +354,81 @@ GridInspector.prototype = {
},
/**
- * Handler for the "reflow" event fired by the inspector's reflow tracker. On reflows,
- * update the grid panel content.
+ * Given a list of new grid fronts, and if we have a currently highlighted grid, check
+ * if its fragments have changed.
+ *
+ * @param {Array} newGridFronts
+ * A list of GridFront objects.
+ * @return {Boolean}
*/
- onReflow() {
- if (this.isPanelVisible()) {
- this.updateGridPanel();
+ haveCurrentFragmentsChanged(newGridFronts) {
+ const currentNode = this.highlighters.gridHighlighterShown;
+ if (!currentNode) {
+ return false;
}
+
+ const newGridFront = newGridFronts.find(g => g.containerNodeFront === currentNode);
+ if (!newGridFront) {
+ return false;
+ }
+
+ const { grids } = this.store.getState();
+ const oldFragments = grids.find(g => g.nodeFront === currentNode).gridFragments;
+ const newFragments = newGridFront.gridFragments;
+
+ return !compareFragmentsGeometry(oldFragments, newFragments);
},
+ /**
+ * Handler for the "reflow" event fired by the inspector's reflow tracker. 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.
+ */
+ onReflow: Task.async(function* () {
+ if (!this.isPanelVisible()) {
+ return;
+ }
+
+ // The list of grids currently displayed.
+ const { grids } = this.store.getState();
+
+ // The new list of grids from the server.
+ let newGridFronts;
+ try {
+ newGridFronts = yield this.layoutInspector.getAllGrids(this.walker.rootNode);
+ } catch (e) {
+ // This call might fail if called asynchrously after the toolbox is finished
+ // closing.
+ return;
+ }
+
+ // Compare the list of DOM nodes which define these grids.
+ const oldNodeFronts = grids.map(grid => grid.nodeFront.actorID);
+ 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.gridHighlighterShown ||
+ (this.highlighters.gridHighlighterShown &&
+ !this.haveCurrentFragmentsChanged(newGridFronts))) {
+ return;
+ }
+ }
+
+ // Either the list of containers or the current fragments have changed, do update.
+ this.updateGridPanel(newGridFronts);
+ }),
+
/**
* Handler for a change in the grid overlay color picker for a grid container.
*
diff --git a/devtools/client/inspector/grids/moz.build b/devtools/client/inspector/grids/moz.build
index 74ce577196a7..55d2d8c97f33 100644
--- a/devtools/client/inspector/grids/moz.build
+++ b/devtools/client/inspector/grids/moz.build
@@ -17,3 +17,4 @@ DevToolsModules(
)
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
diff --git a/devtools/client/inspector/grids/test/browser.ini b/devtools/client/inspector/grids/test/browser.ini
index fc23ef1e5ca7..3abe7983ece5 100644
--- a/devtools/client/inspector/grids/test/browser.ini
+++ b/devtools/client/inspector/grids/test/browser.ini
@@ -28,5 +28,6 @@ support-files =
[browser_grids_grid-outline-highlight-area.js]
[browser_grids_grid-outline-highlight-cell.js]
[browser_grids_grid-outline-selected-grid.js]
+[browser_grids_grid-outline-updates-on-grid-change.js]
[browser_grids_highlighter-setting-rules-grid-toggle.js]
[browser_grids_number-of-css-grids-telemetry.js]
diff --git a/devtools/client/inspector/grids/test/browser_grids_grid-outline-updates-on-grid-change.js b/devtools/client/inspector/grids/test/browser_grids_grid-outline-updates-on-grid-change.js
new file mode 100644
index 000000000000..6dc0896265f4
--- /dev/null
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-outline-updates-on-grid-change.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the grid outline does reflect the grid in the page even after the grid has
+// changed.
+
+const TEST_URI = `
+
+
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+
+ let { inspector, gridInspector, testActor } = yield openLayoutView();
+ let { document: doc } = gridInspector;
+ let { highlighters, store } = inspector;
+
+ info("Clicking on the first checkbox to highlight the grid");
+ let checkbox = doc.querySelector("#grid-list input");
+
+ let onHighlighterShown = highlighters.once("grid-highlighter-shown");
+ let onCheckboxChange = waitUntilState(store, state =>
+ state.grids.length == 1 && state.grids[0].highlighted);
+ let onGridOutlineRendered = waitForDOM(doc, ".grid-outline-cell", 2);
+
+ checkbox.click();
+
+ yield onHighlighterShown;
+ yield onCheckboxChange;
+ let elements = yield onGridOutlineRendered;
+
+ info("Checking the grid outline is shown.");
+ is(elements.length, 2, "Grid outline is shown.");
+
+ info("Changing the grid in the page");
+ let onReflow = new Promise(resolve => {
+ let listener = {
+ callback: () => {
+ inspector.reflowTracker.untrackReflows(listener, listener.callback);
+ resolve();
+ }
+ };
+ inspector.reflowTracker.trackReflows(listener, listener.callback);
+ });
+ let onGridOutlineChanged = waitForDOM(doc, ".grid-outline-cell", 4);
+
+ testActor.eval(`
+ const div = content.document.createElement("div");
+ div.textContent = "item 3";
+ content.document.querySelector(".container").appendChild(div);
+ `);
+
+ yield onReflow;
+ elements = yield onGridOutlineChanged;
+
+ info("Checking the grid outline is correct.");
+ is(elements.length, 4, "Grid outline was changed.");
+});
diff --git a/devtools/client/inspector/grids/test/unit/.eslintrc.js b/devtools/client/inspector/grids/test/unit/.eslintrc.js
new file mode 100644
index 000000000000..54a9a6361b5c
--- /dev/null
+++ b/devtools/client/inspector/grids/test/unit/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools xpcshell eslintrc config.
+ "extends": "../../../../../.eslintrc.xpcshell.js"
+};
diff --git a/devtools/client/inspector/grids/test/unit/head.js b/devtools/client/inspector/grids/test/unit/head.js
new file mode 100644
index 000000000000..eb95bd6b90df
--- /dev/null
+++ b/devtools/client/inspector/grids/test/unit/head.js
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
+const { utils: Cu } = Components;
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
diff --git a/devtools/client/inspector/grids/test/unit/test_compare_fragments_geometry.js b/devtools/client/inspector/grids/test/unit/test_compare_fragments_geometry.js
new file mode 100644
index 000000000000..546eb9a3b3ea
--- /dev/null
+++ b/devtools/client/inspector/grids/test/unit/test_compare_fragments_geometry.js
@@ -0,0 +1,79 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { compareFragmentsGeometry } = require("devtools/client/inspector/grids/utils/utils");
+
+const TESTS = [{
+ desc: "No fragments",
+ grids: [[], []],
+ expected: true
+}, {
+ desc: "Different number of fragments",
+ grids: [
+ [{}, {}, {}],
+ [{}, {}]
+ ],
+ expected: false
+}, {
+ desc: "Different number of columns",
+ grids: [
+ [{cols: {lines: [{}, {}]}, rows: {lines: []}}],
+ [{cols: {lines: [{}]}, rows: {lines: []}}]
+ ],
+ expected: false
+}, {
+ desc: "Different number of rows",
+ grids: [
+ [{cols: {lines: [{}, {}]}, rows: {lines: [{}]}}],
+ [{cols: {lines: [{}, {}]}, rows: {lines: [{}, {}]}}]
+ ],
+ expected: false
+}, {
+ desc: "Different number of rows and columns",
+ grids: [
+ [{cols: {lines: [{}]}, rows: {lines: [{}]}}],
+ [{cols: {lines: [{}, {}]}, rows: {lines: [{}, {}]}}]
+ ],
+ expected: false
+}, {
+ desc: "Different column sizes",
+ grids: [
+ [{cols: {lines: [{start: 0}, {start: 500}]}, rows: {lines: []}}],
+ [{cols: {lines: [{start: 0}, {start: 1000}]}, rows: {lines: []}}]
+ ],
+ expected: false
+}, {
+ desc: "Different row sizes",
+ grids: [
+ [{cols: {lines: [{start: 0}, {start: 500}]}, rows: {lines: [{start: -100}]}}],
+ [{cols: {lines: [{start: 0}, {start: 500}]}, rows: {lines: [{start: 0}]}}]
+ ],
+ expected: false
+}, {
+ desc: "Different row and column sizes",
+ grids: [
+ [{cols: {lines: [{start: 0}, {start: 500}]}, rows: {lines: [{start: -100}]}}],
+ [{cols: {lines: [{start: 0}, {start: 505}]}, rows: {lines: [{start: 0}]}}]
+ ],
+ expected: false
+}, {
+ desc: "Complete structure, same fragments",
+ grids: [
+ [{cols: {lines: [{start: 0}, {start: 100.3}, {start: 200.6}]},
+ rows: {lines: [{start: 0}, {start: 1000}, {start: 2000}]}}],
+ [{cols: {lines: [{start: 0}, {start: 100.3}, {start: 200.6}]},
+ rows: {lines: [{start: 0}, {start: 1000}, {start: 2000}]}}]
+ ],
+ expected: true
+}];
+
+function run_test() {
+ for (let { desc, grids, expected } of TESTS) {
+ if (desc) {
+ do_print(desc);
+ }
+ equal(compareFragmentsGeometry(grids[0], grids[1]), expected);
+ }
+}
diff --git a/devtools/client/inspector/grids/test/unit/xpcshell.ini b/devtools/client/inspector/grids/test/unit/xpcshell.ini
new file mode 100644
index 000000000000..c52a930c717f
--- /dev/null
+++ b/devtools/client/inspector/grids/test/unit/xpcshell.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+tags = devtools
+firefox-appdir = browser
+head = head.js
+
+[test_compare_fragments_geometry.js]
diff --git a/devtools/client/inspector/grids/utils/moz.build b/devtools/client/inspector/grids/utils/moz.build
index e3053b63fab6..f6a6af241ddd 100644
--- a/devtools/client/inspector/grids/utils/moz.build
+++ b/devtools/client/inspector/grids/utils/moz.build
@@ -6,4 +6,5 @@
DevToolsModules(
'l10n.js',
+ 'utils.js',
)
diff --git a/devtools/client/inspector/grids/utils/utils.js b/devtools/client/inspector/grids/utils/utils.js
new file mode 100644
index 000000000000..6a6c341fe5ff
--- /dev/null
+++ b/devtools/client/inspector/grids/utils/utils.js
@@ -0,0 +1,52 @@
+/* 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";
+
+/**
+ * Compares 2 sets of grid fragments to each other and checks if they have the same
+ * general geometry.
+ * This means that things like areas, area names or line names are ignored.
+ * This only checks if the 2 sets of fragments have as many fragments, as many lines, and
+ * that those lines are at the same distance.
+ *
+ * @param {Array} fragments1
+ * A list of gridFragment objects.
+ * @param {Array} fragments2
+ * Another list of gridFragment objects to compare to the first list.
+ * @return {Boolean}
+ * True if the fragments are the same, false otherwise.
+ */
+function compareFragmentsGeometry(fragments1, fragments2) {
+ // Compare the number of fragments.
+ if (fragments1.length !== fragments2.length) {
+ return false;
+ }
+
+ // Compare the number of areas, rows and columns.
+ for (let i = 0; i < fragments1.length; i++) {
+ if (fragments1[i].cols.lines.length !== fragments2[i].cols.lines.length ||
+ fragments1[i].rows.lines.length !== fragments2[i].rows.lines.length) {
+ return false;
+ }
+ }
+
+ // Compare the offset of lines.
+ for (let i = 0; i < fragments1.length; i++) {
+ for (let j = 0; j < fragments1[i].cols.lines.length; j++) {
+ if (fragments1[i].cols.lines[j].start !== fragments2[i].cols.lines[j].start) {
+ return false;
+ }
+ }
+ for (let j = 0; j < fragments1[i].rows.lines.length; j++) {
+ if (fragments1[i].rows.lines[j].start !== fragments2[i].rows.lines[j].start) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+module.exports.compareFragmentsGeometry = compareFragmentsGeometry;
diff --git a/devtools/client/inspector/rules/rules.js b/devtools/client/inspector/rules/rules.js
index bfc063d4d959..9a65157f53b2 100644
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -29,7 +29,7 @@ const {
} = require("devtools/client/inspector/shared/node-types");
const StyleInspectorMenu = require("devtools/client/inspector/shared/style-inspector-menu");
const TooltipsOverlay = require("devtools/client/inspector/shared/tooltips-overlay");
-const {createChild, promiseWarn, throttle} = require("devtools/client/inspector/shared/utils");
+const {createChild, promiseWarn, debounce} = require("devtools/client/inspector/shared/utils");
const EventEmitter = require("devtools/shared/event-emitter");
const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
const clipboardHelper = require("devtools/shared/platform/clipboard");
@@ -107,8 +107,8 @@ function CssRuleView(inspector, document, store, pageStyle) {
this.store = store || {};
this.pageStyle = pageStyle;
- // Allow tests to override throttling behavior, as this can cause intermittents.
- this.throttle = throttle;
+ // Allow tests to override debouncing behavior, as this can cause intermittents.
+ this.debounce = debounce;
this.cssProperties = getCssProperties(inspector.toolbox);
diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_01.js b/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_01.js
index 566bae25925f..e00a11f0a4cd 100644
--- a/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_01.js
@@ -120,8 +120,8 @@ function* testCompletion([key, completion, open, selected],
info("Synthesizing key " + key);
EventUtils.synthesizeKey(key, {}, view.styleWindow);
- // Flush the throttle for the preview text.
- view.throttle.flush();
+ // Flush the debounce for the preview text.
+ view.debounce.flush();
yield onSuggest;
yield onPopupEvent;
diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_02.js b/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_02.js
index fde8f5d1248e..957990a502b4 100644
--- a/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_02.js
@@ -99,8 +99,8 @@ function* testCompletion([key, modifiers, completion, open, selected, change],
EventUtils.synthesizeKey(key, modifiers, view.styleWindow);
- // Flush the throttle for the preview text.
- view.throttle.flush();
+ // Flush the debounce for the preview text.
+ view.debounce.flush();
yield onDone;
yield onPopupEvent;
diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js
index d89e5129d9ea..504b85a958a8 100644
--- a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js
@@ -107,8 +107,8 @@ function* testCompletion([key, modifiers, completion, open, selected, change],
info("Synthesizing key " + key + ", modifiers: " + Object.keys(modifiers));
EventUtils.synthesizeKey(key, modifiers, view.styleWindow);
- // Flush the throttle for the preview text.
- view.throttle.flush();
+ // Flush the debounce for the preview text.
+ view.debounce.flush();
yield onDone;
yield onPopupEvent;
diff --git a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_multiline.js b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_multiline.js
index ec939eafc844..529e19c7a598 100644
--- a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_multiline.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_multiline.js
@@ -99,7 +99,7 @@ add_task(function* () {
let node = editor.popup._list.childNodes[editor.popup.selectedIndex];
EventUtils.synthesizeMouseAtCenter(node, {}, editor.popup._window);
- view.throttle.flush();
+ view.debounce.flush();
yield onSuggest;
yield onRuleviewChanged;
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-commit.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-commit.js
index 8e16601c732b..e9bdfcc8d210 100644
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property-commit.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-commit.js
@@ -72,7 +72,7 @@ function* runTestData(view, {value, commitKey, modifiers, expected}) {
info("Entering test data " + value);
let onRuleViewChanged = view.once("ruleview-changed");
EventUtils.sendString(value, view.styleWindow);
- view.throttle.flush();
+ view.debounce.flush();
yield onRuleViewChanged;
info("Entering the commit key " + commitKey + " " + modifiers);
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-computed.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-computed.js
index ee0a1fa744ca..d9a4a8728782 100644
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property-computed.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-computed.js
@@ -41,10 +41,10 @@ function* editAndCheck(view) {
info("Entering a new value");
EventUtils.sendString(newPaddingValue, view.styleWindow);
- info("Waiting for the throttled previewValue to apply the " +
+ info("Waiting for the debounced previewValue to apply the " +
"changes to document");
- view.throttle.flush();
+ view.debounce.flush();
yield onPropertyChange;
info("Waiting for ruleview-refreshed after previewValue was applied.");
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property-increments.js b/devtools/client/inspector/rules/test/browser_rules_edit-property-increments.js
index ca63cedccfc4..2c88ef02a92c 100644
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property-increments.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-increments.js
@@ -238,7 +238,7 @@ function* runIncrementTest(propertyEditor, view, tests) {
// requests when the test ends).
let onRuleViewChanged = view.once("ruleview-changed");
EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
- view.throttle.flush();
+ view.debounce.flush();
yield onRuleViewChanged;
}
@@ -272,7 +272,7 @@ function* testIncrement(editor, options, view) {
// Only expect a change if the value actually changed!
if (options.start !== options.end) {
- view.throttle.flush();
+ view.debounce.flush();
yield onRuleViewChanged;
}
diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-property_02.js b/devtools/client/inspector/rules/test/browser_rules_edit-property_02.js
index 7e6315236cbf..9e9fbc62ef11 100644
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_02.js
@@ -69,7 +69,7 @@ function* testEditProperty(inspector, ruleView) {
for (let ch of "red;") {
let onPreviewDone = ruleView.once("ruleview-changed");
EventUtils.sendChar(ch, ruleView.styleWindow);
- ruleView.throttle.flush();
+ ruleView.debounce.flush();
yield onPreviewDone;
is(prop.editor.warning.hidden, true,
"warning triangle is hidden or shown as appropriate");
diff --git a/devtools/client/inspector/rules/test/browser_rules_livepreview.js b/devtools/client/inspector/rules/test/browser_rules_livepreview.js
index 1f1302a70b8a..a811cdb204ba 100644
--- a/devtools/client/inspector/rules/test/browser_rules_livepreview.js
+++ b/devtools/client/inspector/rules/test/browser_rules_livepreview.js
@@ -53,7 +53,7 @@ function* testLivePreviewData(data, ruleView, selector) {
info("Entering value in the editor: " + data.value);
let onPreviewDone = ruleView.once("ruleview-changed");
EventUtils.sendString(data.value, ruleView.styleWindow);
- ruleView.throttle.flush();
+ ruleView.debounce.flush();
yield onPreviewDone;
let onValueDone = ruleView.once("ruleview-changed");
diff --git a/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_02.js b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_02.js
index dd1360b96cdb..2534a93977fb 100644
--- a/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_multiple-properties-unfinished_02.js
@@ -15,7 +15,7 @@ add_task(function* () {
// Turn off throttling, which can cause intermittents. Throttling is used by
// the TextPropertyEditor.
- view.throttle = () => {};
+ view.debounce = () => {};
yield selectNode("div", inspector);
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_09.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_09.js
index 620e5d336e81..b52f6f4167c2 100644
--- a/devtools/client/inspector/rules/test/browser_rules_search-filter_09.js
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_09.js
@@ -61,7 +61,7 @@ add_task(function* () {
info("Entering a value and bluring the field to expect a rule change");
onRuleViewChanged = view.once("ruleview-changed");
editor.input.value = "100%";
- view.throttle.flush();
+ view.debounce.flush();
yield onRuleViewChanged;
onRuleViewChanged = view.once("ruleview-changed");
diff --git a/devtools/client/inspector/rules/test/head.js b/devtools/client/inspector/rules/test/head.js
index 0d8f9f88a937..4fdd9ffa41ee 100644
--- a/devtools/client/inspector/rules/test/head.js
+++ b/devtools/client/inspector/rules/test/head.js
@@ -289,7 +289,7 @@ var addProperty = Task.async(function* (view, ruleIndex, name, value,
// triggers a ruleview-changed event (see bug 1209295).
let onPreview = view.once("ruleview-changed");
editor.input.value = value;
- view.throttle.flush();
+ view.debounce.flush();
yield onPreview;
let onValueAdded = view.once("ruleview-changed");
@@ -328,7 +328,7 @@ var setProperty = Task.async(function* (view, textProp, value,
} else {
EventUtils.sendString(value, view.styleWindow);
}
- view.throttle.flush();
+ view.debounce.flush();
yield onPreview;
let onValueDone = view.once("ruleview-changed");
diff --git a/devtools/client/inspector/rules/views/text-property-editor.js b/devtools/client/inspector/rules/views/text-property-editor.js
index 1f1406173622..0c30b8d13add 100644
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -75,7 +75,7 @@ function TextPropertyEditor(ruleEditor, property) {
this._onSwatchCommit = this._onSwatchCommit.bind(this);
this._onSwatchPreview = this._onSwatchPreview.bind(this);
this._onSwatchRevert = this._onSwatchRevert.bind(this);
- this._onValidate = this.ruleView.throttle(this._previewValue, 10, this);
+ this._onValidate = this.ruleView.debounce(this._previewValue, 10, this);
this.update = this.update.bind(this);
this.updatePropertyState = this.updatePropertyState.bind(this);
@@ -899,7 +899,7 @@ TextPropertyEditor.prototype = {
* True if we're reverting the previously previewed value
*/
_previewValue: function (value, reverting = false) {
- // Since function call is throttled, we need to make sure we are still
+ // Since function call is debounced, we need to make sure we are still
// editing, and any selector modifications have been completed
if (!reverting && (!this.editing || this.ruleEditor.isEditing)) {
return;
diff --git a/devtools/client/inspector/shared/utils.js b/devtools/client/inspector/shared/utils.js
index 60dda914c742..465f1669fd03 100644
--- a/devtools/client/inspector/shared/utils.js
+++ b/devtools/client/inspector/shared/utils.js
@@ -100,17 +100,18 @@ function advanceValidate(keyCode, value, insertionPoint) {
exports.advanceValidate = advanceValidate;
/**
- * Create a throttling function wrapper to regulate its frequency.
+ * Create a debouncing function wrapper to only call the target function after a certain
+ * amount of time has passed without it being called.
*
* @param {Function} func
- * The function to throttle
+ * The function to debounce
* @param {number} wait
- * The throttling period
+ * The wait period
* @param {Object} scope
* The scope to use for func
- * @return {Function} The throttled function
+ * @return {Function} The debounced function
*/
-function throttle(func, wait, scope) {
+function debounce(func, wait, scope) {
let timer = null;
return function () {
@@ -126,6 +127,55 @@ function throttle(func, wait, scope) {
};
}
+exports.debounce = debounce;
+
+/**
+ * From underscore's `_.throttle`
+ * http://underscorejs.org
+ * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Underscore may be freely distributed under the MIT license.
+ *
+ * Returns a function, that, when invoked, will only be triggered at most once during a
+ * given window of time. The throttled function will run as much as it can, without ever
+ * going more than once per wait duration.
+ *
+ * @param {Function} func
+ * The function to throttle
+ * @param {number} wait
+ * The wait period
+ * @param {Object} scope
+ * The scope to use for func
+ * @return {Function} The throttled function
+ */
+function throttle(func, wait, scope) {
+ let args, result;
+ let timeout = null;
+ let previous = 0;
+
+ let later = function () {
+ previous = Date.now();
+ timeout = null;
+ result = func.apply(scope, args);
+ args = null;
+ };
+
+ return function () {
+ let now = Date.now();
+ let remaining = wait - (now - previous);
+ args = arguments;
+ if (remaining <= 0) {
+ clearTimeout(timeout);
+ timeout = null;
+ previous = now;
+ result = func.apply(scope, args);
+ args = null;
+ } else if (!timeout) {
+ timeout = setTimeout(later, remaining);
+ }
+ return result;
+ };
+}
+
exports.throttle = throttle;
/**
diff --git a/devtools/client/inspector/test/shared-head.js b/devtools/client/inspector/test/shared-head.js
index 6404aa38c7e8..bbab92965f58 100644
--- a/devtools/client/inspector/test/shared-head.js
+++ b/devtools/client/inspector/test/shared-head.js
@@ -78,9 +78,9 @@ var openInspectorSidebarTab = Task.async(function* (id) {
*/
function openRuleView() {
return openInspectorSidebarTab("ruleview").then(data => {
- // Replace the view to use a custom throttle function that can be triggered manually
+ // Replace the view to use a custom debounce function that can be triggered manually
// through an additional ".flush()" property.
- data.inspector.getPanel("ruleview").view.throttle = manualThrottle();
+ data.inspector.getPanel("ruleview").view.debounce = manualDebounce();
return {
toolbox: data.toolbox,
@@ -199,16 +199,16 @@ var selectNode = Task.async(function* (selector, inspector, reason = "test") {
/**
* Create a throttling function that can be manually "flushed". This is to replace the
- * use of the `throttle` function from `devtools/client/inspector/shared/utils.js`, which
+ * use of the `debounce` function from `devtools/client/inspector/shared/utils.js`, which
* has a setTimeout that can cause intermittents.
- * @return {Function} This function has the same function signature as throttle, but
+ * @return {Function} This function has the same function signature as debounce, but
* the property `.flush()` has been added for flushing out any
- * throttled calls.
+ * debounced calls.
*/
-function manualThrottle() {
+function manualDebounce() {
let calls = [];
- function throttle(func, wait, scope) {
+ function debounce(func, wait, scope) {
return function () {
let existingCall = calls.find(call => call.func === func);
if (existingCall) {
@@ -219,12 +219,12 @@ function manualThrottle() {
};
}
- throttle.flush = function () {
+ debounce.flush = function () {
calls.forEach(({func, scope, args}) => func.apply(scope, args));
calls = [];
};
- return throttle;
+ return debounce;
}
/**
diff --git a/devtools/server/actors/layout.js b/devtools/server/actors/layout.js
index 1650eef0bc30..8de70f81f026 100644
--- a/devtools/server/actors/layout.js
+++ b/devtools/server/actors/layout.js
@@ -58,6 +58,13 @@ var GridActor = ActorClassWithSpec(gridSpec, {
gridFragments: this.gridFragments
};
+ // If the WalkerActor already knows the container element, then also return its
+ // ActorID so we avoid the client from doing another round trip to get it in many
+ // cases.
+ if (this.walker.hasNode(this.containerEl)) {
+ form.containerNodeActorID = this.walker.getNode(this.containerEl).actorID;
+ }
+
return form;
},
});
diff --git a/devtools/shared/fronts/layout.js b/devtools/shared/fronts/layout.js
index 5a1a6185d369..e55098356b47 100644
--- a/devtools/shared/fronts/layout.js
+++ b/devtools/shared/fronts/layout.js
@@ -16,6 +16,18 @@ const GridFront = FrontClassWithSpec(gridSpec, {
this._form = form;
},
+ /**
+ * In some cases, the GridActor already knows the NodeActor ID of the node where the
+ * grid is located. In such cases, this getter returns the NodeFront for it.
+ */
+ get containerNodeFront() {
+ if (!this._form.containerNodeActorID) {
+ return null;
+ }
+
+ return this.conn.getActor(this._form.containerNodeActorID);
+ },
+
/**
* Getter for the grid fragments data.
*/