Bug 1575693 - Fix browser.windows events. r=mkmelin a=jorgk
authorGeoff Lankow <geoff@darktrojan.net>
Fri, 23 Aug 2019 10:30:29 +0200
changeset 36188 b7c5e0ad60a05e6efc93b697549f9e03eb9d3fdd
parent 36187 3f2d30df82df3291b31d74ab1f2b790e194c2e69
child 36189 07ec429a494851508d76355c3337e03a28c07adc
push id392
push userclokep@gmail.com
push dateMon, 02 Sep 2019 20:17:19 +0000
reviewersmkmelin, jorgk
bugs1575693
Bug 1575693 - Fix browser.windows events. r=mkmelin a=jorgk
mail/components/extensions/parent/.eslintrc.js
mail/components/extensions/parent/ext-mail.js
mail/components/extensions/parent/ext-windows.js
mail/components/extensions/test/browser/browser.ini
mail/components/extensions/test/browser/browser_ext_windows.js
--- a/mail/components/extensions/parent/.eslintrc.js
+++ b/mail/components/extensions/parent/.eslintrc.js
@@ -31,17 +31,16 @@ module.exports = {
     "isDefaultCookieStoreId": true,
     "isPrivateCookieStoreId": true,
     "isValidCookieStoreId": true,
 
     // These are defined in ext-mail.js.
     "ExtensionError": true,
     "Tab": true,
     "Window": true,
-    "WindowEventManager": true,
     "convertFolder": true,
     "convertMessage": true,
     "folderPathToURI": true,
     "folderURIToPath": true,
     "getTabBrowser": true,
     "makeWidgetId": true,
     "messageListTracker": true,
     "messageTracker": true,
--- a/mail/components/extensions/parent/ext-mail.js
+++ b/mail/components/extensions/parent/ext-mail.js
@@ -172,48 +172,16 @@ class WindowTracker extends WindowTracke
       }
     }
 
     return win;
   }
 }
 
 /**
- * An event manager API provider which listens for a DOM event in any mail
- * window, and calls the given listener function whenever an event is received.
- * That listener function receives a `fire` object, which it can use to dispatch
- * events to the extension, and a DOM event object.
- *
- * @param {BaseContext} context
- *        The extension context which the event manager belongs to.
- * @param {string} name
- *        The API name of the event manager, e.g.,"runtime.onMessage".
- * @param {string} event
- *        The name of the DOM event to listen for.
- * @param {function} listener
- *        The listener function to call when a DOM event is received.
- */
-global.WindowEventManager = class extends EventManager {
-  constructor({ context, name, event, listener }) {
-    super({
-      context,
-      name,
-      listener: (fire) => {
-        let listener2 = listener.bind(null, fire);
-
-        windowTracker.addListener(event, listener2);
-        return () => {
-          windowTracker.removeListener(event, listener2);
-        };
-      },
-    });
-  }
-};
-
-/**
  * Tracks the opening and closing of tabs and maps them between their numeric WebExtension ID and
  * the native tab info objects.
  */
 class TabTracker extends TabTrackerBase {
   constructor() {
     super();
 
     this._tabs = new WeakMap();
--- a/mail/components/extensions/parent/ext-windows.js
+++ b/mail/components/extensions/parent/ext-windows.js
@@ -1,52 +1,93 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // The ext-* files are imported into the same scopes.
 /* import-globals-from ext-mail.js */
+
+/**
+ * An event manager API provider which listens for a DOM event in any browser
+ * window, and calls the given listener function whenever an event is received.
+ * That listener function receives a `fire` object, which it can use to dispatch
+ * events to the extension, and a DOM event object.
+ *
+ * @param {BaseContext} context
+ *        The extension context which the event manager belongs to.
+ * @param {string} name
+ *        The API name of the event manager, e.g.,"runtime.onMessage".
+ * @param {string} event
+ *        The name of the DOM event to listen for.
+ * @param {function} listener
+ *        The listener function to call when a DOM event is received.
+ *
+ * @returns {object} An injectable api for the new event.
+ */
+function WindowEventManager(context, name, event, listener) {
+  let register = fire => {
+    let listener2 = (window, ...args) => {
+      if (context.canAccessWindow(window)) {
+        listener(fire, window, ...args);
+      }
+    };
+
+    windowTracker.addListener(event, listener2);
+    return () => {
+      windowTracker.removeListener(event, listener2);
+    };
+  };
+
+  return new EventManager({ context, name, register }).api();
+}
+
 this.windows = class extends ExtensionAPI {
   getAPI(context) {
     const { extension } = context;
     const { windowManager } = extension;
 
     return {
       windows: {
-        onCreated: new WindowEventManager({
+        onCreated: WindowEventManager(
           context,
-          name: "windows.onCreated",
-          event: "domwindowopened",
-          listener: (fire, window) => {
+          "windows.onCreated",
+          "domwindowopened",
+          (fire, window) => {
             fire.async(windowManager.convert(window));
-          },
-        }).api(),
+          }
+        ),
 
-        onRemoved: new WindowEventManager({
+        onRemoved: WindowEventManager(
           context,
-          name: "windows.onRemoved",
-          event: "domwindowclosed",
-          listener: (fire, window) => {
+          "windows.onRemoved",
+          "domwindowclosed",
+          (fire, window) => {
             fire.async(windowTracker.getId(window));
-          },
-        }).api(),
+          }
+        ),
 
         onFocusChanged: new EventManager({
           context,
           name: "windows.onFocusChanged",
           register: fire => {
             // Keep track of the last windowId used to fire an onFocusChanged event
             let lastOnFocusChangedWindowId;
 
             let listener = event => {
               // Wait a tick to avoid firing a superfluous WINDOW_ID_NONE
-              // event when switching focus between two Firefox windows.
+              // event when switching focus between two Thunderbird windows.
               Promise.resolve().then(() => {
+                let windowId = Window.WINDOW_ID_NONE;
                 let window = Services.focus.activeWindow;
-                let windowId = window ? windowTracker.getId(window) : Window.WINDOW_ID_NONE;
+                if (window) {
+                  if (!context.canAccessWindow(window)) {
+                    return;
+                  }
+                  windowId = windowTracker.getId(window);
+                }
                 if (windowId !== lastOnFocusChangedWindowId) {
                   fire.async(windowId);
                   lastOnFocusChangedWindowId = windowId;
                 }
               });
             };
             windowTracker.addListener("focus", listener);
             windowTracker.addListener("blur", listener);
--- a/mail/components/extensions/test/browser/browser.ini
+++ b/mail/components/extensions/test/browser/browser.ini
@@ -17,8 +17,9 @@ tags = webextensions
 [browser_ext_commands_execute_browser_action.js]
 [browser_ext_commands_getAll.js]
 [browser_ext_commands_onCommand.js]
 [browser_ext_commands_update.js]
 [browser_ext_composeAction.js]
 [browser_ext_menus.js]
 [browser_ext_mailTabs.js]
 [browser_ext_quickFilter.js]
+[browser_ext_windows.js]
new file mode 100644
--- /dev/null
+++ b/mail/components/extensions/test/browser/browser_ext_windows.js
@@ -0,0 +1,135 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* globals MsgOpenNewWindowForFolder */
+
+let {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+
+add_task(async () => {
+  let extension = ExtensionTestUtils.loadExtension({
+    async background() {
+      let listener = {
+        waitingPromises: [],
+        waitForEvent() {
+          return new Promise((resolve) => {
+            listener.waitingPromises.push(resolve);
+          });
+        },
+        checkWaiting() {
+          if (listener.waitingPromises.length < 1) {
+            browser.test.fail("Unexpected event fired");
+          }
+        },
+        created(win) {
+          listener.checkWaiting();
+          listener.waitingPromises.shift()(["onCreated", win]);
+        },
+        focusChanged(windowId) {
+          listener.checkWaiting();
+          listener.waitingPromises.shift()(["onFocusChanged", windowId]);
+        },
+        removed(windowId) {
+          listener.checkWaiting();
+          listener.waitingPromises.shift()(["onRemoved", windowId]);
+        },
+      };
+      browser.windows.onCreated.addListener(listener.created);
+      browser.windows.onFocusChanged.addListener(listener.focusChanged);
+      browser.windows.onRemoved.addListener(listener.removed);
+
+      let firstWindow = await browser.windows.getCurrent();
+      browser.test.assertEq("normal", firstWindow.type);
+
+      let currentWindows = await browser.windows.getAll();
+      browser.test.assertEq(1, currentWindows.length);
+      browser.test.assertEq(firstWindow.id, currentWindows[0].id);
+
+      // Open a new mail window.
+
+      let createdWindowPromise = listener.waitForEvent();
+      let focusChangedPromise1 = listener.waitForEvent();
+      let focusChangedPromise2 = listener.waitForEvent();
+      let eventName, createdWindow, windowId;
+
+      browser.test.sendMessage("openWindow");
+      [eventName, createdWindow] = await createdWindowPromise;
+      browser.test.assertEq("onCreated", eventName);
+      browser.test.assertEq("normal", createdWindow.type);
+
+      [eventName, windowId] = await focusChangedPromise1;
+      browser.test.assertEq("onFocusChanged", eventName);
+      browser.test.assertEq(browser.windows.WINDOW_ID_NONE, windowId);
+
+      [eventName, windowId] = await focusChangedPromise2;
+      browser.test.assertEq("onFocusChanged", eventName);
+      browser.test.assertEq(createdWindow.id, windowId);
+
+      currentWindows = await browser.windows.getAll();
+      browser.test.assertEq(2, currentWindows.length);
+      browser.test.assertEq(firstWindow.id, currentWindows[0].id);
+      browser.test.assertEq(createdWindow.id, currentWindows[1].id);
+
+      // Focus the first window.
+
+      let platformInfo = await browser.runtime.getPlatformInfo();
+
+      let focusChangedPromise3;
+      if (["mac", "win"].includes(platformInfo.os)) {
+        // Mac and Windows don't fire this event. Pretend they do.
+        focusChangedPromise3 = Promise.resolve(["onFocusChanged", browser.windows.WINDOW_ID_NONE]);
+      } else {
+        focusChangedPromise3 = listener.waitForEvent();
+      }
+      let focusChangedPromise4 = listener.waitForEvent();
+
+      browser.test.sendMessage("switchWindows");
+      [eventName, windowId] = await focusChangedPromise3;
+      browser.test.assertEq("onFocusChanged", eventName);
+      browser.test.assertEq(browser.windows.WINDOW_ID_NONE, windowId);
+
+      [eventName, windowId] = await focusChangedPromise4;
+      browser.test.assertEq("onFocusChanged", eventName);
+      browser.test.assertEq(firstWindow.id, windowId);
+
+      // Close the first window.
+
+      let removedWindowPromise = listener.waitForEvent();
+
+      browser.test.sendMessage("closeWindow");
+      [eventName, windowId] = await removedWindowPromise;
+      browser.test.assertEq("onRemoved", eventName);
+      browser.test.assertEq(createdWindow.id, windowId);
+
+      currentWindows = await browser.windows.getAll();
+      browser.test.assertEq(1, currentWindows.length);
+      browser.test.assertEq(firstWindow.id, currentWindows[0].id);
+
+      browser.windows.onCreated.removeListener(listener.created);
+      browser.windows.onFocusChanged.removeListener(listener.focusChanged);
+      browser.windows.onRemoved.removeListener(listener.removed);
+
+      browser.test.notifyPass();
+    },
+  });
+
+  let account = createAccount();
+
+  await extension.startup();
+
+  await extension.awaitMessage("openWindow");
+  let newWindowPromise = BrowserTestUtils.domWindowOpened();
+  MsgOpenNewWindowForFolder(account.incomingServer.rootFolder.URI);
+  let newWindow = await newWindowPromise;
+
+  await extension.awaitMessage("switchWindows");
+  window.focus();
+
+  await extension.awaitMessage("closeWindow");
+  newWindow.close();
+
+  await extension.awaitFinish();
+  await extension.unload();
+
+  cleanUpAccount(account);
+});