browser_promiseDocumentFlushed.js (7733B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 /** 7 * Dirties style and layout on the current browser window. 8 * 9 * @param {number} Optional factor by which to modify the DOM. Useful for 10 * when multiple calls to dirtyTheDOM may occur, and you need them 11 * to dirty the DOM differently from one another. If you only need 12 * to dirty the DOM once, this can be omitted. 13 */ 14 function dirtyStyleAndLayout(factor = 1) { 15 gNavToolbox.style.padding = factor + "px"; 16 } 17 18 /** 19 * Dirties style of the current browser window, but NOT layout. 20 */ 21 function dirtyStyle() { 22 gNavToolbox.style.color = "red"; 23 } 24 25 const gWindowUtils = window.windowUtils; 26 27 /** 28 * Asserts that no style or layout flushes are required by the 29 * current window. 30 */ 31 function assertNoFlushesRequired() { 32 Assert.ok( 33 !gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_STYLE), 34 "No style flushes are required." 35 ); 36 Assert.ok( 37 !gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_LAYOUT), 38 "No layout flushes are required." 39 ); 40 } 41 42 /** 43 * Asserts that the DOM has been dirtied, and so style and layout flushes 44 * are required. 45 */ 46 function assertFlushesRequired() { 47 Assert.ok( 48 gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_STYLE), 49 "Style flush required." 50 ); 51 Assert.ok( 52 gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_LAYOUT), 53 "Layout flush required." 54 ); 55 } 56 57 /** 58 * Removes style changes from dirtyTheDOM() from the browser window, 59 * and resolves once the refresh driver ticks. 60 */ 61 async function cleanTheDOM() { 62 gNavToolbox.style.padding = ""; 63 gNavToolbox.style.color = ""; 64 await window.promiseDocumentFlushed(() => {}); 65 } 66 67 add_setup(async function () { 68 registerCleanupFunction(cleanTheDOM); 69 }); 70 71 /** 72 * Tests that if the DOM is dirty, that promiseDocumentFlushed will 73 * resolve once layout and style have been flushed. 74 */ 75 add_task(async function test_basic() { 76 info("Dirtying style / layout"); 77 dirtyStyleAndLayout(); 78 assertFlushesRequired(); 79 80 info("Waiting"); 81 await window.promiseDocumentFlushed(() => {}); 82 assertNoFlushesRequired(); 83 84 info("Dirtying style"); 85 dirtyStyle(); 86 assertFlushesRequired(); 87 88 info("Waiting"); 89 await window.promiseDocumentFlushed(() => {}); 90 assertNoFlushesRequired(); 91 92 // The DOM should be clean already, but we'll do this anyway to isolate 93 // failures from other tests. 94 info("Cleaning up"); 95 await cleanTheDOM(); 96 }); 97 98 /** 99 * Test that values returned by the callback passed to promiseDocumentFlushed 100 * get passed down through the Promise resolution. 101 */ 102 add_task(async function test_can_get_results_from_callback() { 103 const NEW_PADDING = "2px"; 104 105 gNavToolbox.style.padding = NEW_PADDING; 106 107 assertFlushesRequired(); 108 109 let paddings = await window.promiseDocumentFlushed(() => { 110 let style = window.getComputedStyle(gNavToolbox); 111 return { 112 left: style.paddingLeft, 113 right: style.paddingRight, 114 top: style.paddingTop, 115 bottom: style.paddingBottom, 116 }; 117 }); 118 119 for (let prop in paddings) { 120 Assert.equal(paddings[prop], NEW_PADDING, "Got expected padding"); 121 } 122 123 await cleanTheDOM(); 124 125 gNavToolbox.style.padding = NEW_PADDING; 126 127 assertFlushesRequired(); 128 129 let rect = await window.promiseDocumentFlushed(() => { 130 let observer = { 131 reflow() { 132 Assert.ok(false, "A reflow should not have occurred."); 133 }, 134 reflowInterruptible() {}, 135 QueryInterface: ChromeUtils.generateQI([ 136 "nsIReflowObserver", 137 "nsISupportsWeakReference", 138 ]), 139 }; 140 141 let docShell = window.docShell; 142 docShell.addWeakReflowObserver(observer); 143 144 let toolboxRect = gNavToolbox.getBoundingClientRect(); 145 146 docShell.removeWeakReflowObserver(observer); 147 return toolboxRect; 148 }); 149 150 // The actual values of this rect aren't super important for 151 // the purposes of this test - we just want to know that a valid 152 // rect was returned, so checking for properties being greater than 153 // 0 is sufficient. 154 for (let property of ["width", "height"]) { 155 Assert.greater( 156 rect[property], 157 0, 158 `Rect property ${property} > 0 (${rect[property]})` 159 ); 160 } 161 162 await cleanTheDOM(); 163 }); 164 165 /** 166 * Test that if promiseDocumentFlushed is requested on a window 167 * that closes before it gets a chance to do a refresh driver 168 * tick, the promiseDocumentFlushed Promise is still resolved, and 169 * the callback is still called. 170 */ 171 add_task(async function test_resolved_in_window_close() { 172 let win = await BrowserTestUtils.openNewBrowserWindow(); 173 174 await win.promiseDocumentFlushed(() => {}); 175 176 // Use advanceTimeAndRefresh to pause paints in the window: 177 let utils = win.windowUtils; 178 utils.advanceTimeAndRefresh(0); 179 180 win.gNavToolbox.style.padding = "5px"; 181 182 const EXPECTED = 1234; 183 let promise = win.promiseDocumentFlushed(() => { 184 // Despite the window not painting before closing, this 185 // callback should be fired when the window gets torn 186 // down and should stil be able to return a result. 187 return EXPECTED; 188 }); 189 190 await BrowserTestUtils.closeWindow(win); 191 Assert.equal(await promise, EXPECTED); 192 }); 193 194 /** 195 * Test that re-entering promiseDocumentFlushed is not possible 196 * from within a promiseDocumentFlushed callback. Doing so will 197 * result in the outer Promise rejecting with InvalidStateError. 198 */ 199 add_task(async function test_reentrancy() { 200 dirtyStyleAndLayout(); 201 assertFlushesRequired(); 202 203 let promise = window.promiseDocumentFlushed(() => { 204 return window.promiseDocumentFlushed(() => { 205 Assert.ok(false, "Should never run this."); 206 }); 207 }); 208 209 await Assert.rejects(promise, ex => ex.name == "InvalidStateError"); 210 }); 211 212 /** 213 * Tests the expected execution order of a series of promiseDocumentFlushed 214 * calls, their callbacks, and the resolutions of their Promises. 215 * 216 * When multiple promiseDocumentFlushed callbacks are queued, the callbacks 217 * should always been run first before any of the Promises are resolved. 218 * 219 * The callbacks should run in the order that they were queued in via 220 * promiseDocumentFlushed. The Promise resolutions should similarly run 221 * in the order that promiseDocumentFlushed was called in. 222 */ 223 add_task(async function test_execution_order() { 224 let result = []; 225 226 dirtyStyleAndLayout(1); 227 let promise1 = window 228 .promiseDocumentFlushed(() => { 229 result.push(0); 230 }) 231 .then(() => { 232 result.push(2); 233 }); 234 235 let promise2 = window 236 .promiseDocumentFlushed(() => { 237 result.push(1); 238 }) 239 .then(() => { 240 result.push(3); 241 }); 242 243 await Promise.all([promise1, promise2]); 244 245 Assert.equal(result.length, 4, "Should have run all callbacks and Promises."); 246 247 let promise3 = window 248 .promiseDocumentFlushed(() => { 249 result.push(4); 250 }) 251 .then(() => { 252 result.push(6); 253 }); 254 255 let promise4 = window 256 .promiseDocumentFlushed(() => { 257 result.push(5); 258 }) 259 .then(() => { 260 result.push(7); 261 }); 262 263 await Promise.all([promise3, promise4]); 264 265 Assert.equal(result.length, 8, "Should have run all callbacks and Promises."); 266 267 for (let i = 0; i < result.length; ++i) { 268 Assert.equal( 269 result[i], 270 i, 271 "Callbacks and Promises should have run in the expected order." 272 ); 273 } 274 275 await cleanTheDOM(); 276 }); 277 278 /** 279 * Tests that modifying the DOM within a promiseDocumentFlushed callback 280 * will result in the Promise being rejected. 281 */ 282 add_task(async function test_reject_on_modification() { 283 dirtyStyleAndLayout(1); 284 assertFlushesRequired(); 285 286 let promise = window.promiseDocumentFlushed(() => { 287 dirtyStyleAndLayout(2); 288 }); 289 290 await Assert.rejects(promise, /NoModificationAllowedError/); 291 292 await cleanTheDOM(); 293 });