and ready for * screenshots. Returns false if the page loaded bug does not have the * expected URL. */ async reftestWait(options = {}) { const { url, useRemote } = options; const loadedURL = await this._loadedURLPromise; if (loadedURL !== url) { lazy.logger.debug( `Window URL does not match the expected URL "${loadedURL}" !== "${url}"` ); return false; } const documentElement = this.document.documentElement; const hasReftestWait = documentElement.classList.contains("reftest-wait"); lazy.logger.debug("Waiting for event loop to spin"); await new Promise(resolve => lazy.setTimeout(resolve, 0)); await this.paintComplete({ useRemote, ignoreThrottledAnimations: true, hasReftestWait, }); if (hasReftestWait) { const event = new this.document.defaultView.Event("TestRendered", { bubbles: true, }); documentElement.dispatchEvent(event); lazy.logger.info("Emitted TestRendered event"); await this.reftestWaitRemoved(); await this.paintComplete({ useRemote, ignoreThrottledAnimations: false, hasReftestWait, }); } if ( options.warnOnOverflow && (this.document.defaultView.innerWidth < documentElement.scrollWidth || this.document.defaultView.innerHeight < documentElement.scrollHeight) ) { lazy.logger.warn( `${url} overflows viewport (width: ${documentElement.scrollWidth}, height: ${documentElement.scrollHeight})` ); } return true; } paintComplete({ useRemote, ignoreThrottledAnimations, hasReftestWait }) { lazy.logger.debug("Waiting for rendering"); let win = this.document.defaultView; let windowUtils = win.windowUtils; let painted = false; const documentElement = this.document.documentElement; return new Promise(resolve => { let maybeResolve = () => { this.flushRendering({ ignoreThrottledAnimations }); if (useRemote) { // Flush display (paint) lazy.logger.debug("Force update of layer tree"); windowUtils.updateLayerTree(); } const once = hasReftestWait && !documentElement.classList.contains("reftest-wait"); if (windowUtils.isMozAfterPaintPending && (!once || !painted)) { lazy.logger.debug("isMozAfterPaintPending: true"); win.windowRoot.addEventListener( "MozAfterPaint", () => { lazy.logger.debug("MozAfterPaint fired"); painted = true; maybeResolve(); }, { once: true } ); } else { // resolve at the start of the next frame in case of leftover paints lazy.logger.debug("isMozAfterPaintPending: false"); win.requestAnimationFrame(() => { win.requestAnimationFrame(resolve); }); } }; maybeResolve(); }); } reftestWaitRemoved() { lazy.logger.debug("Waiting for reftest-wait removal"); return new Promise(resolve => { const documentElement = this.document.documentElement; let observer = new this.document.defaultView.MutationObserver(() => { if (!documentElement.classList.contains("reftest-wait")) { observer.disconnect(); lazy.logger.debug("reftest-wait removed"); lazy.setTimeout(resolve, 0); } }); if (documentElement.classList.contains("reftest-wait")) { observer.observe(documentElement, { attributes: true }); } else { lazy.setTimeout(resolve, 0); } }); } /** * Ensure layout is flushed in each frame * * @param {object} options * @param {boolean} options.ignoreThrottledAnimations Don't flush * the layout of throttled animations. We can end up in a * situation where flushing a throttled animation causes * mozAfterPaint events even when all rendering we care about * should have ceased. See * https://searchfox.org/mozilla-central/rev/d58860eb739af613774c942c3bb61754123e449b/layout/tools/reftest/reftest-content.js#723-729 * for more detail. */ flushRendering(options = {}) { let { ignoreThrottledAnimations } = options; lazy.logger.debug( `flushRendering ignoreThrottledAnimations:${ignoreThrottledAnimations}` ); let anyPendingPaintsGeneratedInDescendants = false; function flushWindow(win) { let utils = win.windowUtils; let afterPaintWasPending = utils.isMozAfterPaintPending; let root = win.document.documentElement; if (root) { try { if (ignoreThrottledAnimations) { utils.flushLayoutWithoutThrottledAnimations(); } else { root.getBoundingClientRect(); } } catch (e) { lazy.logger.error("flushWindow failed", e); } } if (!afterPaintWasPending && utils.isMozAfterPaintPending) { anyPendingPaintsGeneratedInDescendants = true; } for (let i = 0; i < win.frames.length; ++i) { // Skip remote frames, flushRendering will be called on their individual // MarionetteReftest actor via _recursiveFlushRendering performed from // the topmost MarionetteReftest actor. if (!Cu.isRemoteProxy(win.frames[i])) { flushWindow(win.frames[i]); } } } let thisWin = this.document.defaultView; flushWindow(thisWin); if ( anyPendingPaintsGeneratedInDescendants && !thisWin.windowUtils.isMozAfterPaintPending ) { lazy.logger.error( "Descendant frame generated a MozAfterPaint event, " + "but the root document doesn't have one!" ); } } } PK