Bug 1298215 - Add an extension API browser.downloads.showDownloads() to show about:downloads in a new tab; r?mixedpuppy draft
authorThomas Wisniewski <wisniewskit@gmail.com>
Thu, 03 May 2018 17:34:29 -0400
changeset 791325 5f5af32280f6d5f5e4c8e215f899bdff4ec59edf
parent 791312 ce7072c02389db590c487ca5863b7f00ce22336a
push id108780
push userwisniewskit@gmail.com
push dateThu, 03 May 2018 23:54:50 +0000
reviewersmixedpuppy
bugs1298215
milestone61.0a1
Bug 1298215 - Add an extension API browser.downloads.showDownloads() to show about:downloads in a new tab; r?mixedpuppy MozReview-Commit-ID: E2amcLwuZkL
toolkit/components/extensions/parent/.eslintrc.js
toolkit/components/extensions/parent/ext-downloads.js
toolkit/components/extensions/parent/ext-tabs-base.js
toolkit/components/extensions/schemas/downloads.json
toolkit/components/extensions/test/mochitest/chrome.ini
toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_showDownloads.html
--- a/toolkit/components/extensions/parent/.eslintrc.js
+++ b/toolkit/components/extensions/parent/.eslintrc.js
@@ -3,16 +3,17 @@
 module.exports = {
   "globals": {
     "CONTAINER_STORE": true,
     "DEFAULT_STORE": true,
     "EventEmitter": true,
     "EventManager": true,
     "InputEventManager": true,
     "PRIVATE_STORE": true,
+    "Tab": true,
     "TabBase": true,
     "TabManagerBase": true,
     "TabTrackerBase": true,
     "WindowBase": true,
     "WindowManagerBase": true,
     "WindowTrackerBase": true,
     "browserPaintedPromise": true,
     "browserStartupPromise": true,
--- a/toolkit/components/extensions/parent/ext-downloads.js
+++ b/toolkit/components/extensions/parent/ext-downloads.js
@@ -829,12 +829,16 @@ this.downloads = class extends Extension
               registerPromise.then(() => {
                 DownloadMap.off("erase", handler);
               });
             };
           },
         }).api(),
 
         onDeterminingFilename: ignoreEvent(context, "downloads.onDeterminingFilename"),
+
+        showDownloads(options) {
+          Tab.openActiveInternalUrl(context, "about:downloads", (options || {}).windowId, "downloads");
+        },
       },
     };
   }
 };
--- a/toolkit/components/extensions/parent/ext-tabs-base.js
+++ b/toolkit/components/extensions/parent/ext-tabs-base.js
@@ -747,16 +747,66 @@ class TabBase {
    *        The InjectDetails object, specifying what to remove, and from where.
    *
    * @returns {Promise}
    *        Resolves when the operation has completed.
    */
   removeCSS(context, details) {
     return this._execute(context, details, "css", "removeCSS").then(() => {});
   }
+
+  /**
+   * Opens an active tab to an internal link, like about:downloads.
+   *
+   * @param {BaseContext} context
+   *        The context through which to send the message.
+   * @param {string} url
+   *        The internal url to open.
+   * @param {integer} [windowId = null]
+   *        The windowId to open the new tab in.
+   * @param {string} requiredPermission
+   *        Optional. The permission the extension must have to access the URL.
+   *
+   * @returns {Promise}
+   * @static
+   */
+  static openActiveInternalUrl(context, url, windowId = null, requiredPermission = null) {
+    return new Promise((resolve, reject) => {
+      let {extension} = context;
+      if (requiredPermission && !extension.hasPermission(requiredPermission)) {
+        return Promise.reject({message: `No permission for ${requiredPermission}`});
+      }
+
+      let window = windowId !== null ?
+        global.windowTracker.getWindow(windowId, context) :
+        global.windowTracker.topWindow;
+
+      let browser = window.gBrowser || window.BrowserApp;
+      if (!browser) {
+        let obs = (finishedWindow, topic, data) => {
+          if (finishedWindow != window) {
+            return;
+          }
+          Services.obs.removeObserver(obs, "browser-delayed-startup-finished");
+          resolve(window.gBrowser || window.BrowserApp);
+        };
+        Services.obs.addObserver(obs, "browser-delayed-startup-finished");
+      } else {
+        resolve(browser);
+      }
+    }).then(browser => {
+      let options = {};
+
+      // Make sure things like about:blank and data: URIs never inherit,
+      // and instead always get a NullPrincipal.
+      options.disallowInheritPrincipal = true;
+
+      browser.selectedTab = browser.addTab(url, options);
+    });
+  }
 }
 
 defineLazyGetter(TabBase.prototype, "incognito", function() { return this._incognito; });
 
 // Note: These must match the values in windows.json.
 const WINDOW_ID_NONE = -1;
 const WINDOW_ID_CURRENT = -2;
 
