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