Bug 1341126 implement open for browser/page/sidebar actions, r?kmag draft
authorShane Caraveo <scaraveo@mozilla.com>
Wed, 08 Mar 2017 23:36:25 -0800
changeset 647590 1b44b5c7fe592a82c6f12c9fb5ff22c1f90dffc7
parent 647200 4e93516e92e58d166ad37b8544c3230024afb587
child 726564 eb30243541529ff47aaa89db0adc464a92a7fbbc
push id74468
push usermixedpuppy@gmail.com
push dateWed, 16 Aug 2017 16:18:18 +0000
reviewerskmag
bugs1341126
milestone57.0a1
Bug 1341126 implement open for browser/page/sidebar actions, r?kmag MozReview-Commit-ID: 5r5aGpyPQ6W
browser/components/extensions/ext-browserAction.js
browser/components/extensions/ext-pageAction.js
browser/components/extensions/ext-sidebarAction.js
browser/components/extensions/schemas/browser_action.json
browser/components/extensions/schemas/page_action.json
browser/components/extensions/schemas/sidebar_action.json
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_openPanel.js
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -665,14 +665,19 @@ this.browserAction = class extends Exten
         },
 
         getBadgeBackgroundColor: function(details, callback) {
           let tab = getTab(details.tabId);
 
           let color = browserAction.getProperty(tab, "badgeBackgroundColor");
           return Promise.resolve(color || [0xd9, 0, 0, 255]);
         },
+
+        openPopup: function() {
+          let window = windowTracker.topWindow;
+          browserAction.triggerAction(window);
+        },
       },
     };
   }
 };
 
 global.browserActionFor = this.browserAction.for;
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -333,14 +333,19 @@ this.pageAction = class extends Extensio
         },
 
         getPopup(details) {
           let tab = tabTracker.getTab(details.tabId);
 
           let popup = pageAction.getProperty(tab, "popup");
           return Promise.resolve(popup);
         },
+
+        openPopup: function() {
+          let window = windowTracker.topWindow;
+          pageAction.triggerAction(window);
+        },
       },
     };
   }
 };
 
 global.pageActionFor = this.pageAction.for;
--- a/browser/components/extensions/ext-sidebarAction.js
+++ b/browser/components/extensions/ext-sidebarAction.js
@@ -341,16 +341,40 @@ this.sidebarAction = class extends Exten
    */
   triggerAction(window) {
     let {SidebarUI} = window;
     if (SidebarUI) {
       SidebarUI.toggle(this.id);
     }
   }
 
+  /**
+   * Opens this sidebar action for the given window.
+   *
+   * @param {ChromeWindow} window
+   */
+  open(window) {
+    let {SidebarUI} = window;
+    if (SidebarUI) {
+      SidebarUI.show(this.id);
+    }
+  }
+
+  /**
+   * Closes this sidebar action for the given window if this sidebar action is open.
+   *
+   * @param {ChromeWindow} window
+   */
+  close(window) {
+    let {SidebarUI} = window;
+    if (SidebarUI.isOpen && this.id == SidebarUI.currentID) {
+      SidebarUI.hide();
+    }
+  }
+
   getAPI(context) {
     let {extension} = context;
     const sidebarAction = this;
 
     function getTab(tabId) {
       if (tabId !== null) {
         return tabTracker.getTab(tabId);
       }
@@ -401,14 +425,24 @@ this.sidebarAction = class extends Exten
         },
 
         getPanel(details) {
           let nativeTab = getTab(details.tabId);
 
           let panel = sidebarAction.getProperty(nativeTab, "panel");
           return Promise.resolve(panel);
         },
+
+        open() {
+          let window = windowTracker.topWindow;
+          sidebarAction.open(window);
+        },
+
+        close() {
+          let window = windowTracker.topWindow;
+          sidebarAction.close(window);
+        },
       },
     };
   }
 };
 
 global.sidebarActionFor = this.sidebarAction.for;
