Bug 1396017: Redact window titles without the appropriate tabs permissions. r?mixedpuppy draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 01 Sep 2017 12:20:10 -0700
changeset 657659 33c4670a0eded6e0af4f931a1ac4cd57f6115995
parent 657212 2bf1d0e361ab1ad935b61796520b118064d33a39
child 729485 2ee4f0749ba260b92de952967ada765086de08b6
push id77586
push usermaglione.k@gmail.com
push dateFri, 01 Sep 2017 19:20:26 +0000
reviewersmixedpuppy
bugs1396017
milestone57.0a1
Bug 1396017: Redact window titles without the appropriate tabs permissions. r?mixedpuppy MozReview-Commit-ID: 2QJYvJlqt9l
browser/components/extensions/ext-browser.js
browser/components/extensions/test/browser/browser_ext_windows.js
mobile/android/components/extensions/ext-utils.js
toolkit/components/extensions/ext-tabs-base.js
--- a/browser/components/extensions/ext-browser.js
+++ b/browser/components/extensions/ext-browser.js
@@ -714,17 +714,17 @@ class Window extends WindowBase {
 
     if (options.width !== null || options.height !== null) {
       let width = options.width !== null ? options.width : window.outerWidth;
       let height = options.height !== null ? options.height : window.outerHeight;
       window.resizeTo(width, height);
     }
   }
 
-  get title() {
+  get _title() {
     return this.window.document.title;
   }
 
   setTitlePreface(titlePreface) {
     this.window.document.documentElement.setAttribute("titlepreface", titlePreface);
   }
 
   get focused() {
@@ -819,16 +819,22 @@ class Window extends WindowBase {
   * getTabs() {
     let {tabManager} = this.extension;
 
     for (let nativeTab of this.window.gBrowser.tabs) {
       yield tabManager.getWrapper(nativeTab);
     }
   }
 
+  get activeTab() {
+    let {tabManager} = this.extension;
+
+    return tabManager.getWrapper(this.window.gBrowser.selectedTab);
+  }
+
   /**
    * Converts session store data to an object compatible with the return value
    * of the convert() method, representing that data.
    *
    * @param {Extension} extension
    *        The extension for which to convert the data.
    * @param {Object} windowData
    *        Session store data for a closed window, as returned by
--- a/browser/components/extensions/test/browser/browser_ext_windows.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows.js
@@ -59,16 +59,19 @@ add_task(async function testWindowTitle(
                               "Window has the expected title text after update.");
         browser.test.sendMessage("updated", win);
       }
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     background,
+    manifest: {
+      permissions: ["tabs"],
+    },
   });
 
   await extension.startup();
   let {Management: {global: {windowTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
 
   async function createApiWin(options) {
     let promiseLoaded = BrowserTestUtils.waitForNewWindow(true, START_URL);
     extension.sendMessage("create", options);
@@ -134,16 +137,67 @@ add_task(async function testWindowTitle(
       text: NEW_TITLE,
     },
   };
   await updateWindow({titlePreface: PREFACE2}, apiWin, expected);
 
   await extension.unload();
 });
 
+// Test that the window title is only available with the correct tab
+// permissions.
+add_task(async function testWindowTitlePermissions() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(window.gBrowser, "http://example.com/")
+
+  let extension = ExtensionTestUtils.loadExtension({
+    async background() {
+      function awaitMessage(name) {
+        return new Promise(resolve => {
+          browser.test.onMessage.addListener(function listener(...msg) {
+            if (msg[0] === name) {
+              browser.test.onMessage.removeListener(listener);
+              resolve(msg[1]);
+            }
+          });
+        });
+      }
+
+      let window = await browser.windows.getCurrent();
+
+      browser.test.assertEq(undefined, window.title,
+                            "Window title should be null without tab permission");
+
+      browser.test.sendMessage("grant-activeTab");
+      let expectedTitle = await awaitMessage("title");
+
+      window = await browser.windows.getCurrent();
+      browser.test.assertEq(expectedTitle, window.title,
+                            "Window should have the expected title with tab permission granted");
+
+      await browser.test.notifyPass("window-title-permissions");
+    },
+    manifest: {
+      permissions: ["activeTab"],
+      browser_action: {},
+    },
+  });
+
+  await extension.startup();
+
+  await extension.awaitMessage("grant-activeTab");
+  await clickBrowserAction(extension);
+  extension.sendMessage("title", document.title);
+
+  await extension.awaitFinish("window-title-permissions");
+
+  await extension.unload();
+
+  await BrowserTestUtils.removeTab(tab);
+});
+
 add_task(async function testInvalidWindowId() {
   let extension = ExtensionTestUtils.loadExtension({
     async background() {
       await browser.test.assertRejects(
         // Assuming that this windowId does not exist.
         browser.windows.get(123456789),
         /Invalid window/,
         "Should receive invalid window");
--- a/mobile/android/components/extensions/ext-utils.js
+++ b/mobile/android/components/extensions/ext-utils.js
@@ -524,16 +524,22 @@ class Window extends WindowBase {
 
   * getTabs() {
     let {tabManager} = this.extension;
 
     for (let nativeTab of this.window.BrowserApp.tabs) {
       yield tabManager.getWrapper(nativeTab);
     }
   }
+
+  get activeTab() {
+    let {tabManager} = this.extension;
+
+    return tabManager.getWrapper(this.window.BrowserApp.selectedTab);
+  }
 }
 
 Object.assign(global, {Tab, TabContext, Window});
 
 class TabManager extends TabManagerBase {
   get(tabId, default_ = undefined) {
     let nativeTab = tabTracker.getTab(tabId, default_);
 
--- a/toolkit/components/extensions/ext-tabs-base.js
+++ b/toolkit/components/extensions/ext-tabs-base.js
@@ -910,16 +910,28 @@ class WindowBase {
   get state() {
     throw new Error("Not implemented");
   }
 
   set state(state) {
     throw new Error("Not implemented");
   }
 
+  /**
+   * @property {nsIURI | null} title
+   *        Returns the current title of this window if the extension has permission
+   *        to read it, or null otherwise.
+   *        @readonly
+   */
+  get title() {
+    if (this.activeTab.hasTabPermission) {
+      return this._title;
+    }
+  }
+
   // The JSDoc validator does not support @returns tags in abstract functions or
   // star functions without return statements.
   /* eslint-disable valid-jsdoc */
   /**
    * Returns the window state of the given window.
    *
    * @param {DOMWindow} window
    *        The window for which to return a state.
@@ -937,16 +949,23 @@ class WindowBase {
   /**
    * Returns an iterator of TabBase objects for each tab in this window.
    *
    * @returns {Iterator<TabBase>}
    */
   getTabs() {
     throw new Error("Not implemented");
   }
+
+  /**
+   * @property {TabBase} The window's currently active tab.
+   */
+  get activeTab() {
+    throw new Error("Not implemented");
+  }
   /* eslint-enable valid-jsdoc */
 }
 
 Object.assign(WindowBase, {WINDOW_ID_NONE, WINDOW_ID_CURRENT});
 
 /**
  * The parameter type of "tab-attached" events, which are emitted when a
  * pre-existing tab is attached to a new window.