Bug 1325814 - Add tests for menus.getTargetElement API draft
authorRob Wu <rob@robwu.nl>
Sat, 04 Aug 2018 18:40:54 +0200
changeset 828266 efaa0db71538c4e222ce4afe50882582f664457f
parent 828254 27072c31532d536dac4ec7d343a759bde75df575
child 828267 e7635292a7fb9ff5575fd3adebde8e43e14abb26
push id118665
push userbmo:rob@robwu.nl
push dateFri, 10 Aug 2018 16:43:13 +0000
bugs1325814
milestone63.0a1
Bug 1325814 - Add tests for menus.getTargetElement API The tests are split in two files: - browser_ext_menus_targetElement.js to test interaction with web pages via content scripts. - browser_ext_menus_targetElement_extension.js to test interaction with menus in extension processes. And browser_ext_menus_events.js was updated to recognize the new property. MozReview-Commit-ID: F0HEiNADRuM
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_menus_events.js
browser/components/extensions/test/browser/browser_ext_menus_targetElement.js
browser/components/extensions/test/browser/browser_ext_menus_targetElement_extension.js
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -105,16 +105,18 @@ skip-if = (verify && (os == 'linux' || o
 [browser_ext_incognito_popup.js]
 [browser_ext_lastError.js]
 [browser_ext_menus.js]
 [browser_ext_menus_activeTab.js]
 [browser_ext_menus_errors.js]
 [browser_ext_menus_event_order.js]
 [browser_ext_menus_events.js]
 [browser_ext_menus_refresh.js]
+[browser_ext_menus_targetElement.js]
+[browser_ext_menus_targetElement_extension.js]
 [browser_ext_omnibox.js]
 skip-if = (debug && (os == 'linux' || os == 'mac')) || (verify && (os == 'linux' || os == 'mac')) # Bug 1416103 (was bug 1417052)
 [browser_ext_openPanel.js]
 skip-if = (verify && !debug && (os == 'linux' || os == 'mac'))
 [browser_ext_optionsPage_browser_style.js]
 [browser_ext_optionsPage_modals.js]
 [browser_ext_optionsPage_privileges.js]
 [browser_ext_pageAction_context.js]
--- a/browser/components/extensions/test/browser/browser_ext_menus_events.js
+++ b/browser/components/extensions/test/browser/browser_ext_menus_events.js
@@ -2,16 +2,18 @@
  * 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/. */
 "use strict";
 
 const PAGE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html";
 const PAGE_BASE = PAGE.replace("context.html", "");
 const PAGE_HOST_PATTERN = "http://mochi.test/*";
 
+const EXPECT_TARGET_ELEMENT = 13337;
+
 async function grantOptionalPermission(extension, permissions) {
   const {GlobalManager} = ChromeUtils.import("resource://gre/modules/Extension.jsm", {});
   const {ExtensionPermissions} = ChromeUtils.import("resource://gre/modules/ExtensionPermissions.jsm", {});
   let ext = GlobalManager.extensionMap.get(extension.id);
   return ExtensionPermissions.add(ext, permissions);
 }
 
 // Registers a context menu using menus.create(menuCreateParams) and checks
@@ -41,16 +43,21 @@ async function testShowHideEvent({menuCr
 
     let menuCreateParams = await awaitMessage("create-params");
     const [tab] = await browser.tabs.query({active: true, currentWindow: true});
 
     let shownEvents = [];
     let hiddenEvents = [];
 
     browser.menus.onShown.addListener((...args) => {
+      if (args[0].targetElementId) {
+        // In this test, we aren't interested in the exact value,
+        // only in whether it is set or not.
+        args[0].targetElementId = 13337; // = EXPECT_TARGET_ELEMENT
+      }
       shownEvents.push(args[0]);
       if (menuCreateParams.title.includes("TEST_EXPECT_NO_TAB")) {
         browser.test.assertEq(undefined, args[1], "expect no tab");
       } else {
         browser.test.assertEq(tab.id, args[1].id, "expected tab");
       }
       browser.test.assertEq(2, args.length, "expected number of onShown args");
     });
@@ -204,16 +211,18 @@ add_task(async function test_show_hide_w
   });
 
   // Now the menu has been shown and hidden, and in another extension the
   // onShown/onHidden events have been dispatched.
   extension.sendMessage("check menu events");
   let events = await extension.awaitMessage("events from menuless extension");
   is(events.length, 2, "expect two events");
   is(events[1], "onHidden", "last event should be onHidden");
+  ok(events[0].targetElementId, "info.targetElementId must be set in onShown");
+  delete events[0].targetElementId;
   Assert.deepEqual(events[0], {
     menuIds: [],
     contexts: ["page", "all"],
     editable: false,
     pageUrl: PAGE,
     frameId: 0,
   }, "expected onShown info from menuless extension");
   await extension.unload();
@@ -274,22 +283,24 @@ add_task(async function test_show_hide_b
       title: "browserAction popup - TEST_EXPECT_NO_TAB",
       contexts: ["all", "browser_action"],
     },
     expectedShownEvent: {
       contexts: ["page", "all"],
       frameId: 0,
       editable: false,
       get pageUrl() { return popupUrl; },
+      targetElementId: EXPECT_TARGET_ELEMENT,
     },
     expectedShownEventWithPermissions: {
       contexts: ["page", "all"],
       frameId: 0,
       editable: false,
       get pageUrl() { return popupUrl; },
+      targetElementId: EXPECT_TARGET_ELEMENT,
     },
     async doOpenMenu(extension) {
       popupUrl = `moz-extension://${extension.uuid}/popup.html`;
       await clickBrowserAction(extension);
       await openContextMenuInPopup(extension);
     },
     async doCloseMenu(extension) {
       await closeExtensionContextMenu();
@@ -357,16 +368,17 @@ add_task(async function test_show_hide_p
       editable: false,
       frameId: 0,
     },
     expectedShownEventWithPermissions: {
       contexts: ["page", "all"],
       editable: false,
       pageUrl: PAGE,
       frameId: 0,
+      targetElementId: EXPECT_TARGET_ELEMENT,
     },
     async doOpenMenu() {
       await openContextMenu("body");
     },
     async doCloseMenu() {
       await closeExtensionContextMenu();
     },
   });
@@ -386,16 +398,17 @@ add_task(async function test_show_hide_f
       get frameId() { return frameId; },
     },
     expectedShownEventWithPermissions: {
       contexts: ["frame", "all"],
       editable: false,
       get frameId() { return frameId; },
       pageUrl: PAGE,
       frameUrl: PAGE_BASE + "context_frame.html",
+      targetElementId: EXPECT_TARGET_ELEMENT,
     },
     async doOpenMenu() {
       frameId = await ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
         let {contentWindow} = content.document.getElementById("frame");
         return WebNavigationFrames.getFrameId(contentWindow);
       });
       await openContextMenuInFrame("#frame");
     },
