Bug 1362400 part 1 - Make it possible for SMIL to change the link state of an SVG anchor element r=jwatt r=smaug (DOM)
authorRobert Longson <longsonr@gmail.com>
Sat, 06 May 2017 20:32:14 +0100
changeset 356960 005d5107cd831f9a03d7212a7f2c496e7e33e65c
parent 356959 b658ca8e2aed84bbc9cbeb52b341e7d40588a8bd
child 356961 b7dfb3eba4c49b90ba49f72d69ce8f3c910b2c7b
push id31776
push userihsiao@mozilla.com
push dateMon, 08 May 2017 03:11:58 +0000
treeherdermozilla-central@c3e5497cff1c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwatt, smaug
bugs1362400
milestone55.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 1362400 part 1 - Make it possible for SMIL to change the link state of an SVG anchor element r=jwatt r=smaug (DOM)
dom/base/Link.h
dom/interfaces/events/nsIDOMMutationEvent.idl
dom/svg/SVGAElement.cpp
dom/svg/SVGAElement.h
dom/svg/nsSVGElement.cpp
layout/reftests/svg/smil/anim-targethref-10.svg
layout/reftests/svg/smil/reftest.list
layout/svg/nsSVGAFrame.cpp
--- a/dom/base/Link.h
+++ b/dom/base/Link.h
@@ -110,17 +110,17 @@ public:
    * @returns boolean
    *          Defaults to true; should be overridden for specialised cases
    */
   virtual bool HasDeferredDNSPrefetchRequest() { return true; }
 
   virtual size_t
     SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
-  bool ElementHasHref() const;
+  virtual bool ElementHasHref() const;
 
   // This is called by HTMLAnchorElement.
   void TryDNSPrefetch();
   void CancelDNSPrefetch(nsWrapperCache::FlagsType aDeferredFlag,
                          nsWrapperCache::FlagsType aRequestedFlag);
 
   // This is called by HTMLLinkElement.
   void TryDNSPrefetchPreconnectOrPrefetchOrPrerender();
