Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* Any copyright is dedicated to the Public Domain.
"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();
});