Bug 1559216 - Let tabs API wait until tab has been restored r=zombie a=RyanVM
authorRob Wu <rob@robwu.nl>
Thu, 25 Jul 2019 17:55:57 +0000
changeset 544765 fd005a1c331eab389d3dd9b6193760ff21c7ed70
parent 544764 7f81f139e57513ce3db876f315e18e09c9f8c561
child 544766 d95449d7770b3e006294d01ea2dd29ad7d33368c
push id2131
push userffxbld-merge
push dateMon, 26 Aug 2019 18:30:20 +0000
treeherdermozilla-release@b19ffb3ca153 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerszombie, RyanVM
bugs1559216
milestone69.0
Bug 1559216 - Let tabs API wait until tab has been restored r=zombie a=RyanVM Differential Revision: https://phabricator.services.mozilla.com/D37323
browser/components/extensions/parent/ext-tabs.js
browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js
--- a/browser/components/extensions/parent/ext-tabs.js
+++ b/browser/components/extensions/parent/ext-tabs.js
@@ -91,16 +91,19 @@ function showHiddenTabs(id) {
         win.gBrowser.showTab(tab);
       }
     }
   }
 }
 
 let tabListener = {
   tabReadyInitialized: false,
+  // Map[tab -> Promise]
+  tabBlockedPromises: new WeakMap(),
+  // Map[tab -> Deferred]
   tabReadyPromises: new WeakMap(),
   initializingTabs: new WeakSet(),
 
   initTabReady() {
     if (!this.tabReadyInitialized) {
       windowTracker.addListener("progress", this);
 
       this.tabReadyInitialized = true;
@@ -119,28 +122,43 @@ let tabListener = {
       let deferred = this.tabReadyPromises.get(nativeTab);
       if (deferred) {
         deferred.resolve(nativeTab);
         this.tabReadyPromises.delete(nativeTab);
       }
     }
   },
 
+  blockTabUntilRestored(nativeTab) {
+    let promise = ExtensionUtils.promiseEvent(nativeTab, "SSTabRestored").then(
+      ({ target }) => {
+        this.tabBlockedPromises.delete(target);
+        return target;
+      }
+    );
+
+    this.tabBlockedPromises.set(nativeTab, promise);
+  },
+
   /**
    * Returns a promise that resolves when the tab is ready.
    * Tabs created via the `tabs.create` method are "ready" once the location
    * changes to the requested URL. Other tabs are assumed to be ready once their
    * inner window ID is known.
    *
    * @param {XULElement} nativeTab The <tab> element.
    * @returns {Promise} Resolves with the given tab once ready.
    */
   awaitTabReady(nativeTab) {
     let deferred = this.tabReadyPromises.get(nativeTab);
     if (!deferred) {
+      let promise = this.tabBlockedPromises.get(nativeTab);
+      if (promise) {
+        return promise;
+      }
       deferred = PromiseUtils.defer();
       if (
         !this.initializingTabs.has(nativeTab) &&
         (nativeTab.linkedBrowser.innerWindowID ||
           nativeTab.linkedBrowser.currentURI.spec === "about:blank")
       ) {
         deferred.resolve(nativeTab);
       } else {
@@ -1068,16 +1086,18 @@ this.tabs = class extends ExtensionAPI {
 
         duplicate(tabId) {
           // Schema requires tab id.
           let nativeTab = getTabOrActive(tabId);
 
           let gBrowser = nativeTab.ownerGlobal.gBrowser;
           let newTab = gBrowser.duplicateTab(nativeTab);
 
+          tabListener.blockTabUntilRestored(newTab);
+
           return new Promise(resolve => {
             // We need to use SSTabRestoring because any attributes set before
             // are ignored. SSTabRestored is too late and results in a jump in
             // the UI. See http://bit.ly/session-store-api for more information.
             newTab.addEventListener(
               "SSTabRestoring",
               function() {
                 // As the tab is restoring, move it to the correct position.
--- a/browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js
@@ -177,17 +177,18 @@ add_task(async function testDuplicateRes
   const BASE =
     "http://mochi.test:8888/browser/browser/components/extensions/test/browser/";
   const URL = BASE + "file_slowed_document.sjs";
 
   await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
-      permissions: ["tabs"],
+      // The host permission matches the above URL. No :8888 due to bug 1468162.
+      permissions: ["tabs", "http://mochi.test/"],
     },
 
     background: async function() {
       browser.tabs.query(
         {
           lastFocusedWindow: true,
         },
         async tabs => {
@@ -203,16 +204,22 @@ add_task(async function testDuplicateRes
 
           browser.tabs.duplicate(tabs[1].id, async tab => {
             // if the promise is resolved before any onUpdated event has been fired,
             // then the promise has been resolved before waiting for the tab to load
             if (resolvedRightAway === null) {
               resolvedRightAway = true;
             }
 
+            // Regression test for bug 1559216: APIs such as tabs.executeScript
+            // should be queued until tabs.duplicate has restored the tab.
+            let code = "document.URL";
+            let [result] = await browser.tabs.executeScript(tab.id, { code });
+            browser.test.assertEq(tab.url, result, "executeScript result");
+
             await browser.tabs.remove([tabs[1].id, tab.id]);
             if (resolvedRightAway) {
               browser.test.notifyPass("tabs.duplicate.resolvePromiseRightAway");
             } else {
               browser.test.notifyFail("tabs.duplicate.resolvePromiseRightAway");
             }
           });
         }