--- a/dom/interfaces/events/nsIDOMMutationEvent.idl
+++ b/dom/interfaces/events/nsIDOMMutationEvent.idl
@@ -7,16 +7,17 @@
 #include "nsIDOMNode.idl"
 
 [builtinclass, uuid(30c9997f-bc4c-4890-b890-febb6ae3051b)]
 interface nsIDOMMutationEvent : nsISupports
 {
   const unsigned short      MODIFICATION       = 1;
   const unsigned short      ADDITION           = 2;
   const unsigned short      REMOVAL            = 3;
+  const unsigned short      SMIL               = 4;
 
   readonly attribute nsIDOMNode       relatedNode;
   readonly attribute DOMString        prevValue;
   readonly attribute DOMString        newValue;
   readonly attribute DOMString        attrName;
   readonly attribute unsigned short   attrChange;
   void                      initMutationEvent(in DOMString typeArg,
                                               in boolean canBubbleArg,
--- a/dom/svg/SVGAElement.cpp
+++ b/dom/svg/SVGAElement.cpp
@@ -76,16 +76,26 @@ already_AddRefed<SVGAnimatedString>
 SVGAElement::Href()
 {
   return mStringAttributes[HREF].IsExplicitlySet()
          ? mStringAttributes[HREF].ToDOMAnimatedString(this)
          : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this);
 }
 
 //----------------------------------------------------------------------
+// Link methods
+
+bool
+SVGAElement::ElementHasHref() const
+{
+  return mStringAttributes[HREF].IsExplicitlySet() ||
+         mStringAttributes[XLINK_HREF].IsExplicitlySet();
+}
+
+//----------------------------------------------------------------------
 // nsINode methods
 
 nsresult
 SVGAElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
 {
   nsresult rv = Element::GetEventTargetParent(aVisitor);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -272,36 +282,32 @@ SVGAElement::IsLink(nsIURI** aURI) const
 
   static nsIContent::AttrValuesArray sShowVals[] =
     { &nsGkAtoms::_empty, &nsGkAtoms::_new, &nsGkAtoms::replace, nullptr };
 
   static nsIContent::AttrValuesArray sActuateVals[] =
     { &nsGkAtoms::_empty, &nsGkAtoms::onRequest, nullptr };
 
   // Optimization: check for href first for early return
-  bool useXLink = !HasAttr(kNameSpaceID_None, nsGkAtoms::href);
-  const nsAttrValue* href =
-    useXLink
-    ? mAttrsAndChildren.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink)
-    : mAttrsAndChildren.GetAttr(nsGkAtoms::href, kNameSpaceID_None);
+  bool useBareHref = mStringAttributes[HREF].IsExplicitlySet();
 
-  if (href &&
+  if ((useBareHref || mStringAttributes[XLINK_HREF].IsExplicitlySet()) &&
       FindAttrValueIn(kNameSpaceID_XLink, nsGkAtoms::type,
                       sTypeVals, eCaseMatters) !=
                       nsIContent::ATTR_VALUE_NO_MATCH &&
       FindAttrValueIn(kNameSpaceID_XLink, nsGkAtoms::show,
                       sShowVals, eCaseMatters) !=
                       nsIContent::ATTR_VALUE_NO_MATCH &&
       FindAttrValueIn(kNameSpaceID_XLink, nsGkAtoms::actuate,
                       sActuateVals, eCaseMatters) !=
                       nsIContent::ATTR_VALUE_NO_MATCH) {
     nsCOMPtr<nsIURI> baseURI = GetBaseURI();
     // Get absolute URI
     nsAutoString str;
-    const uint8_t idx = useXLink ? XLINK_HREF : HREF;
+    const uint8_t idx = useBareHref ? HREF : XLINK_HREF;
     mStringAttributes[idx].GetAnimValue(str, this);
     nsContentUtils::NewURIWithDocumentCharset(aURI, str, OwnerDoc(), baseURI);
     // must promise out param is non-null if we return true
     return !!*aURI;
   }
 
   *aURI = nullptr;
   return false;
@@ -368,19 +374,17 @@ SVGAElement::UnsetAttr(int32_t aNameSpac
   // The ordering of the parent class's UnsetAttr call and Link::ResetLinkState
   // is important here!  The attribute is not unset until UnsetAttr returns, and
   // we will need the updated attribute value because notifying the document
   // that content states have changed will call IntrinsicState, which will try
   // to get updated information about the visitedness from Link.
   if (aAttr == nsGkAtoms::href &&
       (aNameSpaceID == kNameSpaceID_XLink ||
        aNameSpaceID == kNameSpaceID_None)) {
-    bool hasHref = HasAttr(kNameSpaceID_None, nsGkAtoms::href) ||
-                   HasAttr(kNameSpaceID_XLink, nsGkAtoms::href);
-    Link::ResetLinkState(!!aNotify, hasHref);
+    Link::ResetLinkState(!!aNotify, Link::ElementHasHref());
   }
 
   return rv;
 }
 
 //----------------------------------------------------------------------
 // nsSVGElement methods
 
--- a/dom/svg/SVGAElement.h
+++ b/dom/svg/SVGAElement.h
@@ -63,16 +63,19 @@ public:
     return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
   }
   virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                            nsIAtom* aPrefix, const nsAString& aValue,
                            bool aNotify) override;
   virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
                              bool aNotify) override;
 
