summaryrefslogtreecommitdiffstats
path: root/dom/base/test/browser_promiseDocumentFlushed.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/test/browser_promiseDocumentFlushed.js')
-rw-r--r--dom/base/test/browser_promiseDocumentFlushed.js292
1 files changed, 292 insertions, 0 deletions
diff --git a/dom/base/test/browser_promiseDocumentFlushed.js b/dom/base/test/browser_promiseDocumentFlushed.js
new file mode 100644
index 0000000000..1df8f7af55
--- /dev/null
+++ b/dom/base/test/browser_promiseDocumentFlushed.js
@@ -0,0 +1,292 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Dirties style and layout on the current browser window.
+ *
+ * @param {Number} Optional factor by which to modify the DOM. Useful for
+ * when multiple calls to dirtyTheDOM may occur, and you need them
+ * to dirty the DOM differently from one another. If you only need
+ * to dirty the DOM once, this can be omitted.
+ */
+function dirtyStyleAndLayout(factor = 1) {
+ gNavToolbox.style.padding = factor + "px";
+}
+
+/**
+ * Dirties style of the current browser window, but NOT layout.
+ */
+function dirtyStyle() {
+ gNavToolbox.style.color = "red";
+}
+
+const gWindowUtils = window.windowUtils;
+
+/**
+ * Asserts that no style or layout flushes are required by the
+ * current window.
+ */
+function assertNoFlushesRequired() {
+ Assert.ok(
+ !gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_STYLE),
+ "No style flushes are required."
+ );
+ Assert.ok(
+ !gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_LAYOUT),
+ "No layout flushes are required."
+ );
+}
+
+/**
+ * Asserts that the DOM has been dirtied, and so style and layout flushes
+ * are required.
+ */
+function assertFlushesRequired() {
+ Assert.ok(
+ gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_STYLE),
+ "Style flush required."
+ );
+ Assert.ok(
+ gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_LAYOUT),
+ "Layout flush required."
+ );
+}
+
+/**
+ * Removes style changes from dirtyTheDOM() from the browser window,
+ * and resolves once the refresh driver ticks.
+ */
+async function cleanTheDOM() {
+ gNavToolbox.style.padding = "";
+ gNavToolbox.style.color = "";
+ await window.promiseDocumentFlushed(() => {});
+}
+
+add_setup(async function () {
+ registerCleanupFunction(cleanTheDOM);
+});
+
+/**
+ * Tests that if the DOM is dirty, that promiseDocumentFlushed will
+ * resolve once layout and style have been flushed.
+ */
+add_task(async function test_basic() {
+ info("Dirtying style / layout");
+ dirtyStyleAndLayout();
+ assertFlushesRequired();
+
+ info("Waiting");
+ await window.promiseDocumentFlushed(() => {});
+ assertNoFlushesRequired();
+
+ info("Dirtying style");
+ dirtyStyle();
+ assertFlushesRequired();
+
+ info("Waiting");
+ await window.promiseDocumentFlushed(() => {});
+ assertNoFlushesRequired();
+
+ // The DOM should be clean already, but we'll do this anyway to isolate
+ // failures from other tests.
+ info("Cleaning up");
+ await cleanTheDOM();
+});
+
+/**
+ * Test that values returned by the callback passed to promiseDocumentFlushed
+ * get passed down through the Promise resolution.
+ */
+add_task(async function test_can_get_results_from_callback() {
+ const NEW_PADDING = "2px";
+
+ gNavToolbox.style.padding = NEW_PADDING;
+
+ assertFlushesRequired();
+
+ let paddings = await window.promiseDocumentFlushed(() => {
+ let style = window.getComputedStyle(gNavToolbox);
+ return {
+ left: style.paddingLeft,
+ right: style.paddingRight,
+ top: style.paddingTop,
+ bottom: style.paddingBottom,
+ };
+ });
+
+ for (let prop in paddings) {
+ Assert.equal(paddings[prop], NEW_PADDING, "Got expected padding");
+ }
+
+ await cleanTheDOM();
+
+ gNavToolbox.style.padding = NEW_PADDING;
+
+ assertFlushesRequired();
+
+ let rect = await window.promiseDocumentFlushed(() => {
+ let observer = {
+ reflow() {
+ Assert.ok(false, "A reflow should not have occurred.");
+ },
+ reflowInterruptible() {},
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIReflowObserver",
+ "nsISupportsWeakReference",
+ ]),
+ };
+
+ let docShell = window.docShell;
+ docShell.addWeakReflowObserver(observer);
+
+ let toolboxRect = gNavToolbox.getBoundingClientRect();
+
+ docShell.removeWeakReflowObserver(observer);
+ return toolboxRect;
+ });
+
+ // The actual values of this rect aren't super important for
+ // the purposes of this test - we just want to know that a valid
+ // rect was returned, so checking for properties being greater than
+ // 0 is sufficient.
+ for (let property of ["width", "height"]) {
+ Assert.ok(
+ rect[property] > 0,
+ `Rect property ${property} > 0 (${rect[property]})`
+ );
+ }
+
+ await cleanTheDOM();
+});
+
+/**
+ * Test that if promiseDocumentFlushed is requested on a window
+ * that closes before it gets a chance to do a refresh driver
+ * tick, the promiseDocumentFlushed Promise is still resolved, and
+ * the callback is still called.
+ */
+add_task(async function test_resolved_in_window_close() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+
+ await win.promiseDocumentFlushed(() => {});
+
+ // Use advanceTimeAndRefresh to pause paints in the window:
+ let utils = win.windowUtils;
+ utils.advanceTimeAndRefresh(0);
+
+ win.gNavToolbox.style.padding = "5px";
+
+ const EXPECTED = 1234;
+ let promise = win.promiseDocumentFlushed(() => {
+ // Despite the window not painting before closing, this
+ // callback should be fired when the window gets torn
+ // down and should stil be able to return a result.
+ return EXPECTED;
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ Assert.equal(await promise, EXPECTED);
+});
+
+/**
+ * Test that re-entering promiseDocumentFlushed is not possible
+ * from within a promiseDocumentFlushed callback. Doing so will
+ * result in the outer Promise rejecting with InvalidStateError.
+ */
+add_task(async function test_reentrancy() {
+ dirtyStyleAndLayout();
+ assertFlushesRequired();
+
+ let promise = window.promiseDocumentFlushed(() => {
+ return window.promiseDocumentFlushed(() => {
+ Assert.ok(false, "Should never run this.");
+ });
+ });
+
+ await Assert.rejects(promise, ex => ex.name == "InvalidStateError");
+});
+
+/**
+ * Tests the expected execution order of a series of promiseDocumentFlushed
+ * calls, their callbacks, and the resolutions of their Promises.
+ *
+ * When multiple promiseDocumentFlushed callbacks are queued, the callbacks
+ * should always been run first before any of the Promises are resolved.
+ *
+ * The callbacks should run in the order that they were queued in via
+ * promiseDocumentFlushed. The Promise resolutions should similarly run
+ * in the order that promiseDocumentFlushed was called in.
+ */
+add_task(async function test_execution_order() {
+ let result = [];
+
+ dirtyStyleAndLayout(1);
+ let promise1 = window
+ .promiseDocumentFlushed(() => {
+ result.push(0);
+ })
+ .then(() => {
+ result.push(2);
+ });
+
+ let promise2 = window
+ .promiseDocumentFlushed(() => {
+ result.push(1);
+ })
+ .then(() => {
+ result.push(3);
+ });
+
+ await Promise.all([promise1, promise2]);
+
+ Assert.equal(result.length, 4, "Should have run all callbacks and Promises.");
+
+ let promise3 = window
+ .promiseDocumentFlushed(() => {
+ result.push(4);
+ })
+ .then(() => {
+ result.push(6);
+ });
+
+ let promise4 = window
+ .promiseDocumentFlushed(() => {
+ result.push(5);
+ })
+ .then(() => {
+ result.push(7);
+ });
+
+ await Promise.all([promise3, promise4]);
+
+ Assert.equal(result.length, 8, "Should have run all callbacks and Promises.");
+
+ for (let i = 0; i < result.length; ++i) {
+ Assert.equal(
+ result[i],
+ i,
+ "Callbacks and Promises should have run in the expected order."
+ );
+ }
+
+ await cleanTheDOM();
+});
+
+/**
+ * Tests that modifying the DOM within a promiseDocumentFlushed callback
+ * will result in the Promise being rejected.
+ */
+add_task(async function test_reject_on_modification() {
+ dirtyStyleAndLayout(1);
+ assertFlushesRequired();
+
+ let promise = window.promiseDocumentFlushed(() => {
+ dirtyStyleAndLayout(2);
+ });
+
+ await Assert.rejects(promise, /NoModificationAllowedError/);
+
+ await cleanTheDOM();
+});