bug 1238312 implement tabs.duplicate r?kmag draft
authorAndy McKay <amckay@mozilla.com>
Fri, 12 Feb 2016 15:38:55 -0800
changeset 331025 f31cc613486edf05312b7dabd1aef13759056f46
parent 330694 d719ac4bcbec13e0ba13a41547788e3bf365c679
child 514291 fee1bdd7d7d87bfaf65658b77c9d296b2a42a1b4
push id10887
push userbmo:amckay@mozilla.com
push dateMon, 15 Feb 2016 19:08:00 +0000
reviewerskmag
bugs1238312
milestone47.0a1
bug 1238312 implement tabs.duplicate r?kmag MozReview-Commit-ID: Fzd6BuAEgSl
browser/components/extensions/ext-tabs.js
browser/components/extensions/test/browser/browser.ini
browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js
toolkit/components/extensions/ExtensionUtils.jsm
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -641,12 +641,31 @@ extensions.registerSchemaAPI("tabs", nul
             // If the window we are moving is the same, just move the tab.
             gBrowser.moveTabTo(tab, insertionPoint);
           }
           tabsMoved.push(tab);
         }
 
         return Promise.resolve(tabsMoved.map(tab => TabManager.convert(extension, tab)));
       },
+
+      duplicate: function(tabId) {
+        let tab = TabManager.getTab(tabId);
+        if (!tab) {
+          return Promise.reject({message: `Invalid tab ID: ${tabId}`});
+        }
+
+        let gBrowser = tab.ownerDocument.defaultView.gBrowser;
+        let newTab = gBrowser.duplicateTab(tab);
+        gBrowser.moveTabTo(newTab, tab._tPos + 1);
+        gBrowser.selectTabAtIndex(newTab._tPos);
+
+        return new Promise(resolve => {
+          newTab.addEventListener("SSTabRestored", function listener() {
+            newTab.removeEventListener("SSTabRestored", listener);
+            return resolve(TabManager.convert(extension, newTab));
+          });
+        });
+      },
     },
   };
   return self;
 });
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -26,16 +26,17 @@ support-files =
 [browser_ext_tabs_captureVisibleTab.js]
 [browser_ext_tabs_executeScript.js]
 [browser_ext_tabs_executeScript_good.js]
 [browser_ext_tabs_executeScript_bad.js]
 [browser_ext_tabs_insertCSS.js]
 [browser_ext_tabs_query.js]
 [browser_ext_tabs_getCurrent.js]
 [browser_ext_tabs_create.js]
+[browser_ext_tabs_duplicate.js]
 [browser_ext_tabs_update.js]
 [browser_ext_tabs_onUpdated.js]
 [browser_ext_tabs_sendMessage.js]
 [browser_ext_tabs_move.js]
 [browser_ext_tabs_move_window.js]
 [browser_ext_windows_update.js]
 [browser_ext_contentscript_connect.js]
 [browser_ext_tab_runtimeConnect.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js
@@ -0,0 +1,44 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testDuplicateTab() {
+  yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.net/");
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs"],
+    },
+
+    background: function() {
+      browser.tabs.query({
+        lastFocusedWindow: true,
+      }, function(tabs) {
+        let source = tabs[1];
+        // By moving it 0, we check that the new tab is created next
+        // to the existing one.
+        browser.tabs.move(source.id, {index: 0}, () => {
+          browser.tabs.duplicate(source.id, (tab) => {
+            browser.test.assertEq("http://example.net/", tab.url);
+            // Should be the second tab, next to the one duplicated.
+            browser.test.assertEq(1, tab.index);
+            // Should be selected by default.
+            browser.test.assertTrue(tab.selected);
+            browser.test.notifyPass("tabs.duplicate");
+          });
+        });
+      });
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("tabs.duplicate");
+  yield extension.unload();
+
+  while (window.gBrowser.tabs.length > 1) {
+    let tab = window.gBrowser.tabs[0];
+    if (tab.linkedBrowser.currentURI.spec === "http://example.net/") {
+      yield BrowserTestUtils.removeTab(tab);
+    }
+  }
+});
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -199,17 +199,17 @@ class BaseContext {
   /**
    * Wraps the given promise so it can be safely returned to extension
    * code in this context.
    *
    * If `callback` is provided, however, it is used as a completion
    * function for the promise, and no promise is returned. In this case,
    * the callback is called when the promise resolves or rejects. In the
    * latter case, `lastError` is set to the rejection value, and the
-   * callback funciton must check `browser.runtime.lastError` or
+   * callback function must check `browser.runtime.lastError` or
    * `extension.runtime.lastError` in order to prevent it being reported
    * to the console.
    *
    * @param {Promise} promise The promise with which to wrap the
    *     callback. May resolve to a `SpreadArgs` instance, in which case
    *     each element will be used as a separate argument.
    *
    *     Unless the promise object belongs to the cloneScope global, its