--- a/toolkit/components/extensions/schemas/downloads.json
+++ b/toolkit/components/extensions/schemas/downloads.json
@@ -684,16 +684,35 @@
         "type": "function",
         "unsupported": true,
         "parameters": [
           {
             "name": "enabled",
             "type": "boolean"
           }
         ]
+      },
+      {
+        "name": "showDownloads",
+        "type": "function",
+        "description": "Open the download manager and give it focus.",
+        "parameters": [
+          {
+            "name": "options",
+            "optional": true,
+            "properties": {
+              "windowId": {
+                "description": "The window to open a new tab in.",
+                "optional": true,
+                "type": "integer"
+              }
+            },
+            "type": "object"
+          }
+        ]
       }
     ],
     "events": [
       {
         "description": "This event fires with the <a href='#type-DownloadItem'>DownloadItem</a> object when a download begins.",
         "name": "onCreated",
         "parameters": [
           {
--- a/toolkit/components/extensions/test/mochitest/chrome.ini
+++ b/toolkit/components/extensions/test/mochitest/chrome.ini
@@ -16,16 +16,17 @@ tags = webextensions in-process-webexten
 # NO NEW TESTS.  mochitest-chrome does not run under e10s, avoid adding new
 # tests here unless absolutely necessary.
 
 [test_chrome_ext_contentscript_data_uri.html]
 [test_chrome_ext_contentscript_telemetry.html]
 [test_chrome_ext_contentscript_unrecognizedprop_warning.html]
 [test_chrome_ext_downloads_open.html]
 [test_chrome_ext_downloads_saveAs.html]
+[test_chrome_ext_downloads_showDownloads.html]
 [test_chrome_ext_downloads_uniquify.html]
 [test_chrome_ext_permissions.html]
 skip-if = os == 'android' # Bug 1350559
 [test_chrome_ext_trackingprotection.html]
 [test_chrome_ext_webnavigation_resolved_urls.html]
 [test_chrome_ext_webrequest_background_events.html]
 [test_chrome_ext_webrequest_host_permissions.html]
 [test_chrome_ext_webrequest_mozextension.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_showDownloads.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+
+<html>
+<head>
+<meta charset="utf-8">
+  <script src="chrome://mochikit/content/tests/SimpleTest/AddTask.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+<script>
+"use strict";
+
+let extensionData = {
+  manifest: {
+    permissions: ["downloads", "webNavigation"],
+  },
+  background() {
+    let tabId;
+    browser.webNavigation.onCompleted.addListener(msg => {
+      if (msg.url === "about:downloads") {
+        browser.test.sendMessage("shown");
+        tabId = msg.tabId;
+      }
+    });
+
+    browser.test.onMessage.addListener(msg => {
+      if (msg === "showDownloads") {
+        browser.downloads.showDownloads();
+      } else if (msg === "close") {
+        browser.tabs.remove(tabId);
+        browser.test.sendMessage("closed");
+      }
+    });
+  },
+};
+
+add_task(async function test_webRequest_main_frame() {
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  await extension.startup();
+
+  extension.sendMessage("showDownloads");
+  await extension.awaitMessage("shown");
+  ok(true, "about:downloads shown");
+  extension.sendMessage("close");
+  await extension.awaitMessage("closed");
+
+  await extension.unload();
+});
+</script>
+</head>
+<body>
+<div id="test">Sample text</div>
+
+</body>
+</html>