Bug 1441647 - update contextmenu code to deal with Shadow DOM, r=jaws
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Fri, 04 May 2018 19:02:44 +0300
changeset 473031 329030905869c9dca57f6951aaa4f66b5f7b4f11
parent 473030 5e42fccea4f3cd91f90744629a734e440cc44567
child 473032 4d6703473c83f33c1417ded8624f7fd20e5b6f57
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjaws
bugs1441647
milestone61.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 1441647 - update contextmenu code to deal with Shadow DOM, r=jaws
browser/base/content/test/general/browser_contextmenu.js
browser/base/content/test/general/subtst_contextmenu.html
browser/modules/ContextMenu.jsm
--- a/browser/base/content/test/general/browser_contextmenu.js
+++ b/browser/base/content/test/general/browser_contextmenu.js
@@ -52,16 +52,18 @@ add_task(async function test_xul_text_li
   // Clean up so won't affect HTML element test cases
   lastElementSelector = null;
   gBrowser.removeCurrentTab();
 });
 
 // Below are test cases for HTML element
 
 add_task(async function test_setup_html() {
+  await pushPrefs(["dom.webcomponents.shadowdom.enabled", true]);
+
   let url = example_base + "subtst_contextmenu.html";
 
   await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
 
   await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
     let doc = content.document;
     let audioIframe = doc.querySelector("#test-audio-in-iframe");
     // media documents always use a <video> tag.
@@ -126,16 +128,41 @@ add_task(async function test_link() {
      "context-copylink",      true,
      "context-searchselect",  true,
      "---", null,
      "context-sendlinktodevice", true, [], null,
     ]
   );
 });
 
+add_task(async function test_link_in_shadow_dom() {
+  await test_contextmenu("#shadow-host",
+    ["context-openlinkintab", true,
+     ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+     // We need a blank entry here because the containers submenu is
+     // dynamically generated with no ids.
+     ...(hasContainers ? ["", null] : []),
+     "context-openlink",      true,
+     "context-openlinkprivate", true,
+     "---",                   null,
+     "context-bookmarklink",  true,
+     "context-savelink",      true,
+     ...(hasPocket ? ["context-savelinktopocket", true] : []),
+     "context-copylink",      true,
+     "context-searchselect",  true,
+     "---", null,
+     "context-sendlinktodevice", true, [], null,
+    ],
+    {
+      offsetX: 6,
+      offsetY: 6
+    }
+  );
+});
+
 add_task(async function test_mailto() {
   await test_contextmenu("#test-mailto",
     ["context-copyemail", true,
      "context-searchselect", true
     ]
   );
 });
 
--- a/browser/base/content/test/general/subtst_contextmenu.html
+++ b/browser/base/content/test/general/subtst_contextmenu.html
@@ -3,16 +3,24 @@
 <head>
   <title>Subtest for browser context menu</title>
 </head>
 <body>
 Browser context menu subtest.
 
 <div id="test-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div>
 <a id="test-link" href="http://mozilla.com">Click the monkey!</a>
+<div id="shadow-host"></div>
+<script>
+// Create the shadow DOM in case shadow DOM is enabled.
+if ("ShadowRoot" in this) {
+  var sr = document.getElementById("shadow-host").attachShadow({mode: "closed"});
+  sr.innerHTML = "<a href='http://mozilla.com'>Click the monkey!</a>";
+}
+</script>
 <a id="test-mailto" href="mailto:codemonkey@mozilla.com">Mail the monkey!</a><br>
 <input id="test-input"><br>
 <img id="test-image" src="ctxmenu-image.png">
 <canvas id="test-canvas" width="100" height="100" style="background-color: blue"></canvas>
 <video controls id="test-video-ok"  src="video.ogg" width="100" height="100" style="background-color: green"></video>
 <video id="test-audio-in-video" src="audio.ogg" width="100" height="100" style="background-color: red"></video>
 <video controls id="test-video-bad" src="bogus.duh" width="100" height="100" style="background-color: orange"></video>
 <video controls id="test-video-bad2" width="100" height="100" style="background-color: yellow">