+  // Link
+  virtual bool ElementHasHref() const override;
+
   // WebIDL
   already_AddRefed<SVGAnimatedString> Href();
   already_AddRefed<SVGAnimatedString> Target();
   void GetDownload(nsAString & aDownload);
   void SetDownload(const nsAString & aDownload, ErrorResult& rv);
 
   virtual void NodeInfoChanged(nsIDocument* aOldDoc) final override
   {
--- a/dom/svg/nsSVGElement.cpp
+++ b/dom/svg/nsSVGElement.cpp
@@ -1607,17 +1607,17 @@ nsSVGElement::DidAnimateLength(uint8_t a
   ClearAnyCachedPath();
 
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
     LengthAttributesInfo info = GetLengthInfo();
     frame->AttributeChanged(kNameSpaceID_None,
                             *info.mLengthInfo[aAttrEnum].mName,
-                            nsIDOMMutationEvent::MODIFICATION);
+                            nsIDOMMutationEvent::SMIL);
   }
 }
 
 nsSVGLength2*
 nsSVGElement::GetAnimatedLength(const nsIAtom *aAttrName)
 {
   LengthAttributesInfo lengthInfo = GetLengthInfo();
 
@@ -1704,17 +1704,17 @@ void
 nsSVGElement::DidAnimateLengthList(uint8_t aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
     LengthListAttributesInfo info = GetLengthListInfo();
     frame->AttributeChanged(kNameSpaceID_None,
                             *info.mLengthListInfo[aAttrEnum].mName,
-                            nsIDOMMutationEvent::MODIFICATION);
+                            nsIDOMMutationEvent::SMIL);
   }
 }
 
 void
 nsSVGElement::GetAnimatedLengthListValues(SVGUserUnitList *aFirst, ...)
 {
   LengthListAttributesInfo info = GetLengthListInfo();
 
@@ -1792,17 +1792,17 @@ nsSVGElement::DidAnimateNumberList(uint8
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
     NumberListAttributesInfo info = GetNumberListInfo();
     MOZ_ASSERT(aAttrEnum < info.mNumberListCount, "aAttrEnum out of range");
 
     frame->AttributeChanged(kNameSpaceID_None,
                             *info.mNumberListInfo[aAttrEnum].mName,
-                            nsIDOMMutationEvent::MODIFICATION);
+                            nsIDOMMutationEvent::SMIL);
   }
 }
 
 SVGAnimatedNumberList*
 nsSVGElement::GetAnimatedNumberList(uint8_t aAttrEnum)
 {
   NumberListAttributesInfo info = GetNumberListInfo();
   if (aAttrEnum < info.mNumberListCount) {
@@ -1853,17 +1853,17 @@ nsSVGElement::DidAnimatePointList()
 
   ClearAnyCachedPath();
 
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
     frame->AttributeChanged(kNameSpaceID_None,
                             GetPointListAttrName(),
-                            nsIDOMMutationEvent::MODIFICATION);
+                            nsIDOMMutationEvent::SMIL);
   }
 }
 
 nsAttrValue
 nsSVGElement::WillChangePathSegList()
 {
   MOZ_ASSERT(GetPathDataAttrName(),
              "Changing non-existent path seg list?");
@@ -1890,17 +1890,17 @@ nsSVGElement::DidAnimatePathSegList()
 
   ClearAnyCachedPath();
 
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
     frame->AttributeChanged(kNameSpaceID_None,
                             GetPathDataAttrName(),
-                            nsIDOMMutationEvent::MODIFICATION);
+                            nsIDOMMutationEvent::SMIL);
   }
 }
 
 nsSVGElement::NumberAttributesInfo
 nsSVGElement::GetNumberInfo()
 {
   return NumberAttributesInfo(nullptr, nullptr, 0);
 }
@@ -1931,17 +1931,17 @@ void
 nsSVGElement::DidAnimateNumber(uint8_t aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
     NumberAttributesInfo info = GetNumberInfo();
     frame->AttributeChanged(kNameSpaceID_None,
                             *info.mNumberInfo[aAttrEnum].mName,
-                            nsIDOMMutationEvent::MODIFICATION);
+                            nsIDOMMutationEvent::SMIL);
   }
 }
 
 void
 nsSVGElement::GetAnimatedNumberValues(float *aFirst, ...)
 {
   NumberAttributesInfo info = GetNumberInfo();
 
@@ -2001,17 +2001,17 @@ void
 nsSVGElement::DidAnimateNumberPair(uint8_t aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
     NumberPairAttributesInfo info = GetNumberPairInfo();
     frame->AttributeChanged(kNameSpaceID_None,
                             *info.mNumberPairInfo[aAttrEnum].mName,
-                            nsIDOMMutationEvent::MODIFICATION);
+                            nsIDOMMutationEvent::SMIL);
   }
 }
 
 nsSVGElement::IntegerAttributesInfo
 nsSVGElement::GetIntegerInfo()
 {
   return IntegerAttributesInfo(nullptr, nullptr, 0);
 }
