Bug 1500180 - Take around only geometry data of the fallback tab for pended operations. r=mixedpuppy
authorYUKI "Piro" Hiroshi <yuki@clear-code.com>
Tue, 23 Oct 2018 10:12:46 +0900
changeset 490870 fa0bea21d616b5106fdf879e14ae71359ebad37e
parent 490867 4688a861d5124f7ece9ce1ad6c01193c77391a53
child 490903 89cef77c46f583a1f20c050453fdb7db43f128f1
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersmixedpuppy
bugs1500180
milestone65.0a1
Bug 1500180 - Take around only geometry data of the fallback tab for pended operations. r=mixedpuppy
browser/components/extensions/parent/ext-browser.js
browser/components/extensions/parent/ext-tabs.js
browser/components/extensions/test/browser/browser_ext_tabs_events.js
toolkit/components/extensions/parent/ext-tabs-base.js
--- a/browser/components/extensions/parent/ext-browser.js
+++ b/browser/components/extensions/parent/ext-browser.js
@@ -439,26 +439,31 @@ class TabTracker extends TabTrackerBase 
           // This tab is being created to adopt a tab from a different window.
           // Handle the adoption.
           this.adopt(nativeTab, adoptedTab);
 
           adoptedTab.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetFrameData", {
             windowId: windowTracker.getId(nativeTab.ownerGlobal),
           });
         } else {
-          // 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;
+          // Save the size of the current tab, since the newly-created tab will
+          // likely be active by the time the promise below resolves and the
+          // event is dispatched.
+          const currentTab = nativeTab.ownerGlobal.gBrowser.selectedTab;
+          const {frameLoader} = currentTab.linkedBrowser;
+          const currentTabSize = {
+            width: frameLoader.lazyWidth,
+            height: frameLoader.lazyHeight,
+          };
 
           // We need to delay sending this event until the next tick, since the
           // tab could have been created with a lazy browser but still not have
           // been assigned a SessionStore tab state with the URL and title.
           Promise.resolve().then(() => {
-            this.emitCreated(event.originalTarget, currentTab);
+            this.emitCreated(event.originalTarget, currentTabSize);
           });
         }
         break;
 
       case "TabClose":
         let {adoptedBy} = event.detail;
         if (adoptedBy) {
           // This tab is being closed because it was adopted by a new window.
@@ -579,22 +584,22 @@ class TabTracker extends TabTrackerBase 
     this.emit("tabs-highlighted", {tabIds, windowId});
   }
 
   /**
    * Emits a "tab-created" event for the given tab element.
    *
    * @param {NativeTab} nativeTab
    *        The tab element which is being created.
-   * @param {NativeTab} [currentTab]
-   *        The tab element for the currently active tab.
+   * @param {Object} [currentTabSize]
+   *        The size of the tab element for the currently active tab.
    * @private
    */
