Bug 1499617 follow-up - Folder tab (3-pane) WebExtensions API improvements; r=Fallen
authorGeoff Lankow <geoff@darktrojan.net>
Mon, 07 Jan 2019 21:54:53 +1300
changeset 34134 f8ef458411f47cdc8d7b39dc4ccbf4ab0afaf546
parent 34133 f2f995d7cf9ae66566e1876d71fb9c69111794ad
child 34135 b5994bd3ba7619d27bbae69c8243911afb2e1583
push id389
push userclokep@gmail.com
push dateMon, 18 Mar 2019 19:01:53 +0000
reviewersFallen
bugs1499617
Bug 1499617 follow-up - Folder tab (3-pane) WebExtensions API improvements; r=Fallen
mail/components/extensions/parent/ext-mail.js
mail/components/extensions/parent/ext-mailTabs.js
mail/components/extensions/schemas/mailTabs.json
mail/components/extensions/schemas/tabs.json
mail/components/extensions/test/browser/browser_ext_mailTabs.js
mail/components/extensions/test/browser/browser_ext_menus.js
mail/components/extensions/test/browser/browser_ext_quickFilter.js
--- a/mail/components/extensions/parent/ext-mail.js
+++ b/mail/components/extensions/parent/ext-mail.js
@@ -510,30 +510,30 @@ class Tab extends TabBase {
     if (nativeTab.localName == "tab") {
       let tabmail = nativeTab.ownerDocument.getElementById("tabmail");
       nativeTab = tabmail._getTabContextForTabbyThing(nativeTab)[1];
     }
     super(extension, nativeTab, id);
   }
 
   /** Returns true if this tab is a 3-pane tab. */
