Bug 1396017: Redact window titles without the appropriate tabs permissions. r=mixedpuppy
authorKris Maglione <maglione.k@gmail.com>
Fri, 01 Sep 2017 12:20:10 -0700
changeset 378878 9b9159079eea2092d5914e8f263135a79e92cfe5
parent 378877 cd4cb832958672a9b3d068d5f59cc946a2fe66f3
child 378879 8b92938b98d9a4830afe4e7e280bd00727c29e58
push id32443
push userarchaeopteryx@coole-files.de
push dateTue, 05 Sep 2017 09:41:20 +0000
treeherdermozilla-central@3ecda4678c49 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmixedpuppy
bugs1396017
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
@@ -718,17 +718,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() {
@@ -823,16 +823,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
@@ -650,16 +650,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
@@ -911,16 +911,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.
@@ -938,16 +950,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.