@@ -2042,17 +2042,17 @@ void
 nsSVGElement::DidAnimateInteger(uint8_t aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
   
   if (frame) {
     IntegerAttributesInfo info = GetIntegerInfo();
     frame->AttributeChanged(kNameSpaceID_None,
                             *info.mIntegerInfo[aAttrEnum].mName,
-                            nsIDOMMutationEvent::MODIFICATION);
+                            nsIDOMMutationEvent::SMIL);
   }
 }
 
 void
 nsSVGElement::GetAnimatedIntegerValues(int32_t *aFirst, ...)
 {
   IntegerAttributesInfo info = GetIntegerInfo();
 
@@ -2113,17 +2113,17 @@ void
 nsSVGElement::DidAnimateIntegerPair(uint8_t aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
   
   if (frame) {
     IntegerPairAttributesInfo info = GetIntegerPairInfo();
     frame->AttributeChanged(kNameSpaceID_None,
                             *info.mIntegerPairInfo[aAttrEnum].mName,
-                            nsIDOMMutationEvent::MODIFICATION);
+                            nsIDOMMutationEvent::SMIL);
   }
 }
 
 nsSVGElement::AngleAttributesInfo
 nsSVGElement::GetAngleInfo()
 {
   return AngleAttributesInfo(nullptr, nullptr, 0);
 }
@@ -2161,17 +2161,17 @@ void
 nsSVGElement::DidAnimateAngle(uint8_t aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
     AngleAttributesInfo info = GetAngleInfo();
     frame->AttributeChanged(kNameSpaceID_None,
                             *info.mAngleInfo[aAttrEnum].mName,
-                            nsIDOMMutationEvent::MODIFICATION);
+                            nsIDOMMutationEvent::SMIL);
   }
 }
 
 nsSVGElement::BooleanAttributesInfo
 nsSVGElement::GetBooleanInfo()
 {
   return BooleanAttributesInfo(nullptr, nullptr, 0);
 }
@@ -2200,17 +2200,17 @@ void
 nsSVGElement::DidAnimateBoolean(uint8_t aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
   
   if (frame) {
     BooleanAttributesInfo info = GetBooleanInfo();
     frame->AttributeChanged(kNameSpaceID_None,
                             *info.mBooleanInfo[aAttrEnum].mName,
-                            nsIDOMMutationEvent::MODIFICATION);
+                            nsIDOMMutationEvent::SMIL);
   }
 }
 
 nsSVGElement::EnumAttributesInfo
 nsSVGElement::GetEnumInfo()
 {
   return EnumAttributesInfo(nullptr, nullptr, 0);
 }
@@ -2239,17 +2239,17 @@ void
 nsSVGElement::DidAnimateEnum(uint8_t aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
     EnumAttributesInfo info = GetEnumInfo();
     frame->AttributeChanged(kNameSpaceID_None,
                             *info.mEnumInfo[aAttrEnum].mName,
-                            nsIDOMMutationEvent::MODIFICATION);
+                            nsIDOMMutationEvent::SMIL);
   }
 }
 
 nsSVGViewBox *
 nsSVGElement::GetViewBox()
 {
   return nullptr;
 }
@@ -2276,17 +2276,17 @@ nsSVGElement::DidChangeViewBox(const nsA
 void
 nsSVGElement::DidAnimateViewBox()
 {
   nsIFrame* frame = GetPrimaryFrame();
   
   if (frame) {
     frame->AttributeChanged(kNameSpaceID_None,
                             nsGkAtoms::viewBox,
-                            nsIDOMMutationEvent::MODIFICATION);
+                            nsIDOMMutationEvent::SMIL);
   }
 }
 
 SVGAnimatedPreserveAspectRatio *
 nsSVGElement::GetPreserveAspectRatio()
 {
   return nullptr;
 }
@@ -2316,17 +2316,17 @@ nsSVGElement::DidChangePreserveAspectRat
 void
 nsSVGElement::DidAnimatePreserveAspectRatio()
 {
   nsIFrame* frame = GetPrimaryFrame();
   
   if (frame) {
     frame->AttributeChanged(kNameSpaceID_None,
                             nsGkAtoms::preserveAspectRatio,
-                            nsIDOMMutationEvent::MODIFICATION);
+                            nsIDOMMutationEvent::SMIL);
   }
 }
 
 nsAttrValue
 nsSVGElement::WillChangeTransformList()
 {
   return WillChangeValue(GetTransformListAttrName());
 }
