michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "docShell", () => { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShell); michael@0: }); michael@0: michael@0: const EXPECTED_REFLOWS = [ michael@0: // tabbrowser.adjustTabstrip() call after tabopen animation has finished michael@0: "adjustTabstrip@chrome://browser/content/tabbrowser.xml|" + michael@0: "_handleNewTab@chrome://browser/content/tabbrowser.xml|" + michael@0: "onxbltransitionend@chrome://browser/content/tabbrowser.xml|", michael@0: michael@0: // switching focus in updateCurrentBrowser() causes reflows michael@0: "updateCurrentBrowser@chrome://browser/content/tabbrowser.xml|" + michael@0: "onselect@chrome://browser/content/browser.xul|", michael@0: michael@0: // switching focus in openLinkIn() causes reflows michael@0: "openLinkIn@chrome://browser/content/utilityOverlay.js|" + michael@0: "openUILinkIn@chrome://browser/content/utilityOverlay.js|" + michael@0: "BrowserOpenTab@chrome://browser/content/browser.js|", michael@0: michael@0: // accessing element.scrollPosition in _fillTrailingGap() flushes layout michael@0: "get_scrollPosition@chrome://global/content/bindings/scrollbox.xml|" + michael@0: "_fillTrailingGap@chrome://browser/content/tabbrowser.xml|" + michael@0: "_handleNewTab@chrome://browser/content/tabbrowser.xml|" + michael@0: "onxbltransitionend@chrome://browser/content/tabbrowser.xml|", michael@0: michael@0: // The TabView iframe causes reflows in the parent document. michael@0: "iQClass_height@chrome://browser/content/tabview.js|" + michael@0: "GroupItem_getContentBounds@chrome://browser/content/tabview.js|" + michael@0: "GroupItem_shouldStack@chrome://browser/content/tabview.js|" + michael@0: "GroupItem_arrange@chrome://browser/content/tabview.js|" + michael@0: "GroupItem_add@chrome://browser/content/tabview.js|" + michael@0: "GroupItems_newTab@chrome://browser/content/tabview.js|" + michael@0: "TabItem__reconnect@chrome://browser/content/tabview.js|" + michael@0: "TabItem@chrome://browser/content/tabview.js|" + michael@0: "TabItems_link@chrome://browser/content/tabview.js|" + michael@0: "TabItems_init/this._eventListeners.open@chrome://browser/content/tabview.js|", michael@0: michael@0: // SessionStore.getWindowDimensions() michael@0: "ssi_getWindowDimension@resource:///modules/sessionstore/SessionStore.jsm|" + michael@0: "ssi_updateWindowFeatures/<@resource:///modules/sessionstore/SessionStore.jsm|" + michael@0: "ssi_updateWindowFeatures@resource:///modules/sessionstore/SessionStore.jsm|" + michael@0: "ssi_collectWindowData@resource:///modules/sessionstore/SessionStore.jsm|", michael@0: michael@0: // tabPreviews.capture() michael@0: "tabPreviews_capture@chrome://browser/content/browser.js|" + michael@0: "tabPreviews_handleEvent/<@chrome://browser/content/browser.js|", michael@0: michael@0: // tabPreviews.capture() michael@0: "tabPreviews_capture@chrome://browser/content/browser.js|" + michael@0: "@chrome://browser/content/browser.js|" michael@0: ]; michael@0: michael@0: const PREF_PRELOAD = "browser.newtab.preload"; michael@0: const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directorySource"; michael@0: michael@0: /* michael@0: * This test ensures that there are no unexpected michael@0: * uninterruptible reflows when opening new tabs. michael@0: */ michael@0: function test() { michael@0: waitForExplicitFinish(); michael@0: michael@0: Services.prefs.setBoolPref(PREF_PRELOAD, false); michael@0: Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, "data:application/json,{}"); michael@0: registerCleanupFunction(() => { michael@0: Services.prefs.clearUserPref(PREF_PRELOAD); michael@0: Services.prefs.clearUserPref(PREF_NEWTAB_DIRECTORYSOURCE); michael@0: }); michael@0: michael@0: // Add a reflow observer and open a new tab. michael@0: docShell.addWeakReflowObserver(observer); michael@0: BrowserOpenTab(); michael@0: michael@0: // Wait until the tabopen animation has finished. michael@0: waitForTransitionEnd(function () { michael@0: // Remove reflow observer and clean up. michael@0: docShell.removeWeakReflowObserver(observer); michael@0: gBrowser.removeCurrentTab(); 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 path = (new Error().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 stack of EXPECTED_REFLOWS) { michael@0: if (path.startsWith(stack)) { michael@0: ok(true, "expected uninterruptible reflow '" + stack + "'"); 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 waitForTransitionEnd(callback) { michael@0: let tab = gBrowser.selectedTab; michael@0: tab.addEventListener("transitionend", function onEnd(event) { michael@0: if (event.propertyName === "max-width") { michael@0: tab.removeEventListener("transitionend", onEnd); michael@0: executeSoon(callback); michael@0: } michael@0: }); michael@0: }