bug 1472427, <img usemap> should work in shadow DOM, r=baku
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Wed, 04 Jul 2018 20:26:09 +0300
changeset 425095 fcc1e5fa1c4296d304e10884a0632e2c3d74f670
parent 425094 8151e107216cc6fe4eb473fd48f20059be0e2668
child 425096 237d7cd76af8a389c3ab1df98e5beefd534e4da0
push id104967
push useropettay@mozilla.com
push dateWed, 04 Jul 2018 17:28:14 +0000
treeherdermozilla-inbound@fcc1e5fa1c42 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1472427
milestone63.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 1472427, <img usemap> should work in shadow DOM, r=baku
dom/base/nsDocument.cpp
dom/base/nsFocusManager.cpp
dom/base/nsIDocument.h
dom/base/nsImageLoadingContent.cpp
dom/base/nsImageLoadingContent.h
dom/base/test/mochitest.ini
dom/base/test/test_bug1472427.html
dom/html/HTMLImageElement.cpp
layout/generic/nsImageFrame.cpp
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -9588,57 +9588,25 @@ void
 nsIDocument::SetNavigationTiming(nsDOMNavigationTiming* aTiming)
 {
   mTiming = aTiming;
   if (!mLoadingTimeStamp.IsNull() && mTiming) {
     mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(), mLoadingTimeStamp);
   }
 }
 
-Element*
-nsIDocument::FindImageMap(const nsAString& aUseMapValue)
-{
-  if (aUseMapValue.IsEmpty()) {
-    return nullptr;
-  }
-
-  nsAString::const_iterator start, end;
-  aUseMapValue.BeginReading(start);
-  aUseMapValue.EndReading(end);
-
-  int32_t hash = aUseMapValue.FindChar('#');
-  if (hash < 0) {
-    return nullptr;
-  }
-  // aUsemap contains a '#', set start to point right after the '#'
-  start.advance(hash + 1);
-
-  if (start == end) {
-    return nullptr; // aUsemap == "#"
-  }
-
-  const nsAString& mapName = Substring(start, end);
-
+nsContentList*
+nsIDocument::ImageMapList()
+{
   if (!mImageMaps) {
-    mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map, nsGkAtoms::map);
-  }
-
-  uint32_t i, n = mImageMaps->Length(true);
-  nsString name;
-  for (i = 0; i < n; ++i) {
-    nsIContent* map = mImageMaps->Item(i);
-    if (map->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id, mapName,
-                                      eCaseMatters) ||
-        map->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, mapName,
-                                      eCaseMatters)) {
-      return map->AsElement();
-    }
-  }
-
-  return nullptr;
+    mImageMaps = new nsContentList(this, kNameSpaceID_XHTML,
+                                   nsGkAtoms::map, nsGkAtoms::map);
+  }
+
+  return mImageMaps;
 }
 
 #define DEPRECATED_OPERATION(_op) #_op "Warning",
 static const char* kDeprecationWarnings[] = {
 #include "nsDeprecatedOperationList.h"
   nullptr
 };
 #undef DEPRECATED_OPERATION
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -43,16 +43,17 @@
 #include "TabChild.h"
 #include "nsFrameLoader.h"
 #include "nsNumberControlFrame.h"
 #include "nsNetUtil.h"
 #include "nsRange.h"
 
 #include "mozilla/ContentEvents.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLImageElement.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/dom/HTMLSlotElement.h"
 #include "mozilla/dom/Text.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/IMEStateManager.h"