--- a/browser/components/extensions/schemas/browser_action.json
+++ b/browser/components/extensions/schemas/browser_action.json
@@ -401,34 +401,20 @@
             "optional": true,
             "parameters": []
           }
         ]
       },
       {
         "name": "openPopup",
         "type": "function",
-        "description": "Opens the extension popup window in the active window but does not grant tab permissions.",
-        "unsupported": true,
-        "async": "callback",
-        "parameters": [
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "name": "popupView",
-                "type": "object",
-                "optional": true,
-                "description": "JavaScript 'window' object for the popup window if it was succesfully opened.",
-                "additionalProperties": { "type": "any" }
-              }
-            ]
-          }
-        ]
+        "requireUserInput": true,
+        "description": "Opens the extension popup window in the active window.",
+        "async": true,
+        "parameters": []
       }
     ],
     "events": [
       {
         "name": "onClicked",
         "type": "function",
         "description": "Fired when a browser action icon is clicked.  This event will not fire if the browser action has a popup.",
         "parameters": [
--- a/browser/components/extensions/schemas/page_action.json
+++ b/browser/components/extensions/schemas/page_action.json
@@ -210,16 +210,24 @@
             "parameters": [
               {
                 "name": "result",
                 "type": "string"
               }
             ]
           }
         ]
+      },
+      {
+        "name": "openPopup",
+        "type": "function",
+        "requireUserInput": true,
+        "description": "Opens the extension page action in the active window.",
+        "async": true,
+        "parameters": []
       }
     ],
     "events": [
       {
         "name": "onClicked",
         "type": "function",
         "description": "Fired when a page action icon is clicked.  This event will not fire if the page action has a popup.",
         "parameters": [
--- a/browser/components/extensions/schemas/sidebar_action.json
+++ b/browser/components/extensions/schemas/sidebar_action.json
@@ -176,12 +176,28 @@
               "tabId": {
                 "type": "integer",
                 "optional": true,
                 "description": "Specify the tab to get the sidebar from. If no tab is specified, the non-tab-specific sidebar is returned."
               }
             }
           }
         ]
+      },
+      {
+        "name": "open",
+        "type": "function",
+        "requireUserInput": true,
+        "description": "Opens the extension sidebar in the active window.",
+        "async": true,
+        "parameters": []
+      },
+      {
+        "name": "close",
+        "type": "function",
+        "requireUserInput": true,
+        "description": "Closes the extension sidebar in the active window if the sidebar belongs to the extension.",
+        "async": true,
+        "parameters": []
       }
     ]
   }
 ]
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -78,16 +78,17 @@ skip-if = true # bug 1382487
 [browser_ext_getViews.js]
 [browser_ext_identity_indication.js]
 [browser_ext_incognito_views.js]
 [browser_ext_incognito_popup.js]
 [browser_ext_lastError.js]
 [browser_ext_menus.js]
 [browser_ext_omnibox.js]
 skip-if = debug || asan # Bug 1354681
