292 lines
7.5 KiB
JavaScript
292 lines
7.5 KiB
JavaScript
/* 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();
|
|
});
|