Bug 1419195: Show items from WebExtensions in bookmarks sidebar context menu draft
authorPeter Simonyi <pts@petersimonyi.ca>
Sun, 15 Jul 2018 23:09:00 -0400
changeset 820279 fe49a91e61b20c5a5d991c448af1168dfbc1b5bf
parent 820131 5a8107262015714d2907a85abc24c847ad9b32d2
child 820280 af017452f04a9f7b0a8bcf5c4c06c51cc07c6ba2
push id116783
push userbmo:pts+bmo@petersimonyi.ca
push dateThu, 19 Jul 2018 11:47:49 +0000
bugs1419195
milestone63.0a1
Bug 1419195: Show items from WebExtensions in bookmarks sidebar context menu MozReview-Commit-ID: Bku5RM11TSC
browser/components/extensions/parent/ext-menus.js
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_contextMenus.js
--- a/browser/components/extensions/parent/ext-menus.js
+++ b/browser/components/extensions/parent/ext-menus.js
@@ -728,38 +728,75 @@ const menuTracker = {
       this.onWindowOpen(window);
     }
     windowTracker.addOpenListener(this.onWindowOpen);
   },
 
   unregister() {
     Services.obs.removeObserver(this, "on-build-contextmenu");
     for (const window of windowTracker.browserWindows()) {
-      for (const id of this.menuIds) {
-        const menu = window.document.getElementById(id);
-        menu.removeEventListener("popupshowing", this);
-      }
+      this.cleanupWindow(window);
     }
     windowTracker.removeOpenListener(this.onWindowOpen);
   },
 
   observe(subject, topic, data) {
     subject = subject.wrappedJSObject;
     gMenuBuilder.build(subject);
   },
 
   onWindowOpen(window) {
     for (const id of menuTracker.menuIds) {
       const menu = window.document.getElementById(id);
       menu.addEventListener("popupshowing", menuTracker);
     }
+
+    const browser = window.document.getElementById("sidebar");
+    browser.addEventListener("load", menuTracker.onSidebarLoad,
+                             {capture: true}); // Load events don't bubble.
+    if (window.sidebar.document.readyState === "complete") {
+      menuTracker.onSidebarLoad({currentTarget: browser});
+    }
+  },
+
+  cleanupWindow(window) {
+    for (const id of this.menuIds) {
+      const menu = window.document.getElementById(id);
+      menu.removeEventListener("popupshowing", this);
+    }
+    const browser = window.document.getElementById("sidebar");
+    browser.removeEventListener("load", this.onSidebarLoad, {capture: true});
+
+    const URL = window.document.getElementById("viewBookmarksSidebar")
+                               .getAttribute("sidebarurl");
+    if (window.sidebar.location.href === URL) {
+      const menu = window.sidebar.document.getElementById("placesContext");
+      menu.removeEventListener("popupshowing", this);
+    }
+  },
+
+  onSidebarLoad(event) {
+    // The listener is on the sidebar <browser>, so window is the regular
+    // browser window that contains the sidebar.
+    const window = event.currentTarget.ownerGlobal;
+    const URL = window.document.getElementById("viewBookmarksSidebar")
+                               .getAttribute("sidebarurl");
+    if (window.sidebar.location.href === URL) {
+      const menu = window.sidebar.document.getElementById("placesContext");
+      menu.addEventListener("popupshowing", menuTracker);
+    }
   },
 
   handleEvent(event) {
     const menu = event.target;
+
+    if (event.view === event.view.parent.sidebar) {
+      return menuTracker.handleSidebarCtxMenu(event);
+    }
+
     if (menu.id === "placesContext") {
       const trigger = menu.triggerNode;
       if (!trigger._placesNode) {
         return;
       }
 
       gMenuBuilder.build({
         menu,
@@ -774,16 +811,29 @@ const menuTracker = {
     }
     if (menu.id === "tabContextMenu") {
       const trigger = menu.triggerNode;
       const tab = trigger.localName === "tab" ? trigger : tabTracker.activeTab;
       const pageUrl = tab.linkedBrowser.currentURI.spec;
       gMenuBuilder.build({menu, tab, pageUrl, onTab: true});
     }
   },
+
+  handleSidebarCtxMenu(event) {
+    const menu = event.target;
+    const tree = menu.triggerNode.parentElement;
+    const cell = tree.boxObject.getCellAt(event.x, event.y);
+    const node = tree.view.nodeForTreeIndex(cell.row);
+
+    gMenuBuilder.build({
+      menu,
+      bookmarkId: node.bookmarkGuid,
+      onBookmark: true,
+    });
+  },
 };
 
 this.menusInternal = class extends ExtensionAPI {
   constructor(extension) {
     super(extension);
 
     if (!gMenuMap.size) {
       menuTracker.register();
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -71,16 +71,17 @@ skip-if = (verify && debug && (os == 'ma
 skip-if = (verify && (os == 'linux' || os == 'mac'))
 [browser_ext_commands_execute_sidebar_action.js]
 [browser_ext_commands_getAll.js]
 [browser_ext_commands_onCommand.js]
 [browser_ext_commands_update.js]
 [browser_ext_connect_and_move_tabs.js]
 [browser_ext_contentscript_connect.js]
 [browser_ext_contextMenus.js]
+support-files = !/browser/components/places/tests/browser/head.js
 [browser_ext_contextMenus_checkboxes.js]
 [browser_ext_contextMenus_commands.js]
 [browser_ext_contextMenus_icons.js]
 [browser_ext_contextMenus_onclick.js]
 [browser_ext_contextMenus_radioGroups.js]
 [browser_ext_contextMenus_uninstall.js]
 [browser_ext_contextMenus_urlPatterns.js]
 [browser_ext_currentWindow.js]
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus.js
@@ -1,12 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/browser/components/places/tests/browser/head.js",
+  this);
+/* globals withSidebarTree, synthesizeClickOnSelectedTreeCell */
+
 const PAGE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html";
 
 add_task(async function() {
   let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
 
   gBrowser.selectedTab = tab1;
 
   let extension = ExtensionTestUtils.loadExtension({
@@ -509,63 +514,87 @@ add_task(async function testRemoveAllWit
   await confirmMenuItems("gamma");
   await closeContextMenu();
 
   await first.unload();
   await second.unload();
   BrowserTestUtils.removeTab(tab);
 });
 
-add_task(async function test_bookmark_contextmenu() {
-  const bookmarksToolbar = document.getElementById("PersonalToolbar");
-  setToolbarVisibility(bookmarksToolbar, true);
-
-  const extension = ExtensionTestUtils.loadExtension({
+function bookmarkContextMenuExtension() {
+  return ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["contextMenus", "bookmarks"],
     },
     async background() {
       const url = "https://example.com/";
       const title = "Example";
       let newBookmark = await browser.bookmarks.create({
         url,
         title,
         parentId: "toolbar_____",
       });
       await browser.contextMenus.create({
         title: "Get bookmark",
         contexts: ["bookmark"],
       });
-      browser.test.sendMessage("bookmark-created");
+      browser.test.sendMessage("bookmark-created", newBookmark.id);
       browser.contextMenus.onClicked.addListener(async (info) => {
         browser.test.assertEq(newBookmark.id, info.bookmarkId, "Bookmark ID matches");
 
         let [bookmark] = await browser.bookmarks.get(info.bookmarkId);
         browser.test.assertEq(title, bookmark.title, "Bookmark title matches");
         browser.test.assertEq(url, bookmark.url, "Bookmark url matches");
         browser.test.assertFalse(info.hasOwnProperty("pageUrl"), "Context menu does not expose pageUrl");
         await browser.bookmarks.remove(info.bookmarkId);
         browser.test.sendMessage("test-finish");
       });
     },
   });
+}
+
+add_task(async function test_bookmark_contextmenu() {
+  const bookmarksToolbar = document.getElementById("PersonalToolbar");
+  setToolbarVisibility(bookmarksToolbar, true);
+
+  const extension = bookmarkContextMenuExtension();
+
   await extension.startup();
   await extension.awaitMessage("bookmark-created");
   let menu = await openChromeContextMenu(
     "placesContext",
     "#PersonalToolbar .bookmark-item:last-child");
 
   let menuItem = menu.getElementsByAttribute("label", "Get bookmark")[0];
   closeChromeContextMenu("placesContext", menuItem);
 
   await extension.awaitMessage("test-finish");
   await extension.unload();
   setToolbarVisibility(bookmarksToolbar, false);
 });
 
+add_task(async function test_bookmark_sidebar_contextmenu() {
+  await withSidebarTree("bookmarks", async (tree) => {
+    let extension = bookmarkContextMenuExtension();
+    await extension.startup();
+    let bookmarkGuid = await extension.awaitMessage("bookmark-created");
+
+    let menu = window.sidebar.document.getElementById("placesContext");
+    tree.selectItems([bookmarkGuid]);
+    let shown = BrowserTestUtils.waitForEvent(menu, "popupshown");
+    synthesizeClickOnSelectedTreeCell(tree, {type: "contextmenu"});
+    await shown;
+
+    let menuItem = menu.getElementsByAttribute("label", "Get bookmark")[0];
+    closeChromeContextMenu("placesContext", menuItem, window.sidebar);
+    await extension.awaitMessage("test-finish");
+    await extension.unload();
+  });
+});
+
 add_task(async function test_bookmark_context_requires_permission() {
   const bookmarksToolbar = document.getElementById("PersonalToolbar");
   setToolbarVisibility(bookmarksToolbar, true);
 
   const extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["contextMenus"],
     },