+[browser_ext_openPanel.js]
 [browser_ext_optionsPage_browser_style.js]
 [browser_ext_optionsPage_privileges.js]
 [browser_ext_pageAction_context.js]
 [browser_ext_pageAction_contextMenu.js]
 [browser_ext_pageAction_popup.js]
 [browser_ext_pageAction_popup_resize.js]
 [browser_ext_pageAction_simple.js]
 [browser_ext_pageAction_telemetry.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_openPanel.js
@@ -0,0 +1,139 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(async function test_openPopup_requires_user_interaction() {
+  const {GlobalManager} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+  async function backgroundScript() {
+    browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tabInfo) => {
+      if (changeInfo.status != "complete") {
+        return;
+      }
+      await browser.pageAction.show(tabId);
+
+      await browser.test.assertRejects(
+        browser.pageAction.openPopup(),
+        "pageAction.openPopup may only be called from a user input handler",
+        "The error is informative.");
+      await browser.test.assertRejects(
+        browser.browserAction.openPopup(),
+        "browserAction.openPopup may only be called from a user input handler",
+        "The error is informative.");
+      await browser.test.assertRejects(
+        browser.sidebarAction.open(),
+        "sidebarAction.open may only be called from a user input handler",
+        "The error is informative.");
+      await browser.test.assertRejects(
+        browser.sidebarAction.close(),
+        "sidebarAction.close may only be called from a user input handler",
+        "The error is informative.");
+
+      browser.runtime.onMessage.addListener(async msg => {
+        browser.test.assertEq(msg, "from-panel", "correct message received");
+        browser.test.sendMessage("panel-opened");
+      });
+
+      browser.test.sendMessage("ready");
+    });
+    browser.tabs.create({url: "tab.html"});
+  }
+
+  let extensionData = {
+    background: backgroundScript,
+    manifest: {
+      "browser_action": {
+        "default_popup": "panel.html",
+      },
+      "page_action": {
+        "default_popup": "panel.html",
+      },
+      "sidebar_action": {
+        "default_panel": "panel.html",
+      },
+    },
+
+    files: {
+      "tab.html": `
+      <!DOCTYPE html>
+      <html><head><meta charset="utf-8"></head><body>
+      <button id="openBrowserAction">openBrowserAction</button>
+      <button id="openPageAction">openPageAction</button>
+      <button id="openSidebarAction">openSidebarAction</button>
+      <button id="closeSidebarAction">closeSidebarAction</button>
+      <script src="tab.js"></script>
+      </body></html>
+      `,
+      "panel.html": `
+      <!DOCTYPE html>
+      <html><head><meta charset="utf-8"></head><body>
+      <script src="panel.js"></script>
+      </body></html>
+      `,
+      "tab.js": function() {
+        document.getElementById("openBrowserAction").addEventListener("click", () => {
+          browser.browserAction.openPopup();
+        }, {once: true});
+        document.getElementById("openPageAction").addEventListener("click", () => {
+          browser.pageAction.openPopup();
+        }, {once: true});
+        document.getElementById("openSidebarAction").addEventListener("click", () => {
+          browser.sidebarAction.open();
+        }, {once: true});
+        document.getElementById("closeSidebarAction").addEventListener("click", () => {
+          browser.sidebarAction.close();
+        }, {once: true});
+      },
+      "panel.js": function() {
+        browser.runtime.sendMessage("from-panel");
+      },
+    },
+  };
+
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+
+  async function click(id) {
+    let open = extension.awaitMessage("panel-opened");
+    await BrowserTestUtils.synthesizeMouseAtCenter(id, {}, gBrowser.selectedBrowser);
+    return open;
+  }
+
+  function testActiveTab(extension, expected) {
+    let ext = GlobalManager.extensionMap.get(extension.id);
+    is(ext.tabManager.hasActiveTabPermission(gBrowser.selectedTab), expected,
+       "activeTab permission is correct");
+  }
+
+  await extension.startup();
+  await extension.awaitMessage("ready");
+
+  await click("#openBrowserAction");
+  testActiveTab(extension, false);
+  closeBrowserAction(extension);
+  await new Promise(resolve => setTimeout(resolve, 0));
+
+  await click("#openPageAction");
+  closePageAction(extension);
+  await new Promise(resolve => setTimeout(resolve, 0));
+
+  await click("#openSidebarAction");
+
+  await BrowserTestUtils.synthesizeMouseAtCenter("#closeSidebarAction", {}, gBrowser.selectedBrowser);
+  await BrowserTestUtils.waitForCondition(() => !SidebarUI.isOpen);
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  await extension.unload();
+
+  extensionData.manifest.permissions = ["activeTab"];
+  extension = ExtensionTestUtils.loadExtension(extensionData);
+  await extension.startup();
+  await extension.awaitMessage("ready");
+
+  await click("#openBrowserAction");
+  testActiveTab(extension, true);
+  closeBrowserAction(extension);
+  await new Promise(resolve => setTimeout(resolve, 0));
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  await extension.unload();
+});