-  get isMail3Pane() {
+  get mailTab() {
     return this.nativeTab.mode.type == "folder";
   }
 
   /** Overrides the matches function to enable querying for 3-pane tabs. */
   matches(queryInfo, context) {
     let result = super.matches(queryInfo, context);
-    return result && (!queryInfo.isMail3Pane || this.isMail3Pane);
+    return result && (!queryInfo.mailTab || this.mailTab);
   }
 
-  /** Adds the isMail3Pane property and removes some useless properties from a tab object. */
+  /** Adds the mailTab property and removes some useless properties from a tab object. */
   convert(fallback) {
     let result = super.convert(fallback);
-    result.isMail3Pane = this.isMail3Pane;
+    result.mailTab = this.mailTab;
 
     // These properties are not useful to Thunderbird extensions and are not returned.
     for (let key of [
       "attention",
       "audible",
       "discarded",
       "hidden",
       "incognito",
--- a/mail/components/extensions/parent/ext-mailTabs.js
+++ b/mail/components/extensions/parent/ext-mailTabs.js
@@ -5,17 +5,21 @@
 ChromeUtils.defineModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "MailServices", "resource:///modules/MailServices.jsm");
 ChromeUtils.defineModuleGetter(this, "QuickFilterManager",
                                "resource:///modules/QuickFilterManager.jsm");
 
 const LAYOUTS = ["standard", "wide", "vertical"];
 // From nsIMsgDBView.idl
 const SORT_TYPE_MAP = new Map(
-  Object.keys(Ci.nsMsgViewSortType).map(key => [Ci.nsMsgViewSortType[key], key])
+  Object.keys(Ci.nsMsgViewSortType).map(key => {
+    // Change "byFoo" to "foo".
+    let shortKey = key[2].toLowerCase() + key.substring(3);
+    return [Ci.nsMsgViewSortType[key], shortKey];
+  })
 );
 const SORT_ORDER_MAP = new Map(
   Object.keys(Ci.nsMsgViewSortOrder).map(key => [Ci.nsMsgViewSortOrder[key], key])
 );
 
 /**
  * Converts a mail tab to a simle object for use in messages.
  * @return {Object}
@@ -132,45 +136,39 @@ this.mailTabs = class extends ExtensionA
       let tab;
       if (tabId) {
         tab = tabManager.get(tabId);
       } else {
         tab = tabManager.wrapTab(tabTracker.activeTab);
         tabId = tab.id;
       }
 
-      if (tab && tab.isMail3Pane) {
+      if (tab && tab.mailTab) {
         return tab;
       }
       throw new ExtensionError(`Invalid mail tab ID: ${tabId}`);
     }
 
     return {
       mailTabs: {
-        async getAll() {
+        async query({ active, currentWindow, lastFocusedWindow, windowId }) {
           return Array.from(tabManager.query({
+            active,
+            currentWindow,
+            lastFocusedWindow,
+            mailTab: true,
+            windowId,
+
             // All of these are needed for tabManager to return every tab we want.
-            "currentWindow": null,
-            "index": null,
-            "isMail3Pane": true,
-            "lastFocusedWindow": null,
-            "screen": null,
-            "windowId": null,
-            "windowType": null,
+            index: null,
+            screen: null,
+            windowType: null,
           }, context), (tab) => convertMailTab(tab, context));
         },
 
-        async getCurrent() {
-          let tab = tabManager.wrapTab(tabTracker.activeTab);
-          if (!tab || !tab.isMail3Pane) {
-            return null;
-          }
-          return convertMailTab(tab, context);
-        },
-
         async update(tabId, args) {
           let tab = getTabOrActive(tabId);
           let window = tab.window;
 
           let {
             displayedFolder,
             layout,
             folderPaneVisible,
@@ -192,20 +190,24 @@ this.mailTabs = class extends ExtensionA
                   `"${displayedFolder.accountId}" not found.`
                 );
               }
             } else {
               tab.nativeTab.folderDisplay.showFolderUri(uri);
             }
           }
 
-          if (sortType && sortType in Ci.nsMsgViewSortType &&
-              sortOrder && sortOrder in Ci.nsMsgViewSortOrder) {
-            tab.nativeTab.folderDisplay.view.sort(Ci.nsMsgViewSortType[sortType],
-                                                  Ci.nsMsgViewSortOrder[sortOrder]);
+          if (sortType) {
+            // Change "foo" to "byFoo".
+            sortType = "by" + sortType[0].toUpperCase() + sortType.substring(1);
+            if (sortType in Ci.nsMsgViewSortType &&
+                sortOrder && sortOrder in Ci.nsMsgViewSortOrder) {
+              tab.nativeTab.folderDisplay.view.sort(Ci.nsMsgViewSortType[sortType],
+                                                    Ci.nsMsgViewSortOrder[sortOrder]);
+            }
           }
 
           // Layout applies to all folder tabs.
           if (layout) {
             Services.prefs.setIntPref("mail.pane_config.dynamic", LAYOUTS.indexOf(layout));
           }
 
           if (typeof folderPaneVisible == "boolean") {
--- a/mail/components/extensions/schemas/mailTabs.json
+++ b/mail/components/extensions/schemas/mailTabs.json
@@ -1,30 +1,11 @@
 [
   {
-    "namespace": "manifest",
-    "types": [
-      {
-        "$extend": "OptionalPermission",
-        "choices": [
-          {
-            "type": "string",
-            "enum": [
-              "mailTabs"
-            ]
-          }
-        ]
-      }
-    ]
-  },
-  {
     "namespace": "mailTabs",
-    "permissions": [
-      "mailTabs"
-    ],
     "types": [
       {
         "id": "QuickFilterTagsDetail",
         "type": "object",
         "properties": {
           "tags": {
             "type": "object",
             "description": "Object keys are tags to filter on, values are <code>true</code> if the message must have the tag, or <code>false</code> if it must not have the tag. For a list of available tags, call the :ref:`messages.listTags` method.",
@@ -72,39 +53,60 @@
             "description": "Shows messages where <var>text</var> matches the message body.",
             "optional": true
           }
         }
       }
     ],
     "functions": [
       {
-        "name": "getAll",
+        "name": "query",
         "type": "function",
-        "description": "Returns an array of all mail tabs in all windows.",
+        "description": "Gets all mail tabs that have the specified properties, or all tabs if no properties are specified.",
         "async": true,
-        "parameters": []
-      },
-      {
-        "name": "getCurrent",
-        "type": "function",
-        "description": "Returns the current mail tab in the most recent window, or throws an exception if the current tab is not a mail tab.",
-        "async": true,
-        "parameters": []
+        "parameters": [
+          {
+            "type": "object",
+            "name": "queryInfo",
+            "properties": {
+              "active": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the tabs are active in their windows."
+              },
+              "currentWindow": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the tabs are in the current window."
+              },
+              "lastFocusedWindow": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the tabs are in the last focused window."
+              },
+              "windowId": {
+                "type": "integer",
+                "optional": true,
+                "minimum": -2,
+                "description": "The ID of the parent window, or $(ref:windows.WINDOW_ID_CURRENT) for the $(topic:current-window)[current window]."
+              }
+            }
+          }
+        ]
       },
       {
         "name": "update",
         "type": "function",
         "description": "Modifies the properties of a mail tab. Properties that are not specified in <var>updateProperties</var> are not modified.",
         "async": true,
         "parameters": [
           {
             "name": "tabId",
             "type": "integer",
-            "description": "Defaults to the selected tab of the current window.",
+            "description": "Defaults to the active tab of the current window.",
             "optional": true,
             "minimum": 1
           },
           {
             "name": "updateProperties",
             "type": "object",
             "properties": {
               "displayedFolder": {
@@ -124,36 +126,36 @@
                   }
                 }
               },
               "sortType": {
                 "type": "string",
                 "description": "Sorts the list of messages. <var>sortOrder</var> must also be given.",
                 "optional": true,
                 "enum": [
-                  "byNone",
-                  "byDate",
-                  "bySubject",
-                  "byAuthor",
-                  "byId",
-                  "byThread",
-                  "byPriority",
-                  "byStatus",
-                  "bySize",
-                  "byFlagged",
-                  "byUnread",
-                  "byRecipient",
-                  "byLocation",
-                  "byTags",
-                  "byJunkStatus",
-                  "byAttachments",
-                  "byAccount",
-                  "byCustom",
-                  "byReceived",
-                  "byCorrespondent"
+                  "none",
+                  "date",
+                  "subject",
+                  "author",
+                  "id",
+                  "thread",
+                  "priority",
+                  "status",
+                  "size",
+                  "flagged",
+                  "unread",
+                  "recipient",
+                  "location",
+                  "tags",
+                  "junkStatus",
+                  "attachments",
+                  "account",
+                  "custom",
+                  "received",
+                  "correspondent"
                 ]
               },
               "sortOrder": {
                 "type": "string",
                 "description": "Sorts the list of messages. <var>sortType</var> must also be given.",
                 "optional": true,
                 "enum": [
                   "none",
@@ -189,32 +191,32 @@
         "name": "getSelectedMessages",
         "type": "function",
         "description": "Lists the selected messages in the current folder. A messages permission is required to do this.",
         "async": true,
         "parameters": [
           {
             "name": "tabId",
             "type": "integer",
-            "description": "Defaults to the selected tab of the current window.",
+            "description": "Defaults to the active tab of the current window.",
             "optional": true,
             "minimum": 1
           }
         ]
       },
       {
         "name": "setQuickFilter",
         "type": "function",
         "description": "Sets the Quick Filter user interface based on the options specified.",
         "async": true,
         "parameters": [
           {
             "name": "tabId",
             "type": "integer",
-            "description": "Defaults to the selected tab of the current window.",
+            "description": "Defaults to the active tab of the current window.",
             "optional": true,
             "minimum": 1
           },
           {
             "name": "properties",
             "type": "object",
             "properties": {
               "show": {
--- a/mail/components/extensions/schemas/tabs.json
+++ b/mail/components/extensions/schemas/tabs.json
@@ -34,17 +34,17 @@
           "highlighted": {"type": "boolean", "description": "Whether the tab is highlighted. Works as an alias of active"},
           "active": {"type": "boolean", "description": "Whether the tab is active in its window. (Does not necessarily mean the window is focused.)"},
           "url": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL the tab is displaying. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."},
           "title": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The title of the tab. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."},
           "favIconUrl": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL of the tab's favicon. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission. It may also be an empty string if the tab is loading."},
           "status": {"type": "string", "optional": true, "description": "Either <em>loading</em> or <em>complete</em>."},
           "width": {"type": "integer", "optional": true, "description": "The width of the tab in pixels."},
           "height": {"type": "integer", "optional": true, "description": "The height of the tab in pixels."},
-          "isMail3Pane": {"type": "boolean", "optional": true, "description": "Whether the tab is a 3-pane tab."}
+          "mailTab": {"type": "boolean", "optional": true, "description": "Whether the tab is a 3-pane tab."}
         }
       },
       {
         "id": "TabStatus",
         "type": "string",
         "enum": ["loading", "complete"],
         "description": "Whether the tabs have completed loading."
       },
@@ -222,17 +222,17 @@
         "type": "function",
         "description": "Gets all tabs that have the specified properties, or all tabs if no properties are specified.",
         "async": "callback",
         "parameters": [
           {
             "type": "object",
             "name": "queryInfo",
             "properties": {
-              "isMail3Pane": {
+              "mailTab": {
                 "type": "boolean",
                 "optional": true,
                 "description": "Whether the tab is a Thunderbird 3-pane tab."
               },
               "active": {
                 "type": "boolean",
                 "optional": true,
                 "description": "Whether the tabs are active in their windows."
--- a/mail/components/extensions/test/browser/browser_ext_mailTabs.js
+++ b/mail/components/extensions/test/browser/browser_ext_mailTabs.js
@@ -53,17 +53,17 @@ add_task(async function test_update() {
           assertDeepEqual(expected[key], actual[key]);
           continue;
         }
         browser.test.assertEq(expected[key], actual[key]);
       }
     }
 
     async function checkCurrent(expected) {
-      let current = await browser.mailTabs.getCurrent();
+      let [current] = await browser.mailTabs.query({ active: true, currentWindow: true });
       assertDeepEqual(expected, current);
     }
 
     let [accountId] = await awaitMessage();
     let { folders } = await browser.accounts.get(accountId);
     let state = {
       sortType: null,
       sortOrder: null,
@@ -75,33 +75,33 @@ add_task(async function test_update() {
         name: "Local Folders",
         path: "/",
       },
     };
     await checkCurrent(state);
     await awaitMessage("checkRealLayout", state);
 
     browser.mailTabs.update({ displayedFolder: folders[0] });
-    state.sortType = "byDate";
+    state.sortType = "date";
     state.sortOrder = "ascending";
     state.folderPaneVisible = true;
     state.messagePaneVisible = true;
     state.displayedFolder = folders[0];
     await checkCurrent(state);
     await awaitMessage("checkRealLayout", state);
     await awaitMessage("checkRealSort", state);
 
     state.sortOrder = "descending";
-    for (let value of ["byDate", "bySubject", "byAuthor"]) {
+    for (let value of ["date", "subject", "author"]) {
       await browser.mailTabs.update({ sortType: value, sortOrder: "descending" });
       state.sortType = value;
       await awaitMessage("checkRealSort", state);
     }
     state.sortOrder = "ascending";
-    for (let value of ["byAuthor", "bySubject", "byDate"]) {
+    for (let value of ["author", "subject", "date"]) {
       await browser.mailTabs.update({ sortType: value, sortOrder: "ascending" });
       state.sortType = value;
       await awaitMessage("checkRealSort", state);
     }
 
     for (let key of ["folderPaneVisible", "messagePaneVisible"]) {
       for (let value of [false, true]) {
         await browser.mailTabs.update({ [key]: value });
@@ -120,33 +120,34 @@ add_task(async function test_update() {
     let selectedMessages = await browser.mailTabs.getSelectedMessages();
     browser.test.assertEq(0, selectedMessages.length);
 
     browser.test.notifyPass("mailTabs");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     background,
-    manifest: { permissions: ["accountsRead", "mailTabs", "messagesRead"] },
+    manifest: { permissions: ["accountsRead", "messagesRead"] },
   });
 
   extension.onMessage("checkRealLayout", (expected) => {
     let intValue = ["standard", "wide", "vertical"].indexOf(expected.layout);
     is(Services.prefs.getIntPref("mail.pane_config.dynamic"), intValue);
     if (typeof expected.messagePaneVisible == "boolean") {
       is(document.getElementById("messagepaneboxwrapper").collapsed, !expected.messagePaneVisible);
     }
     if (typeof expected.folderPaneVisible == "boolean") {
       is(document.getElementById("folderPaneBox").collapsed, !expected.folderPaneVisible);
     }
     extension.sendMessage();
   });
 
   extension.onMessage("checkRealSort", (expected) => {
     for (let [columnId, sortType] of window.gFolderDisplay.COLUMNS_MAP) {
+      sortType = sortType[2].toLowerCase() + sortType.substring(3);
       if (sortType == expected.sortType) {
         let column = document.getElementById(columnId);
         is(column.getAttribute("sortDirection"), expected.sortOrder);
         extension.sendMessage();
         return;
       }
     }
     throw new Error("This test should never get here.");
@@ -168,17 +169,17 @@ add_task(async function test_events() {
           browser.test.onMessage.removeListener(listener);
           resolve(args);
         });
       });
     }
 
     let [accountId] = await awaitMessage();
 
-    let current = await browser.mailTabs.getCurrent();
+    let [current] = await browser.mailTabs.query({ active: true, currentWindow: true });
     browser.test.assertEq(accountId, current.displayedFolder.accountId);
     browser.test.assertEq("/", current.displayedFolder.path);
 
     async function selectFolder(newFolderPath) {
       return new Promise(resolve => {
         browser.mailTabs.onDisplayedFolderChanged.addListener(function listener(tabId, folder) {
           browser.mailTabs.onDisplayedFolderChanged.removeListener(listener);
           browser.test.assertEq(current.id, tabId);
@@ -235,17 +236,17 @@ add_task(async function test_events() {
   let folderMap = new Map([
     ["/", rootFolder],
     ["/Trash", subFolders[0]],
     ["/Unsent Messages", subFolders[1]],
   ]);
 
   let extension = ExtensionTestUtils.loadExtension({
     background,
-    manifest: { permissions: ["accountsRead", "mailTabs", "messagesRead"] },
+    manifest: { permissions: ["accountsRead", "messagesRead"] },
   });
 
   extension.onMessage("selectFolder", (newFolderPath) => {
     window.gFolderTreeView.selectFolder(folderMap.get(newFolderPath));
   });
 
   extension.onMessage("selectMessage", (newMessages) => {
     let allMessages = [...window.gFolderDisplay.displayedFolder.messages];
@@ -272,18 +273,18 @@ add_task(async function test_background_
           browser.test.sendMessage(messageToSend, ...sendArgs);
         }
       });
     }
 
     let [accountId] = await awaitMessage();
     let { folders } = await browser.accounts.get(accountId);
     let allTabs = await browser.tabs.query({});
-    let queryTabs = await browser.tabs.query({ isMail3Pane: true });
-    let allMailTabs = await browser.mailTabs.getAll();
+    let queryTabs = await browser.tabs.query({ mailTab: true });
+    let allMailTabs = await browser.mailTabs.query({});
 
     browser.test.assertEq(4, allTabs.length);
     browser.test.assertEq(2, queryTabs.length);
     browser.test.assertEq(2, allMailTabs.length);
 
     browser.test.assertEq(accountId, allMailTabs[0].displayedFolder.accountId);
     browser.test.assertEq("/", allMailTabs[0].displayedFolder.path);
 
@@ -306,17 +307,17 @@ add_task(async function test_background_
 
     // Should be in the same state, since we're updating a background tab.
     await awaitMessage("checkRealLayout", {
       messagePaneVisible: true,
       folderPaneVisible: true,
       displayedFolder: "/Trash",
     });
 
-    allMailTabs = await browser.mailTabs.getAll();
+    allMailTabs = await browser.mailTabs.query({});
     browser.test.assertEq(2, allMailTabs.length);
 
     browser.test.assertEq(accountId, allMailTabs[0].displayedFolder.accountId);
     browser.test.assertEq("/Unsent Messages", allMailTabs[0].displayedFolder.path);
 
     browser.test.assertEq(accountId, allMailTabs[1].displayedFolder.accountId);
     browser.test.assertEq("/Trash", allMailTabs[1].displayedFolder.path);
     browser.test.assertTrue(allMailTabs[1].active);
@@ -351,17 +352,17 @@ add_task(async function test_background_
       displayedFolder: "/Trash",
     });
 
     browser.test.notifyPass("mailTabs");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     background,
-    manifest: { permissions: ["accountsRead", "mailTabs", "tabs"] },
+    manifest: { permissions: ["accountsRead"] },
   });
 
   extension.onMessage("checkRealLayout", async (expected) => {
     is(document.getElementById("messagepaneboxwrapper").collapsed, !expected.messagePaneVisible);
     is(document.getElementById("folderPaneBox").collapsed, !expected.folderPaneVisible);
     is(window.gFolderTreeView.getSelectedFolders()[0].URI,
        account.incomingServer.serverURI + expected.displayedFolder);
     extension.sendMessage();
--- a/mail/components/extensions/test/browser/browser_ext_menus.js
+++ b/mail/components/extensions/test/browser/browser_ext_menus.js
@@ -111,30 +111,30 @@ add_task(async function test_thread_pane
   is(event.displayedFolder.path, "/Trash");
   is(event.selectedMessages.cursor, null);
   ok(!event.selectedFolder);
 
   await extension.unload();
 });
 
 add_task(async function test_tab() {
-  async function checkTabEvent(index, active, isMail3Pane) {
+  async function checkTabEvent(index, active, mailTab) {
     EventUtils.synthesizeMouseAtCenter(tabs[index], {type: "contextmenu"}, window);
 
     await BrowserTestUtils.waitForEvent(menu, "popupshown");
     ok(menu.querySelector("#test1_mochi_test-menuitem-_tab"));
     menu.hidePopup();
 
     let [event, tab] = await checkEvent(extension, ["tab"], ["tab"]);
     ok(!event.selectedFolder);
     ok(!event.displayedFolder);
     ok(!event.selectedMessages);
     is(tab.active, active);
     is(tab.index, index);
-    is(tab.isMail3Pane, isMail3Pane);
+    is(tab.mailTab, mailTab);
   }
 
   let extension = createExtension();
   await extension.startup();
 
   let tabmail = document.getElementById("tabmail");
   window.openContentTab("about:config");
   window.openContentTab("about:mozilla");
--- a/mail/components/extensions/test/browser/browser_ext_quickFilter.js
+++ b/mail/components/extensions/test/browser/browser_ext_quickFilter.js
@@ -55,17 +55,16 @@ add_task(async () => {
     browser.mailTabs.setQuickFilter({ tags: { mode: "all", tags: { "$label1": true, "$label2": false } } });
     await awaitMessage("checkVisible", 0, 6);
 
     browser.test.notifyPass("quickFilter");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     background,
-    manifest: { permissions: ["mailTabs"] },
   });
 
   extension.onMessage("checkVisible", async (...expected) => {
     // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
     await new Promise(r => setTimeout(r, 500));
 
     let actual = [];
     let dbView = window.gFolderDisplay.view.dbView;