@@ -2414,17 +2414,17 @@ void
 nsSVGElement::DidAnimateString(uint8_t aAttrEnum)
 {
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
     StringAttributesInfo info = GetStringInfo();
     frame->AttributeChanged(info.mStringInfo[aAttrEnum].mNamespaceID,
                             *info.mStringInfo[aAttrEnum].mName,
-                            nsIDOMMutationEvent::MODIFICATION);
+                            nsIDOMMutationEvent::SMIL);
   }
 }
 
 nsSVGElement::StringListAttributesInfo
 nsSVGElement::GetStringListInfo()
 {
   return StringListAttributesInfo(nullptr, nullptr, 0);
 }
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/anim-targethref-10.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+  <animate href="#x" attributeName="href" values="https://www.google.com"/>
+  <a id="x" fill="red"><rect x="15" y="15" width="200" height="200"/></a>
+  <style>
+    a:any-link {
+      fill: blue;
+    }
+  </style>
+</svg>
--- a/layout/reftests/svg/smil/reftest.list
+++ b/layout/reftests/svg/smil/reftest.list
@@ -200,16 +200,17 @@ fails-if(!stylo) == anim-strokecolor-1.s
 == anim-targethref-2.svg anim-standard-ref.svg
 == anim-targethref-3.svg anim-standard-ref.svg
 == anim-targethref-4.svg anim-standard-ref.svg
 == anim-targethref-5.svg anim-standard-ref.svg
 == anim-targethref-6.svg anim-standard-ref.svg
 == anim-targethref-7.svg anim-standard-ref.svg
 == anim-targethref-8.svg anim-standard-ref.svg
 == anim-targethref-9.svg anim-standard-ref.svg
+== anim-targethref-10.svg anim-standard-ref.svg
 
 == anim-text-rotate-01.svg anim-text-rotate-01-ref.svg
 == anim-feFuncR-tableValues-01.svg anim-feFuncR-tableValues-01-ref.svg
 
 skip == anim-text-x-y-dx-dy-01.svg anim-text-x-y-dx-dy-01-ref.svg # bug 579588
 
 == anim-width-done-1a.svg anim-standard-ref.svg
 == anim-width-done-1b.svg anim-standard-ref.svg
--- a/layout/svg/nsSVGAFrame.cpp
+++ b/layout/svg/nsSVGAFrame.cpp
@@ -2,16 +2,17 @@
 /* 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/. */
 
 // Keep in (case-insensitive) order:
 #include "gfxMatrix.h"
 #include "mozilla/dom/SVGAElement.h"
 #include "nsAutoPtr.h"
+#include "nsIDOMMutationEvent.h"
 #include "nsSVGContainerFrame.h"
 #include "nsSVGIntegrationUtils.h"
 #include "nsSVGUtils.h"
 #include "SVGLengthList.h"
 
 using namespace mozilla;
 
 class nsSVGAFrame : public nsSVGDisplayContainerFrame
@@ -89,16 +90,31 @@ nsSVGAFrame::AttributeChanged(int32_t   
       aAttribute == nsGkAtoms::transform) {
     // We don't invalidate for transform changes (the layers code does that).
     // Also note that SVGTransformableElement::GetAttributeChangeHint will
     // return nsChangeHint_UpdateOverflow for "transform" attribute changes
     // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
     NotifySVGChanged(TRANSFORM_CHANGED);
   }
 
+  // Currently our SMIL implementation does not modify the DOM attributes. Once
+  // we implement the SVG 2 SMIL behaviour this can be removed
+  // SVGAElement::SetAttr/UnsetAttr's ResetLinkState() call will be sufficient.
+  if (aModType == nsIDOMMutationEvent::SMIL &&
+      aAttribute == nsGkAtoms::href &&
+      (aNameSpaceID == kNameSpaceID_None ||
+       aNameSpaceID == kNameSpaceID_XLink)) {
+
+    dom::SVGAElement* content = static_cast<dom::SVGAElement*>(mContent);
+
+    // SMIL may change whether an <a> element is a link, in which case we will
+    // need to update the link state.
+    content->ResetLinkState(true, content->ElementHasHref());
+  }
+
  return NS_OK;
 }
 
 //----------------------------------------------------------------------
 // nsSVGDisplayableFrame methods
 
 void
 nsSVGAFrame::NotifySVGChanged(uint32_t aFlags)