Bug 1247493: [webext] Implement the `tabId` property of windows.create. r?billm draft
authorKris Maglione <maglione.k@gmail.com>
Thu, 11 Feb 2016 15:35:59 -0800
changeset 330490 36045cd470c2e96f746e88ce18c6d3769940f451
parent 330489 5f84d1778a83899cc04b37db839153dee8dfb86e
child 514187 9af1ffaec3a613c4305b8e03d9117ed15ac40074
push id10771
push usermaglione.k@gmail.com
push dateThu, 11 Feb 2016 23:39:49 +0000
reviewersbillm
bugs1247493
milestone47.0a1
Bug 1247493: [webext] Implement the `tabId` property of windows.create. r?billm MozReview-Commit-ID: Jw4KvvUqkBh
browser/components/extensions/ext-windows.js
browser/components/extensions/test/browser/browser.ini
browser/components/extensions/test/browser/browser_ext_tabs_events.js
browser/components/extensions/test/browser/browser_ext_windows_create_tabId.js
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -1,15 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+                                  "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
 } = ExtensionUtils;
 
 extensions.registerSchemaAPI("windows", null, (extension, context) => {
   return {
@@ -63,17 +65,35 @@ extensions.registerSchemaAPI("windows", 
       create: function(createData) {
         function mkstr(s) {
           let result = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
           result.data = s;
           return result;
         }
 
         let args = Cc["@mozilla.org/supports-array;1"].createInstance(Ci.nsISupportsArray);
-        if (createData.url !== null) {
+
+        if (createData.tabId !== null) {
+          if (createData.url !== null) {
+            return Promise.reject({ message: "`tabId` may not be used in conjunction with `url`" });
+          }
+          if (createData.incognito !== null) {
+            return Promise.reject({ message: "`tabId` may not be used in conjunction with `incognito`" });
+          }
+
+          let tab = TabManager.getTab(createData.tabId);
+          if (tab == null) {
+            return Promise.reject({ message: `Invalid tab ID: ${createData.tabId}` });
+          }
+          args.AppendElement(tab);
+
+          // Private browsing tabs can only be moved to private browsing
+          // windows.
+          createData.incognito = PrivateBrowsingUtils.isBrowserPrivate(tab.linkedBrowser);
+        } else if (createData.url !== null) {
           if (Array.isArray(createData.url)) {
             let array = Cc["@mozilla.org/supports-array;1"].createInstance(Ci.nsISupportsArray);
             for (let url of createData.url) {
               array.AppendElement(mkstr(url));
             }
             args.AppendElement(array);
           } else {
             args.AppendElement(mkstr(createData.url));
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -34,12 +34,13 @@ skip-if = !e10s
 [browser_ext_tabs_query.js]
 [browser_ext_tabs_getCurrent.js]
 [browser_ext_tabs_create.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_create_tabId.js]
 [browser_ext_windows_update.js]
 [browser_ext_contentscript_connect.js]
 [browser_ext_tab_runtimeConnect.js]
 [browser_ext_webNavigation_getFrames.js]
--- a/browser/components/extensions/test/browser/browser_ext_tabs_events.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_events.js
@@ -42,21 +42,22 @@ add_task(function* testTabEvents() {
           browser.test.assertEq(name, i in events && events[i].type,
                                 `Got expected ${name} event`);
         }
         return events.splice(0);
       });
     }
 
     browser.test.log("Create second browser window");
+    let windowId;
     Promise.all([
       browser.windows.getCurrent(),
       browser.windows.create({}),
     ]).then(windows => {
-      let windowId = windows[0].id;
+      windowId = windows[0].id;
       let otherWindowId = windows[1].id;
       let initialTab;
 
       return expectEvents(["onCreated"]).then(([created]) => {
         initialTab = created.tab;
 
         browser.test.log("Create tab in window 1");
         return browser.tabs.create({ windowId, index: 0, url: "about:blank" });
@@ -104,20 +105,53 @@ add_task(function* testTabEvents() {
           browser.test.log("Close second window");
           return browser.windows.remove(otherWindowId);
         }).then(() => {
           return expectEvents(["onRemoved"]);
         }).then(([removed]) => {
           browser.test.assertEq(initialTab.id, removed.tabId, "Expected removed tab ID");
           browser.test.assertEq(otherWindowId, removed.windowId, "Expected removed tab window ID");
           browser.test.assertEq(true, removed.isWindowClosing, "Expected isWindowClosing value");
-        }).then(() => {
-          browser.test.notifyPass("tabs-events");
         });
       });
+    }).then(() => {
+      browser.test.log("Create additional tab in window 1");
+      return browser.tabs.create({ windowId, url: "about:blank" });
+    }).then(tab => {
+      return expectEvents(["onCreated"]).then(() => {
+        browser.test.log("Create a new window, adopting the new tab");
+
+        // We have to explicitly wait for the event here, since its timing is
+        // not predictable.
+        let promiseAttached = new Promise(resolve => {
+          browser.tabs.onAttached.addListener(function listener(tabId) {
+            browser.tabs.onAttached.removeListener(listener);
+            resolve();
+          });
+        });
+
+        return Promise.all([
+          browser.windows.create({ tabId: tab.id }),
+          promiseAttached,
+        ]);
+      }).then(([window]) => {
+        return expectEvents(["onDetached", "onAttached"]).then(([detached, attached]) => {
+          browser.test.assertEq(tab.id, detached.tabId, "Expected onDetached tab ID");
+
+          browser.test.assertEq(tab.id, attached.tabId, "Expected onAttached tab ID");
+          browser.test.assertEq(0, attached.newPosition, "Expected onAttached new index");
+          browser.test.assertEq(window.id, attached.newWindowId,
+                                "Expected onAttached new window id");
+
+          browser.test.log("Close the new window");
+          return browser.windows.remove(window.id);
+        });
+      });
+    }).then(() => {
+      browser.test.notifyPass("tabs-events");
     }).catch(e => {
       try {
         browser.test.fail(`${e} :: ${e.stack}`);
       } catch (ex) {
         throw e;
       }
       browser.test.notifyFail("tabs-events");
     });
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_windows_create_tabId.js
@@ -0,0 +1,108 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testWindowCreate() {
+  function background() {
+    let promiseTabAttached = () => {
+      return new Promise(resolve => {
+        browser.tabs.onAttached.addListener(function listener() {
+          browser.tabs.onAttached.removeListener(listener);
+          resolve();
+        });
+      });
+    };
+
+    let windowId;
+    browser.windows.getCurrent().then(window => {
+      windowId = window.id;
+
+      browser.test.log("Create additional tab in window 1");
+      return browser.tabs.create({ windowId, url: "about:blank" });
+    }).then(tab => {
+      browser.test.log("Create a new window, adopting the new tab");
+
+      // Note that we want to check against actual boolean values for
+      // all of the `incognito` property tests.
+      browser.test.assertEq(false, tab.incognito, "Tab is not private");
+
+      return Promise.all([
+        promiseTabAttached(),
+        browser.windows.create({ tabId: tab.id }),
+      ]);
+    }).then(([, window]) => {
+      browser.test.assertEq(false, window.incognito, "New window is not private");
+
+      browser.test.log("Close the new window");
+      return browser.windows.remove(window.id);
+    }).then(() => {
+      browser.test.log("Create a new private window");
+
+      return browser.windows.create({ incognito: true });
+    }).then(privateWindow => {
+      browser.test.assertEq(true, privateWindow.incognito, "Private window is private");
+
+      browser.test.log("Create additional tab in private window");
+      return browser.tabs.create({ windowId: privateWindow.id }).then(privateTab => {
+        browser.test.assertEq(true, privateTab.incognito, "Private tab is private");
+
+        browser.test.log("Create a new window, adopting the new private tab");
+
+        return Promise.all([
+          promiseTabAttached(),
+          browser.windows.create({ tabId: privateTab.id }),
+        ]);
+      }).then(([, newWindow]) => {
+        browser.test.assertEq(true, newWindow.incognito, "New private window is private");
+
+        browser.test.log("Close the new private window");
+        return browser.windows.remove(newWindow.id);
+      }).then(() => {
+        browser.test.log("Close the private window");
+        return browser.windows.remove(privateWindow.id);
+      });
+    }).then(() => {
+      return browser.tabs.query({ windowId, active: true });
+    }).then(([tab]) => {
+      browser.test.log("Try to create a window with both a tab and a URL");
+
+      return browser.windows.create({ tabId: tab.id, url: "http://example.com/" }).then(
+        window => {
+          browser.test.fail("Create call should have failed");
+        },
+        error => {
+          browser.test.assertTrue(/`tabId` may not be used in conjunction with `url`/.test(error.message),
+                                  "Create call failed as expected");
+        }).then(() => {
+          browser.test.log("Try to create a window with both a tab and an incognito setting");
+
+          return browser.windows.create({ tabId: tab.id, incognito: false });
+        }).then(
+          window => {
+            browser.test.fail("Create call should have failed");
+          },
+          error => {
+            browser.test.assertTrue(/`tabId` may not be used in conjunction with `incognito`/.test(error.message),
+                                    "Create call failed as expected");
+          });
+    }).then(() => {
+      browser.test.notifyPass("window-create");
+    }).catch(e => {
+      browser.test.fail(`${e} :: ${e.stack}`);
+      browser.test.notifyFail("window-create");
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs"],
+    },
+
+    background,
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("window-create");
+  yield extension.unload();
+});
+