-  emitCreated(nativeTab, currentTab) {
-    this.emit("tab-created", {nativeTab, currentTab});
+  emitCreated(nativeTab, currentTabSize) {
+    this.emit("tab-created", {nativeTab, currentTabSize});
   }
 
   /**
    * Emits a "tab-removed" event for the given tab element.
    *
    * @param {NativeTab} nativeTab
    *        The tab element which is being removed.
    * @param {boolean} isWindowClosing
--- a/browser/components/extensions/parent/ext-tabs.js
+++ b/browser/components/extensions/parent/ext-tabs.js
@@ -398,17 +398,17 @@ this.tabs = class extends ExtensionAPI {
           },
         }).api(),
 
         onCreated: new EventManager({
           context,
           name: "tabs.onCreated",
           register: fire => {
             let listener = (eventName, event) => {
-              fire.async(tabManager.convert(event.nativeTab, event.currentTab));
+              fire.async(tabManager.convert(event.nativeTab, event.currentTabSize));
             };
 
             tabTracker.on("tab-created", listener);
             return () => {
               tabTracker.off("tab-created", listener);
             };
           },
         }).api(),
@@ -559,17 +559,22 @@ this.tabs = class extends ExtensionAPI {
                 privateBrowsingId: PrivateBrowsingUtils.isBrowserPrivate(window.gBrowser) ? 1 : 0,
               });
             } else {
               options.allowInheritPrincipal = true;
               options.triggeringPrincipal = context.principal;
             }
 
             tabListener.initTabReady();
-            let currentTab = window.gBrowser.selectedTab;
+            const currentTab = window.gBrowser.selectedTab;
+            const {frameLoader} = currentTab.linkedBrowser;
+            const currentTabSize = {
+              width: frameLoader.lazyWidth,
+              height: frameLoader.lazyHeight,
+            };
 
             if (createProperties.openerTabId !== null) {
               options.ownerTab = tabTracker.getTab(createProperties.openerTabId);
               options.openerBrowser = options.ownerTab.linkedBrowser;
               if (options.ownerTab.ownerGlobal !== window) {
                 return Promise.reject({message: "Opener tab must be in the same window as the tab being created"});
               }
             }
@@ -626,17 +631,17 @@ this.tabs = class extends ExtensionAPI {
 
               // Mark the tab as initializing, so that operations like
               // `executeScript` wait until the requested URL is loaded in
               // the tab before dispatching messages to the inner window
               // that contains the URL we're attempting to load.
               tabListener.initializingTabs.add(nativeTab);
             }
 
-            return tabManager.convert(nativeTab, currentTab);
+            return tabManager.convert(nativeTab, currentTabSize);
           });
         },
 
         async remove(tabs) {
           if (!Array.isArray(tabs)) {
             tabs = [tabs];
           }
 
--- a/browser/components/extensions/test/browser/browser_ext_tabs_events.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_events.js
@@ -367,8 +367,46 @@ add_task(async function testTabCreateRel
      "Middle click should open site to correct url.");
   BrowserTestUtils.removeTab(openTab);
 
   await extension.awaitMessage("tabRemoved");
   await extension.unload();
 
   BrowserTestUtils.removeTab(openerTab);
 });
+
+add_task(async function testLastTabRemoval() {
+  await SpecialPowers.pushPrefEnv({set: [
+    ["browser.tabs.closeWindowWithLastTab", false],
+  ]});
+
+  async function background() {
+    let windowId;
+    browser.tabs.onCreated.addListener(tab => {
+      browser.test.assertEq(windowId, tab.windowId,
+                            "expecting onCreated after onRemoved on the same window");
+      browser.test.sendMessage("tabCreated", `${tab.width}x${tab.height}`);
+    });
+    browser.tabs.onRemoved.addListener((tabId, info) => {
+      windowId = info.windowId;
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs"],
+    },
+    background,
+  });
+
+  let newWin = await BrowserTestUtils.openNewBrowserWindow();
+  await extension.startup();
+
+  const oldBrowser = newWin.gBrowser.selectedBrowser;
+  const expectedDims = `${oldBrowser.clientWidth}x${oldBrowser.clientHeight}`;
+  BrowserTestUtils.removeTab(newWin.gBrowser.selectedTab);
+
+  const actualDims = await extension.awaitMessage("tabCreated");
+  is(actualDims, expectedDims, "created tab reports a size same to the removed last tab");
+
+  await extension.unload();
+  await BrowserTestUtils.closeWindow(newWin);
+});
--- a/toolkit/components/extensions/parent/ext-tabs-base.js
+++ b/toolkit/components/extensions/parent/ext-tabs-base.js
@@ -291,17 +291,17 @@ class TabBase {
   }
 
   /**
    * @property {FrameLoader} frameLoader
    *        Returns the frameloader for the given tab.
    *        @readonly
    */
   get frameLoader() {
-    return this.browser.frameLoader;
+    return this.browser && this.browser.frameLoader;
   }
 
   /**
    * @property {string} cookieStoreId
    *        Returns the cookie store identifier for the given tab.
    *        @readonly
    *        @abstract
    */
@@ -576,22 +576,22 @@ class TabBase {
     return true;
   }
 
   /**
    * Converts this tab object to a JSON-compatible object containing the values
    * of its properties which the extension is permitted to access, in the format
    * required 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.
+   * @param {Object} [fallbackTabSize]
+   *        A geometry data if the lazy geometry data for this tab hasn't been
+   *        initialized yet.
    * @returns {object}
    */
-  convert(fallbackTab = null) {
+  convert(fallbackTabSize = null) {
     let result = {
       id: this.id,
       index: this.index,
       windowId: this.windowId,
       highlighted: this.highlighted,
       active: this.active,
       attention: this.attention,
       pinned: this.pinned,
@@ -606,19 +606,19 @@ class TabBase {
       mutedInfo: this.mutedInfo,
       isArticle: this.isArticle,
       isInReaderMode: this.isInReaderMode,
       sharingState: this.sharingState,
     };
 
     // 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 (fallbackTabSize && (!result.width || !result.height)) {
+      result.width = fallbackTabSize.width;
+      result.height = fallbackTabSize.height;
     }
 
     let opener = this.openerTabId;
     if (opener) {
       result.openerTabId = opener;
     }
 
     if (this.extension.hasPermission("cookies")) {
@@ -1826,25 +1826,25 @@ class TabManagerBase {
 
   /**
    * Converts the given native tab to a JSON-compatible object, in the format
    * required to be returned by WebExtension APIs, which may be safely passed to
    * extension code.
    *
    * @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.
+   * @param {Object} [fallbackTabSize]
+   *        A geometry data if the lazy geometry data for this tab hasn't been
+   *        initialized yet.
    *
    * @returns {Object}
    */
-  convert(nativeTab, fallbackTab = null) {
+  convert(nativeTab, fallbackTabSize = null) {
     return this.getWrapper(nativeTab)
-               .convert(fallbackTab && this.getWrapper(fallbackTab));
+               .convert(fallbackTabSize);
   }
 
   // The JSDoc validator does not support @returns tags in abstract functions or
   // star functions without return statements.
   /* eslint-disable valid-jsdoc */
   /**
    * Returns an iterator of TabBase objects which match the given query info.
    *