/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const EXPECTED_REFLOWS = [ // handleEvent flushes layout to get the tabstrip width after a resize. "handleEvent@chrome://browser/content/tabbrowser.xml|", // Loading a tab causes a reflow. "loadTabs@chrome://browser/content/tabbrowser.xml|" + "loadOneOrMoreURIs@chrome://browser/content/browser.js|" + "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|", // Selecting the address bar causes a reflow. "select@chrome://global/content/bindings/textbox.xml|" + "focusAndSelectUrlBar@chrome://browser/content/browser.js|" + "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|", // Focusing the content area causes a reflow. "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|", ]; if (Services.appinfo.OS == "Darwin") { // TabsInTitlebar._update causes a reflow on OS X trying to do calculations // since layout info is already dirty. This doesn't seem to happen before // MozAfterPaint on other platforms. EXPECTED_REFLOWS.push("rect@chrome://browser/content/browser.js|" + "TabsInTitlebar._update@chrome://browser/content/browser.js|" + "updateAppearance@chrome://browser/content/browser.js|" + "handleEvent@chrome://browser/content/tabbrowser.xml|"); // _onOverflow causes a reflow getting widths. EXPECTED_REFLOWS.push("OverflowableToolbar.prototype._onOverflow@resource:///modules/CustomizableUI.jsm|" + "OverflowableToolbar.prototype.init@resource:///modules/CustomizableUI.jsm|" + "OverflowableToolbar.prototype.observe@resource:///modules/CustomizableUI.jsm|" + "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|"); // Same as above since in packaged builds there are no function names and the resource URI includes "app" EXPECTED_REFLOWS.push("@resource://app/modules/CustomizableUI.jsm|" + "@resource://app/modules/CustomizableUI.jsm|" + "@resource://app/modules/CustomizableUI.jsm|" + "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|"); } /* * This test ensures that there are no unexpected * uninterruptible reflows when opening new windows. */ function test() { waitForExplicitFinish(); // Add a reflow observer and open a new window let win = OpenBrowserWindow(); let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShell); docShell.addWeakReflowObserver(observer); // Wait until the mozafterpaint event occurs. waitForMozAfterPaint(win, function paintListener() { // Remove reflow observer and clean up. docShell.removeWeakReflowObserver(observer); win.close(); finish(); }); } let observer = { reflow: function (start, end) { // Gather information about the current code path. let stack = new Error().stack; let path = stack.split("\n").slice(1).map(line => { return line.replace(/:\d+$/, ""); }).join("|"); let pathWithLineNumbers = (new Error().stack).split("\n").slice(1).join("|"); // Stack trace is empty. Reflow was triggered by native code. if (path === "") { return; } // Check if this is an expected reflow. for (let expectedStack of EXPECTED_REFLOWS) { if (path.startsWith(expectedStack) || // Accept an empty function name for gBrowserInit._delayedStartup or TabsInTitlebar._update to workaround bug 906578. path.startsWith(expectedStack.replace(/(^|\|)(gBrowserInit\._delayedStartup|TabsInTitlebar\._update)@/, "$1@"))) { ok(true, "expected uninterruptible reflow '" + expectedStack + "'"); return; } } ok(false, "unexpected uninterruptible reflow '" + pathWithLineNumbers + "'"); }, reflowInterruptible: function (start, end) { // We're not interested in interruptible reflows. }, QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver, Ci.nsISupportsWeakReference]) }; function waitForMozAfterPaint(win, callback) { win.addEventListener("MozAfterPaint", function onEnd(event) { if (event.target != win) return; win.removeEventListener("MozAfterPaint", onEnd); executeSoon(callback); }); }