--- a/browser/modules/ContextMenu.jsm
+++ b/browser/modules/ContextMenu.jsm
@@ -489,85 +489,86 @@ class ContextMenu {
 
   _handleContentContextMenu(aEvent) {
     let defaultPrevented = aEvent.defaultPrevented;
 
     if (!Services.prefs.getBoolPref("dom.event.contextmenu.enabled")) {
       let plugin = null;
 
       try {
-        plugin = aEvent.target.QueryInterface(Ci.nsIObjectLoadingContent);
+        plugin = aEvent.composedTarget.QueryInterface(Ci.nsIObjectLoadingContent);
       } catch (e) {}
 
       if (plugin && plugin.displayedType == Ci.nsIObjectLoadingContent.TYPE_PLUGIN) {
         // Don't open a context menu for plugins.
         return;
       }
 
       defaultPrevented = false;
     }
 
     if (defaultPrevented) {
       return;
     }
 
-    let doc = aEvent.target.ownerDocument;
+    let doc = aEvent.composedTarget.ownerDocument;
     let {
       mozDocumentURIIfNotForErrorPages: docLocation,
       characterSet: charSet,
       baseURI,
       referrer,
       referrerPolicy
     } = doc;
     docLocation = docLocation && docLocation.spec;
     let frameOuterWindowID = WebNavigationFrames.getFrameId(doc.defaultView);
-    let loginFillInfo = LoginManagerContent.getFieldContext(aEvent.target);
+    let loginFillInfo = LoginManagerContent.getFieldContext(aEvent.composedTarget);
 
     // The same-origin check will be done in nsContextMenu.openLinkInTab.
     let parentAllowsMixedContent = !!this.global.docShell.mixedContentChannel;
 
     // Get referrer attribute from clicked link and parse it
-    let referrerAttrValue = Services.netUtils.parseAttributePolicyString(aEvent.target.
-                            getAttribute("referrerpolicy"));
+    let referrerAttrValue =
+      Services.netUtils.parseAttributePolicyString(aEvent.composedTarget.
+                                                   getAttribute("referrerpolicy"));
 
     if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
       referrerPolicy = referrerAttrValue;
     }
 
     let disableSetDesktopBg = null;
 
     // Media related cache info parent needs for saving
     let contentType = null;
     let contentDisposition = null;
-    if (aEvent.target.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
-        aEvent.target instanceof Ci.nsIImageLoadingContent &&
-        aEvent.target.currentURI) {
-      disableSetDesktopBg = this._disableSetDesktopBackground(aEvent.target);
+    if (aEvent.composedTarget.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
+        aEvent.composedTarget instanceof Ci.nsIImageLoadingContent &&
+        aEvent.composedTarget.currentURI) {
+      disableSetDesktopBg = this._disableSetDesktopBackground(aEvent.composedTarget);
 
       try {
         let imageCache = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                                                          .getImgCacheForDocument(doc);
         // The image cache's notion of where this image is located is
         // the currentURI of the image loading content.
-        let props = imageCache.findEntryProperties(aEvent.target.currentURI, doc);
+        let props = imageCache.findEntryProperties(aEvent.composedTarget.currentURI, doc);
 
         try {
           contentType = props.get("type", Ci.nsISupportsCString).data;
         } catch (e) {}
 
         try {
           contentDisposition = props.get("content-disposition", Ci.nsISupportsCString).data;
         } catch (e) {}
       } catch (e) {}
     }
 
     let selectionInfo = BrowserUtils.getSelectionDetails(this.content);
     let loadContext = this.global.docShell.QueryInterface(Ci.nsILoadContext);
     let userContextId = loadContext.originAttributes.userContextId;
-    let popupNodeSelectors = this._getNodeSelectors(aEvent.target);
+    let popupNodeSelectors = this._getNodeSelectors(aEvent.composedTarget);
 
     this._setContext(aEvent);
     let context = this.context;
     this.target = context.target;
 
     let spellInfo = null;
     let editFlags = null;
     let principal = null;
@@ -576,30 +577,30 @@ class ContextMenu {
     let targetAsCPOW = context.target;
     if (targetAsCPOW) {
       this._cleanContext();
     }
 
     let isRemote = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
 
     if (isRemote) {
-      editFlags = SpellCheckHelper.isEditable(aEvent.target, this.content);
+      editFlags = SpellCheckHelper.isEditable(aEvent.composedTarget, this.content);
 
       if (editFlags & SpellCheckHelper.SPELLCHECKABLE) {
         spellInfo = InlineSpellCheckerContent.initContextMenu(aEvent, editFlags, this.global);
       }
 
       // Set the event target first as the copy image command needs it to
       // determine what was context-clicked on. Then, update the state of the
       // commands on the context menu.
       this.global.docShell.contentViewer.QueryInterface(Ci.nsIContentViewerEdit)
-                          .setCommandNode(aEvent.target);
-      aEvent.target.ownerGlobal.updateCommands("contentcontextmenu");
+                          .setCommandNode(aEvent.composedTarget);
+      aEvent.composedTarget.ownerGlobal.updateCommands("contentcontextmenu");
 
-      customMenuItems = PageMenuChild.build(aEvent.target);
+      customMenuItems = PageMenuChild.build(aEvent.composedTarget);
       principal = doc.nodePrincipal;
     }
 
     let data = {
       context,
       charSet,
       baseURI,
       isRemote,
@@ -702,17 +703,17 @@ class ContextMenu {
   _setContext(aEvent) {
     this.context = Object.create(null);
     const context = this.context;
 
     context.screenX = aEvent.screenX;
     context.screenY = aEvent.screenY;
     context.mozInputSource = aEvent.mozInputSource;
 
-    const node = aEvent.target;
+    const node = aEvent.composedTarget;
 
     const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
     context.shouldDisplay = true;
 
     if (node.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE ||
         // Don't display for XUL element unless <label class="text-link">
         (node.namespaceURI == XUL_NS && !this._isXULTextLinkLabel(node))) {