@@ -416,16 +429,17 @@ add_task(async function test_show_hide_p
       editable: true,
       frameId: 0,
     },
     expectedShownEventWithPermissions: {
       contexts: ["editable", "password", "all"],
       editable: true,
       frameId: 0,
       pageUrl: PAGE,
+      targetElementId: EXPECT_TARGET_ELEMENT,
     },
     async doOpenMenu() {
       await openContextMenu("#password");
     },
     async doCloseMenu() {
       await closeExtensionContextMenu();
     },
   });
@@ -444,16 +458,17 @@ add_task(async function test_show_hide_l
     },
     expectedShownEventWithPermissions: {
       contexts: ["link", "all"],
       editable: false,
       frameId: 0,
       linkText: "Some link",
       linkUrl: PAGE_BASE + "some-link",
       pageUrl: PAGE,
+      targetElementId: EXPECT_TARGET_ELEMENT,
     },
     async doOpenMenu() {
       await openContextMenu("#link1");
     },
     async doCloseMenu() {
       await closeExtensionContextMenu();
     },
   });
@@ -476,16 +491,17 @@ add_task(async function test_show_hide_i
       mediaType: "image",
       editable: false,
       frameId: 0,
       // Apparently, when a link has no content, its href is used as linkText.
       linkText: PAGE_BASE + "image-around-some-link",
       linkUrl: PAGE_BASE + "image-around-some-link",
       srcUrl: PAGE_BASE + "ctxmenu-image.png",
       pageUrl: PAGE,
+      targetElementId: EXPECT_TARGET_ELEMENT,
     },
     async doOpenMenu() {
       await openContextMenu("#img-wrapped-in-link");
     },
     async doCloseMenu() {
       await closeExtensionContextMenu();
     },
   });
