From f5b580e5e9b310e8995ad569fd7825f10b5b67c3 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Fri, 21 Apr 2017 13:22:34 -0700 Subject: [PATCH] Bug 1358415: Don't trigger reflow just to compute tab geometry. r=aswan MozReview-Commit-ID: DnFSbDfOskT --HG-- extra : rebase_source : e6829657b250fa8ec893b3e73b7d9956dfe34424 extra : amend_source : f20cfab374b29b7d3475579a0fe5e11ab2e6dcb4 --- browser/components/extensions/ext-tabs.js | 5 ++-- browser/components/extensions/ext-utils.js | 23 +++++++++++---- .../components/extensions/ExtensionTabs.jsm | 29 +++++++++++++++++-- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/browser/components/extensions/ext-tabs.js b/browser/components/extensions/ext-tabs.js index 89c62bf610a9..831be2c0b0a9 100644 --- a/browser/components/extensions/ext-tabs.js +++ b/browser/components/extensions/ext-tabs.js @@ -112,7 +112,7 @@ this.tabs = class extends ExtensionAPI { onCreated: new SingletonEventManager(context, "tabs.onCreated", fire => { let listener = (eventName, event) => { - fire.async(tabManager.convert(event.nativeTab)); + fire.async(tabManager.convert(event.nativeTab, event.currentTab)); }; tabTracker.on("tab-created", listener); @@ -360,6 +360,7 @@ this.tabs = class extends ExtensionAPI { options.disallowInheritPrincipal = true; tabListener.initTabReady(); + let currentTab = window.gBrowser.selectedTab; let nativeTab = window.gBrowser.addTab(url || window.BROWSER_NEW_TAB_URL, options); let active = true; @@ -394,7 +395,7 @@ this.tabs = class extends ExtensionAPI { tabListener.initializingTabs.add(nativeTab); } - return tabManager.convert(nativeTab); + return tabManager.convert(nativeTab, currentTab); }); }, diff --git a/browser/components/extensions/ext-utils.js b/browser/components/extensions/ext-utils.js index 5aee45c20675..18108b1a5cda 100644 --- a/browser/components/extensions/ext-utils.js +++ b/browser/components/extensions/ext-utils.js @@ -238,13 +238,18 @@ class TabTracker extends TabTrackerBase { }); } + // Save the current tab, since the newly-created tab will likely be + // active by the time the promise below resolves and the event is + // dispatched. + let currentTab = nativeTab.ownerGlobal.gBrowser.selectedTab; + // We need to delay sending this event until the next tick, since the // tab does not have its final index when the TabOpen event is dispatched. Promise.resolve().then(() => { if (event.detail.adoptedTab) { this.emitAttached(event.originalTarget); } else { - this.emitCreated(event.originalTarget); + this.emitCreated(event.originalTarget, currentTab); } }); break; @@ -389,10 +394,12 @@ class TabTracker extends TabTrackerBase { * * @param {NativeTab} nativeTab * The tab element which is being created. + * @param {NativeTab} [currentTab] + * The tab element for the currently active tab. * @private */ - emitCreated(nativeTab) { - this.emit("tab-created", {nativeTab}); + emitCreated(nativeTab, currentTab) { + this.emit("tab-created", {nativeTab, currentTab}); } /** @@ -479,12 +486,18 @@ class Tab extends TabBase { return this.nativeTab.linkedBrowser; } + get frameLoader() { + // If we don't have a frameLoader yet, just return a dummy with no width and + // height. + return super.frameLoader || {lazyWidth: 0, lazyHeight: 0}; + } + get cookieStoreId() { return getCookieStoreIdForTab(this, this.nativeTab); } get height() { - return this.browser.clientHeight; + return this.frameLoader.lazyHeight; } get index() { @@ -525,7 +538,7 @@ class Tab extends TabBase { } get width() { - return this.browser.clientWidth; + return this.frameLoader.lazyWidth; } get window() { diff --git a/toolkit/components/extensions/ExtensionTabs.jsm b/toolkit/components/extensions/ExtensionTabs.jsm index c377cdd97036..dacd6acaffd1 100644 --- a/toolkit/components/extensions/ExtensionTabs.jsm +++ b/toolkit/components/extensions/ExtensionTabs.jsm @@ -284,6 +284,15 @@ class TabBase { throw new Error("Not implemented"); } + /** + * @property {nsIFrameLoader} browser + * Returns the frameloader for the given tab. + * @readonly + */ + get frameLoader() { + return this.browser.frameLoader; + } + /** * @property {string} cookieStoreId * Returns the cookie store identifier for the given tab. @@ -454,9 +463,12 @@ class TabBase { * of its properties which the extension is permitted to access, in the format * requried to be returned by WebExtension APIs. * + * @param {Tab} [fallbackTab] + * A tab to retrieve geometry data from if the lazy geometry data for + * this tab hasn't been initialized yet. * @returns {object} */ - convert() { + convert(fallbackTab = null) { let result = { id: this.id, index: this.index, @@ -472,6 +484,13 @@ class TabBase { mutedInfo: this.mutedInfo, }; + // If the tab has not been fully layed-out yet, fallback to the geometry + // from a different tab (usually the currently active tab). + if (fallbackTab && (!result.width || !result.height)) { + result.width = fallbackTab.width; + result.height = fallbackTab.height; + } + if (this.extension.hasPermission("cookies")) { result.cookieStoreId = this.cookieStoreId; } @@ -1632,11 +1651,15 @@ class TabManagerBase { * * @param {NativeTab} nativeTab * The native tab to convert. + * @param {NativeTab} [fallbackTab] + * A tab to retrieve geometry data from if the lazy geometry data for + * this tab hasn't been initialized yet. * * @returns {Object} */ - convert(nativeTab) { - return this.getWrapper(nativeTab).convert(); + convert(nativeTab, fallbackTab = null) { + return this.getWrapper(nativeTab) + .convert(fallbackTab && this.getWrapper(fallbackTab)); } // The JSDoc validator does not support @returns tags in abstract functions or