Bug 1434376 - Add basic tests for window.promiseDocumentFlushed. r=Paolo
☠☠ backed out by 3b82dc57cc73 ☠ ☠
authorMike Conley <mconley@mozilla.com>
Sun, 11 Feb 2018 20:13:53 -0500
changeset 405068 1b474fb6d7981de578cb1338c636d897a8b5a28c
parent 405067 4e6a21e50be0d839a2262dfc212b9910cfc1c3ab
child 405069 2ed5aefc5bc26b72eef307bfe489c57cb7b0d19c
push id60107
push usermconley@mozilla.com
push dateFri, 23 Feb 2018 20:43:35 +0000
treeherderautoland@2ed5aefc5bc2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersPaolo
bugs1434376
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1434376 - Add basic tests for window.promiseDocumentFlushed. r=Paolo MozReview-Commit-ID: KmyqaupJRtw
dom/base/test/browser.ini
dom/base/test/browser_promiseDocumentFlushed.js
--- a/dom/base/test/browser.ini
+++ b/dom/base/test/browser.ini
@@ -43,14 +43,15 @@ tags = mcb
 skip-if = !e10s # this only makes sense with e10s-multi
 [browser_messagemanager_loadprocessscript.js]
 [browser_aboutnewtab_process_selection.js]
 skip-if = !e10s # this only makes sense with e10s-multi
 [browser_messagemanager_targetframeloader.js]
 [browser_messagemanager_unload.js]
 [browser_pagehide_on_tab_close.js]
 skip-if = e10s # this tests non-e10s behavior. it's not expected to work in e10s.
+[browser_promiseDocumentFlushed.js]
 [browser_state_notifications.js]
 skip-if = true # Bug 1271028
 [browser_use_counters.js]
 [browser_timeout_throttling_with_audio_playback.js]
 [browser_bug1303838.js]
 [browser_inputStream_structuredClone.js]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/browser_promiseDocumentFlushed.js
@@ -0,0 +1,246 @@
+/* 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.QueryInterface(Ci.nsIInterfaceRequestor)
+                           .getInterface(Ci.nsIDOMWindowUtils);
+
+/**
+ * Asserts that no style or layout flushes are required by the
+ * current window.
+ */
+function assertNoFlushesRequired() {
+  Assert.ok(!gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_STYLE),
+            "No 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_LAYOUT),
+            "Style and layout flushes are 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_task(async function setup() {
+  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() {
+  dirtyStyleAndLayout();
+  assertFlushesRequired();
+
+  await window.promiseDocumentFlushed(() => {});
+  assertNoFlushesRequired();
+
+  dirtyStyle();
+  assertFlushesRequired();
+
+  await window.promiseDocumentFlushed(() => {});
+  assertNoFlushesRequired();
+
+  // The DOM should be clean already, but we'll do this anyway to isolate
+  // failures from other tests.
+  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: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
+                                             Ci.nsISupportsWeakReference])
+    };
+
+    let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                         .getInterface(Ci.nsIWebNavigation)
+                         .QueryInterface(Ci.nsIDocShell);
+    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(() => {});
+
+  let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.nsIDocShell);
+  docShell.contentViewer.pausePainting();
+
+  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 NS_ERROR_FAILURE.
+ */
+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.result == Cr.NS_ERROR_FAILURE);
+});
+
+/**
+ * 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.");
+  }
+});