From 93c1ada2a46c8d2ec4821c4ee43831392033fb2a Mon Sep 17 00:00:00 2001 From: Mike Conley Date: Sun, 11 Feb 2018 20:13:53 -0500 Subject: [PATCH] Bug 1434376 - Add basic tests for window.promiseDocumentFlushed. r=Paolo MozReview-Commit-ID: KmyqaupJRtw --HG-- extra : rebase_source : f7bc5281f194f28a7c0d36f6d6dfd87428c60cf1 --- dom/base/test/browser.ini | 1 + .../test/browser_promiseDocumentFlushed.js | 246 ++++++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 dom/base/test/browser_promiseDocumentFlushed.js diff --git a/dom/base/test/browser.ini b/dom/base/test/browser.ini index 39246fe015c6..4bfa3348229c 100644 --- a/dom/base/test/browser.ini +++ b/dom/base/test/browser.ini @@ -48,6 +48,7 @@ skip-if = !e10s # this only makes sense with e10s-multi [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] diff --git a/dom/base/test/browser_promiseDocumentFlushed.js b/dom/base/test/browser_promiseDocumentFlushed.js new file mode 100644 index 000000000000..a1c0ea362b19 --- /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."); + } +});