@@ -3852,22 +3853,22 @@ nsFocusManager::TryToMoveFocusToSubDocum
 }
 
 nsIContent*
 nsFocusManager::GetNextTabbableMapArea(bool aForward,
                                        int32_t aCurrentTabIndex,
                                        Element* aImageContent,
                                        nsIContent* aStartContent)
 {
-  nsAutoString useMap;
-  aImageContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usemap, useMap);
-
-  nsCOMPtr<nsIDocument> doc = aImageContent->GetComposedDoc();
-  if (doc) {
-    nsCOMPtr<nsIContent> mapContent = doc->FindImageMap(useMap);
+  if (aImageContent->IsInComposedDoc()) {
+    HTMLImageElement* imgElement = HTMLImageElement::FromNode(aImageContent);
+    // The caller should check the element type, so we can assert here.
+    MOZ_ASSERT(imgElement);
+
+    nsCOMPtr<nsIContent> mapContent = imgElement->FindImageMap();
     if (!mapContent)
       return nullptr;
     uint32_t count = mapContent->GetChildCount();
     // First see if the the start content is in this map
 
     int32_t index = mapContent->ComputeIndexOf(aStartContent);
     int32_t tabIndex;
     if (index < 0 || (aStartContent->IsFocusable(&tabIndex) &&
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2928,17 +2928,17 @@ public:
 
   nsDOMNavigationTiming* GetNavigationTiming() const
   {
     return mTiming;
   }
 
   void SetNavigationTiming(nsDOMNavigationTiming* aTiming);
 
-  Element* FindImageMap(const nsAString& aNormalizedMapName);
+  nsContentList* ImageMapList();
 
   // Add aLink to the set of links that need their status resolved.
   void RegisterPendingLinkUpdate(mozilla::dom::Link* aLink);
 
   // Update state on links in mLinksToUpdate.  This function must be called
   // prior to selector matching that needs to differentiate between :link and
   // :visited.  In particular, it does _not_ need to be called before doing any
   // selector matching that uses TreeMatchContext::eNeverMatchVisited.  The only
--- a/dom/base/nsImageLoadingContent.cpp
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -12,16 +12,17 @@
 
 #include "nsImageLoadingContent.h"
 #include "nsError.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIDOMWindow.h"
 #include "nsServiceManagerUtils.h"
+#include "nsContentList.h"
 #include "nsContentPolicyUtils.h"
 #include "nsIURI.h"
 #include "nsILoadGroup.h"
 #include "imgIContainer.h"
 #include "imgLoader.h"
 #include "imgRequestProxy.h"
 #include "nsThreadUtils.h"
 #include "nsNetUtil.h"
@@ -1731,8 +1732,66 @@ nsImageLoadingContent::ScriptedImageObse
 
 // Only HTMLInputElement.h overrides this for <img> tags
 // all other subclasses use this one, i.e. ignore referrer attributes
 mozilla::net::ReferrerPolicy
 nsImageLoadingContent::GetImageReferrerPolicy()
 {
   return mozilla::net::RP_Unset;
 }
+
+Element*
+nsImageLoadingContent::FindImageMap()
+{
+  nsIContent* thisContent = AsContent();
+  Element* thisElement = thisContent->AsElement();
+
+  nsAutoString useMap;
+  thisElement->GetAttr(kNameSpaceID_None, nsGkAtoms::usemap, useMap);
+  if (useMap.IsEmpty()) {
+    return nullptr;
+  }
+
+  nsAString::const_iterator start, end;
+  useMap.BeginReading(start);
+  useMap.EndReading(end);
+
+  int32_t hash = useMap.FindChar('#');
+  if (hash < 0) {
+    return nullptr;
+  }
+  // useMap contains a '#', set start to point right after the '#'
+  start.advance(hash + 1);
+
+  if (start == end) {
+    return nullptr; // useMap == "#"
+  }
+
+  RefPtr<nsContentList> imageMapList;
+  if (thisElement->IsInUncomposedDoc()) {
+    // Optimize the common case and use document level image map.
+    imageMapList = thisElement->OwnerDoc()->ImageMapList();
+  } else {
+    // Per HTML spec image map should be searched in the element's scope,
+    // so using SubtreeRoot() here.
+    // Because this is a temporary list, we don't need to make it live.
+    imageMapList = new nsContentList(thisElement->SubtreeRoot(),
+                                     kNameSpaceID_XHTML,
+                                     nsGkAtoms::map, nsGkAtoms::map,
+                                     true, /* deep */
+                                     false /* live */);
+  }
+
+  nsAutoString mapName(Substring(start, end));
+
+  uint32_t i, n = imageMapList->Length(true);
+  for (i = 0; i < n; ++i) {
+    nsIContent* map = imageMapList->Item(i);
+    if (map->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id,
+                                      mapName, eCaseMatters) ||
+        map->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+                                      mapName, eCaseMatters)) {
+      return map->AsElement();
+    }
+  }
+
+  return nullptr;
+}
--- a/dom/base/nsImageLoadingContent.h
+++ b/dom/base/nsImageLoadingContent.h
@@ -28,16 +28,19 @@
 class nsIURI;
 class nsIDocument;
 class nsPresContext;
 class nsIContent;
 class imgRequestProxy;
 
 namespace mozilla {
 class AsyncEventDispatcher;
+namespace dom {
+class Element;
+} // namespace Element;
 } // namespace mozilla
 
 #ifdef LoadImage
 // Undefine LoadImage to prevent naming conflict with Windows.
 #undef LoadImage
 #endif
 
 class nsImageLoadingContent : public nsIImageLoadingContent
@@ -70,16 +73,18 @@ public:
   already_AddRefed<imgIRequest>
     GetRequest(int32_t aRequestType, mozilla::ErrorResult& aError);
   int32_t
     GetRequestType(imgIRequest* aRequest, mozilla::ErrorResult& aError);
   already_AddRefed<nsIURI> GetCurrentURI(mozilla::ErrorResult& aError);
   already_AddRefed<nsIURI> GetCurrentRequestFinalURI();
   void ForceReload(bool aNotify, mozilla::ErrorResult& aError);
 
+  mozilla::dom::Element* FindImageMap();
+
 protected:
   enum ImageLoadType {
     // Most normal image loads
     eImageLoadType_Normal,
     // From a <img srcset> or <picture> context. Affects type given to content
     // policy.
     eImageLoadType_Imageset
   };
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -612,16 +612,17 @@ skip-if = toolkit == 'android'
 [test_bug1381710.html]
 [test_bug1384661.html]
 [test_bug1399605.html]
 [test_bug1404385.html]
 [test_bug1406102.html]
 [test_bug1421568.html]
 [test_bug1453693.html]
 skip-if = os == "mac"
+[test_bug1472427.html]
 [test_caretPositionFromPoint.html]
 [test_change_policy.html]
 [test_clearTimeoutIntervalNoArg.html]
 [test_constructor-assignment.html]
 [test_constructor.html]
 [test_copyimage.html]
 subsuite = clipboard
 skip-if = toolkit == 'android' #bug 904183
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_bug1472427.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1472427
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1472427</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 1472427 **/
+
+  SimpleTest.waitForExplicitFinish();
+  var image100x100 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAnUlEQVR42u3RAQ0AAAQAMLJqqCA1zP4Kz+qe4IwUIgQhQhAiBCFCECJEiBCECEGIEIQIQYgQhCBECEKEIEQIQoQgBCFCECIEIUIQIgQhCBGCECEIEYIQIQhBiBCECEGIEIQIQQhChCBECEKEIEQIQhAiBCFCECIEIUIQghAhCBGCECEIEYIQhAhBiBCECEGIEIQIESIEIUIQIgQh3y1XQhXMIlaKKwAAAABJRU5ErkJggg==";
+  var ifr;
+  var doc;
+  var img;
+  var host;
+  var root;
+  var map;
+  var area;
+
+  function initPage() {
+    ifr = document.createElement("iframe");
+    ifr.src = "about:blank";
+    document.body.appendChild(ifr);
+    ifr.onload = initIframe;
+  }
+
+  function initIframe() {
+    ifr.contentWindow.focus();
+    doc = ifr.contentDocument;
+    host = doc.createElement("div");
+    doc.body.appendChild(host);
+    root = host.attachShadow({mode: "open"});
+
+    img = document.createElement("img");
+    img.useMap = "#map"
+    img.src = image100x100;
+    img.onload = runTest;
+    root.appendChild(img);
+
+    map = doc.createElement("map");
+    map.name = "map";
+    root.appendChild(map);
+
+    area = doc.createElement("area");
+    area.shape = "rect";
+    area.href = "#area";
+    area.coords = "0,0,100,100";
+    map.appendChild(area);
+  }
+
+  function runTest() {
+    var gotClick = false;
+    var expectedTarget = area;
+    root.addEventListener("click",
+                          function(e) {
+                            gotClick = true;
+                            is(e.target, expectedTarget,
+                                expectedTarget.localName + " element should be the target for the click.");
+                            e.preventDefault();
+                          });
+    synthesizeMouse(img, 50, 50, {}, ifr.contentWindow);
+    ok(gotClick, "Should have got a click event.");
+
+    gotclick = false;
+    map.name = "wrongNameMap";
+    expectedTarget = img;
+    synthesizeMouse(img, 50, 50, {}, ifr.contentWindow);
+    ok(gotClick, "Should have got a click event.");
+    SimpleTest.finish();
+  }
+
+  </script>
+</head>
+<body onload="SpecialPowers.pushPrefEnv({'set':[['dom.webcomponents.shadowdom.enabled', true]]}, initPage);">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1472427">Mozilla Bug 1472427</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -515,33 +515,26 @@ HTMLImageElement::GetEventTargetParent(E
 }
 
 bool
 HTMLImageElement::IsHTMLFocusable(bool aWithMouse,
                                   bool *aIsFocusable, int32_t *aTabIndex)
 {
   int32_t tabIndex = TabIndex();
 
-  if (IsInUncomposedDoc()) {
-    nsAutoString usemap;
-    GetUseMap(usemap);
-    // XXXbz which document should this be using?  sXBL/XBL2 issue!  I
-    // think that OwnerDoc() is right, since we don't want to
-    // assume stuff about the document we're bound to.
-    if (OwnerDoc()->FindImageMap(usemap)) {
-      if (aTabIndex) {
-        // Use tab index on individual map areas
-        *aTabIndex = (sTabFocusModel & eTabFocus_linksMask)? 0 : -1;
-      }
-      // Image map is not focusable itself, but flag as tabbable
-      // so that image map areas get walked into.
-      *aIsFocusable = false;
+  if (IsInComposedDoc() && FindImageMap()) {
+    if (aTabIndex) {
+      // Use tab index on individual map areas
+      *aTabIndex = (sTabFocusModel & eTabFocus_linksMask)? 0 : -1;
+    }
+    // Image map is not focusable itself, but flag as tabbable
+    // so that image map areas get walked into.
+    *aIsFocusable = false;
 
-      return false;
-    }
+    return false;
   }
 
   if (aTabIndex) {
     // Can be in tab order if tabindex >=0 and form controls are tabbable.
     *aTabIndex = (sTabFocusModel & eTabFocus_formElementsMask)? tabIndex : -1;
   }
 
   *aIsFocusable =
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -22,16 +22,17 @@
 #include "mozilla/dom/ResponsiveImageSelector.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Unused.h"
 
 #include "nsCOMPtr.h"
 #include "nsFontMetrics.h"
 #include "nsIImageLoadingContent.h"
+#include "nsImageLoadingContent.h"
 #include "nsString.h"
 #include "nsPrintfCString.h"
 #include "nsPresContext.h"
 #include "nsIPresShell.h"
 #include "nsGkAtoms.h"
 #include "nsIDocument.h"
 #include "nsContentUtils.h"
 #include "nsCSSAnonBoxes.h"
@@ -946,23 +947,20 @@ nsRect
 nsImageFrame::GetInnerArea() const
 {
   return GetContentRectRelativeToSelf();
 }
 
 Element*
 nsImageFrame::GetMapElement() const
 {
-  nsAutoString usemap;
-  if (mContent->AsElement()->GetAttr(kNameSpaceID_None,
-                                     nsGkAtoms::usemap,
-                                     usemap)) {
-    return mContent->OwnerDoc()->FindImageMap(usemap);
-  }
-  return nullptr;
+  nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+  return imageLoader ?
+    static_cast<nsImageLoadingContent*>(imageLoader.get())->FindImageMap() :
+    nullptr;
 }
 
 // get the offset into the content area of the image where aImg starts if it is a continuation.
 nscoord
 nsImageFrame::GetContinuationOffset() const
 {
   nscoord offset = 0;
   for (nsIFrame *f = GetPrevInFlow(); f; f = f->GetPrevInFlow()) {