@@ -504,16 +520,17 @@ add_task(async function test_show_hide_e
       frameId: 0,
     },
     expectedShownEventWithPermissions: {
       contexts: ["editable", "selection", "all"],
       editable: true,
       frameId: 0,
       pageUrl: PAGE,
       get selectionText() { return selectionText; },
+      targetElementId: EXPECT_TARGET_ELEMENT,
     },
     async doOpenMenu() {
       // Select lots of text in the test page before opening the menu.
       selectionText = await ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
         let node = content.document.getElementById("editabletext");
         node.select();
         node.focus();
         return node.value;
@@ -542,16 +559,17 @@ add_task(async function test_show_hide_v
     },
     expectedShownEventWithPermissions: {
       contexts: ["video", "all"],
       mediaType: "video",
       editable: false,
       frameId: 0,
       srcUrl: VIDEO_URL,
       pageUrl: PAGE,
+      targetElementId: EXPECT_TARGET_ELEMENT,
     },
     async doOpenMenu() {
       await ContentTask.spawn(gBrowser.selectedBrowser, VIDEO_URL, function(VIDEO_URL) {
         let video = content.document.createElement("video");
         video.controls = true;
         video.src = VIDEO_URL;
         content.document.body.appendChild(video);
         video.focus();
@@ -580,16 +598,17 @@ add_task(async function test_show_hide_a
     },
     expectedShownEventWithPermissions: {
       contexts: ["audio", "all"],
       mediaType: "audio",
       editable: false,
       frameId: 0,
       srcUrl: AUDIO_URL,
       pageUrl: PAGE,
+      targetElementId: EXPECT_TARGET_ELEMENT,
     },
     async doOpenMenu() {
       await ContentTask.spawn(gBrowser.selectedBrowser, AUDIO_URL, function(AUDIO_URL) {
         let audio = content.document.createElement("audio");
         audio.controls = true;
         audio.src = AUDIO_URL;
         content.document.body.appendChild(audio);
         audio.focus();
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_menus_targetElement.js
@@ -0,0 +1,261 @@
+/* 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/. */
+"use strict";
+
+const PAGE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html";
+
+// Loads an extension that records menu visibility events in the current tab.
+// The returned extension has two helper functions "openContextMenu" and
+// "checkIsValid" that are used to verify the behavior of targetElementId.
+async function loadExtensionAndTab() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
+  gBrowser.selectedTab = tab;
+
+  function contentScript() {
+    browser.test.onMessage.addListener((msg, targetElementId, expectedSelector, description) => {
+      browser.test.assertEq("checkIsValid", msg, "Expected message");
+
+      let expected = expectedSelector ? document.querySelector(expectedSelector) : null;
+      let elem = browser.menus.getTargetElement(targetElementId);
+      browser.test.assertEq(expected, elem, description);
+      browser.test.sendMessage("checkIsValidDone");
+    });
+  }
+
+  async function background() {
+    browser.menus.onShown.addListener(async (info, tab) => {
+      browser.test.sendMessage("onShownMenu", info.targetElementId);
+    });
+    await browser.tabs.executeScript({file: "contentScript.js"});
+    browser.test.sendMessage("ready");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["menus", "http://mochi.test/*"],
+    },
+    background,
+    files: {
+      "contentScript.js": contentScript,
+    },
+  });
+
+  extension.openAndCloseMenu = async (selector) => {
+    await openContextMenu(selector);
+    let targetElementId = await extension.awaitMessage("onShownMenu");
+    await closeContextMenu();
+    return targetElementId;
+  };
+
+  extension.checkIsValid = async (targetElementId, expectedSelector, description) => {
+    extension.sendMessage("checkIsValid", targetElementId, expectedSelector, description);
+    await extension.awaitMessage("checkIsValidDone");
+  };
+
+  await extension.startup();
+  await extension.awaitMessage("ready");
+  return {extension, tab};
+}
+
+// Tests that info.targetElementId is only available with the right permissions.
+add_task(async function required_permission() {
+  let {extension, tab} = await loadExtensionAndTab();
+
+  // Load another extension to verify that the permission from the first
+  // extension does not enable the "targetElementId" parameter.
+  function background() {
+    browser.contextMenus.onShown.addListener((info, tab) => {
+      browser.test.assertEq(undefined, info.targetElementId, "targetElementId requires permission");
+      browser.test.sendMessage("onShown");
+    });
+    browser.contextMenus.onClicked.addListener(async (info) => {
+      browser.test.assertEq(undefined, info.targetElementId, "targetElementId requires permission");
+      const code = `
+        browser.test.assertEq(undefined, browser.menus, "menus API requires permission in content script");
+        browser.test.assertEq(undefined, browser.contextMenus, "contextMenus API not available in content script.");
+      `;
+      await browser.tabs.executeScript({code});
+      browser.test.sendMessage("onClicked");
+    });
+    browser.contextMenus.create({title: "menu for page"}, () => {
+      browser.test.sendMessage("ready");
+    });
+  }
+  let extension2 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["contextMenus", "http://mochi.test/*"],
+    },
+    background,
+  });
+  await extension2.startup();
+  await extension2.awaitMessage("ready");
+
+  let menu = await openContextMenu();
+  await extension.awaitMessage("onShownMenu");
+  let menuItem = menu.getElementsByAttribute("label", "menu for page")[0];
+  await closeExtensionContextMenu(menuItem);
+
+  await extension2.awaitMessage("onShown");
+  await extension2.awaitMessage("onClicked");
+  await extension2.unload();
+
+  await extension.unload();
+  BrowserTestUtils.removeTab(tab);
+});
+
+// Tests that the basic functionality works as expected.
+add_task(async function getTargetElement_in_page() {
+  let {extension, tab} = await loadExtensionAndTab();
+
+  for (let selector of ["#img1", "#link1", "#password"]) {
+    let targetElementId = await extension.openAndCloseMenu(selector);
+    ok(Number.isInteger(targetElementId), `targetElementId (${targetElementId}) should be an integer for ${selector}`);
+
+    await extension.checkIsValid(targetElementId, selector, `Expected target to match ${selector}`);
+  }
+
+  await extension.unload();
+  BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function getTargetElement_in_frame() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
+  gBrowser.selectedTab = tab;
+
+  async function background() {
+    let targetElementId;
+    browser.menus.onShown.addListener(async (info, tab) => {
+      browser.test.assertTrue(info.frameUrl.endsWith("context_frame.html"), `Expected frame ${info.frameUrl}`);
+      targetElementId = info.targetElementId;
+      let elem = browser.menus.getTargetElement(targetElementId);
+      browser.test.assertEq(null, elem, "should not find page element in extension's background");
+
+      await browser.tabs.executeScript(tab.id, {
+        code: `{
+          let elem = browser.menus.getTargetElement(${targetElementId});
+          browser.test.assertEq(null, elem, "should not find element from different frame");
+        }`,
+      });
+
+      await browser.tabs.executeScript(tab.id, {
+        frameId: info.frameId,
+        code: `{
+          let elem = browser.menus.getTargetElement(${targetElementId});
+          browser.test.assertEq(document.body, elem, "should find the target element in the frame");
+        }`,
+      });
+      browser.test.sendMessage("pageAndFrameChecked");
+    });
+
+    browser.menus.onClicked.addListener(info => {
+      browser.test.assertEq(targetElementId, info.targetElementId, "targetElementId in onClicked must match onShown.");
+      browser.test.sendMessage("onClickedChecked");
+    });
+
+    browser.menus.create({title: "menu for frame", contexts: ["frame"]}, () => {
+      browser.test.sendMessage("ready");
+    });
+  }
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["menus", "http://mochi.test/*"],
+    },
+    background,
+  });
+
+  await extension.startup();
+  await extension.awaitMessage("ready");
+
+  let menu = await openContextMenuInFrame("#frame");
+  await extension.awaitMessage("pageAndFrameChecked");
+  let menuItem = menu.getElementsByAttribute("label", "menu for frame")[0];
+  await closeExtensionContextMenu(menuItem);
+  await extension.awaitMessage("onClickedChecked");
+
+  await extension.unload();
+  BrowserTestUtils.removeTab(tab);
+});
+
+// Test that getTargetElement does not return a detached element.
+add_task(async function getTargetElement_after_removing_element() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
+
+  function background() {
+    function contentScript(targetElementId) {
+      let expectedElem = document.getElementById("edit-me");
+      let {nextElementSibling} = expectedElem;
+
+      let elem = browser.menus.getTargetElement(targetElementId);
+      browser.test.assertEq(expectedElem, elem, "Expected target element before element removal");
+
+      expectedElem.remove();
+      elem = browser.menus.getTargetElement(targetElementId);
+      browser.test.assertEq(null, elem, "Expected no target element after element removal.");
+
+      nextElementSibling.insertAdjacentElement("beforebegin", expectedElem);
+      elem = browser.menus.getTargetElement(targetElementId);
+      browser.test.assertEq(expectedElem, elem, "Expected target element after element restoration.");
+    }
+    browser.menus.onClicked.addListener(async (info, tab) => {
+      const code = `(${contentScript})(${info.targetElementId})`;
+      browser.test.log(code);
+      await browser.tabs.executeScript(tab.id, {code});
+      browser.test.sendMessage("checkedRemovedElement");
+    });
+    browser.menus.create({title: "some menu item"}, () => {
+      browser.test.sendMessage("ready");
+    });
+  }
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["menus", "http://mochi.test/*"],
+    },
+    background,
+  });
+
+  await extension.startup();
+  await extension.awaitMessage("ready");
+  let menu = await openContextMenu("#edit-me");
+  let menuItem = menu.getElementsByAttribute("label", "some menu item")[0];
+  await closeExtensionContextMenu(menuItem);
+  await extension.awaitMessage("checkedRemovedElement");
+  await extension.unload();
+  BrowserTestUtils.removeTab(tab);
+});
+
+// Tests whether targetElementId expires after opening a new menu.
+add_task(async function expireTargetElement() {
+  let {extension, tab} = await loadExtensionAndTab();
+
+  // Open the menu once to get the first element ID.
+  let targetElementId = await extension.openAndCloseMenu("#longtext");
+
+  // Open another menu. The previous ID should expire.
+  await extension.openAndCloseMenu("#longtext");
+  await extension.checkIsValid(targetElementId, null, `Expected initial target ID to expire after opening another menu`);
+
+  await extension.unload();
+  BrowserTestUtils.removeTab(tab);
+});
+
+// Tests whether targetElementId of different tabs are independent.
+add_task(async function independentMenusInDifferentTabs() {
+  let {extension, tab} = await loadExtensionAndTab();
+
+  let targetElementId = await extension.openAndCloseMenu("#longtext");
+
+  let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE + "?");
+  gBrowser.selectedTab = tab2;
+
+  let targetElementId2 = await extension.openAndCloseMenu("#editabletext");
+
+  await extension.checkIsValid(targetElementId2, null,
+                               "targetElementId from different tab should not resolve.");
+  await extension.checkIsValid(targetElementId, "#longtext",
+                               "Expected getTargetElement to work after closing a menu in another tab.");
+
+  await extension.unload();
+  BrowserTestUtils.removeTab(tab);
+  BrowserTestUtils.removeTab(tab2);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_menus_targetElement_extension.js
@@ -0,0 +1,165 @@
+/* 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/. */
+"use strict";
+
+add_task(async function getTargetElement_in_extension_tab() {
+  async function background() {
+    browser.menus.onShown.addListener(info => {
+      let elem = browser.menus.getTargetElement(info.targetElementId);
+      browser.test.assertEq(null, elem, "should not get element of tab content in background");
+
+      // By using getViews() here, we verify that the targetElementId can
+      // synchronously be mapped to a valid element in a different tab
+      // during the onShown event.
+      let [tabGlobal] = browser.extension.getViews({type: "tab"});
+      elem = tabGlobal.browser.menus.getTargetElement(info.targetElementId);
+      browser.test.assertEq("BUTTON", elem.tagName, "should get element in tab content");
+      browser.test.sendMessage("elementChecked");
+    });
+
+    browser.tabs.create({url: "tab.html"});
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["menus"],
+    },
+    files: {
+      "tab.html": `<!DOCTYPE html><meta charset="utf-8"><button>Button in tab</button>`,
+    },
+    background,
+  });
+
+  let extensionTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, null, true);
+  await extension.startup();
+  // Must wait for the tab to have loaded completely before calling openContextMenu.
+  await extensionTabPromise;
+  await openContextMenu("button");
+  await extension.awaitMessage("elementChecked");
+  await closeContextMenu();
+
+  // Unloading the extension will automatically close the extension's tab.html
+  await extension.unload();
+});
+
+add_task(async function getTargetElement_in_extension_tab_on_click() {
+  // Similar to getTargetElement_in_extension_tab, except we check whether
+  // calling getTargetElement in onClicked results in the expected behavior.
+  async function background() {
+    browser.menus.onClicked.addListener(info => {
+      let [tabGlobal] = browser.extension.getViews({type: "tab"});
+      let elem = tabGlobal.browser.menus.getTargetElement(info.targetElementId);
+      browser.test.assertEq("BUTTON", elem.tagName, "should get element in tab content on click");
+      browser.test.sendMessage("elementClicked");
+    });
+
+    browser.menus.create({title: "click here"}, () => {
+      browser.tabs.create({url: "tab.html"});
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["menus"],
+    },
+    files: {
+      "tab.html": `<!DOCTYPE html><meta charset="utf-8"><button>Button in tab</button>`,
+    },
+    background,
+  });
+
+  let extensionTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, null, true);
+  await extension.startup();
+  await extensionTabPromise;
+  let menu = await openContextMenu("button");
+  let menuItem = menu.getElementsByAttribute("label", "click here")[0];
+  await closeExtensionContextMenu(menuItem);
+  await extension.awaitMessage("elementClicked");
+
+  await extension.unload();
+});
+
+add_task(async function getTargetElement_in_browserAction_popup() {
+  async function background() {
+    browser.menus.onShown.addListener(info => {
+      let elem = browser.menus.getTargetElement(info.targetElementId);
+      browser.test.assertEq(null, elem, "should not get element of popup content in background");
+
+      let [popupGlobal] = browser.extension.getViews({type: "popup"});
+      elem = popupGlobal.browser.menus.getTargetElement(info.targetElementId);
+      browser.test.assertEq("BUTTON", elem.tagName, "should get element in popup content");
+      browser.test.sendMessage("popupChecked");
+    });
+
+    // Ensure that onShown is registered (workaround for bug 1300234):
+    await browser.menus.removeAll();
+    browser.test.sendMessage("ready");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["menus"],
+      browser_action: {
+        default_popup: "popup.html",
+      },
+    },
+    files: {
+      "popup.html": `<!DOCTYPE html><meta charset="utf-8"><button>Button in popup</button>`,
+    },
+    background,
+  });
+
+  await extension.startup();
+  await extension.awaitMessage("ready");
+
+  await clickBrowserAction(extension);
+  await openContextMenuInPopup(extension, "button");
+  await extension.awaitMessage("popupChecked");
+  await closeContextMenu();
+  await closeBrowserAction(extension);
+
+  await extension.unload();
+});
+
+add_task(async function getTargetElement_in_sidebar_panel() {
+  async function sidebarJs() {
+    browser.menus.onShown.addListener(info => {
+      let expected = document.querySelector("button");
+      let elem = browser.menus.getTargetElement(info.targetElementId);
+      browser.test.assertEq(expected, elem, "should get element in sidebar content");
+      browser.test.sendMessage("done");
+    });
+
+    // Ensure that onShown is registered (workaround for bug 1300234):
+    await browser.menus.removeAll();
+    browser.test.sendMessage("ready");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    useAddonManager: "temporary", // To automatically show sidebar on load.
+    manifest: {
+      permissions: ["menus"],
+      sidebar_action: {
+        default_panel: "sidebar.html",
+      },
+    },
+    files: {
+      "sidebar.html": `
+        <!DOCTYPE html><meta charset="utf-8">
+        <button>Button in sidebar</button>
+        <script src="sidebar.js"></script>
+      `,
+      "sidebar.js": sidebarJs,
+    },
+  });
+
+  await extension.startup();
+  await extension.awaitMessage("ready");
+
+  let sidebarMenu = await openContextMenuInSidebar("button");
+  await extension.awaitMessage("done");
+  await closeContextMenu(sidebarMenu);
+
+  await extension.unload();
+});