tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SVGAnimationElement.cpp (13100B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/SVGAnimationElement.h"
      8 
      9 #include "mozilla/SMILAnimationController.h"
     10 #include "mozilla/SMILAnimationFunction.h"
     11 #include "mozilla/SMILTimeContainer.h"
     12 #include "mozilla/dom/BindContext.h"
     13 #include "mozilla/dom/ElementInlines.h"
     14 #include "mozilla/dom/SVGSVGElement.h"
     15 #include "mozilla/dom/SVGSwitchElement.h"
     16 #include "nsAttrValueOrString.h"
     17 #include "nsContentUtils.h"
     18 #include "nsIContentInlines.h"
     19 
     20 namespace mozilla::dom {
     21 
     22 //----------------------------------------------------------------------
     23 // nsISupports methods
     24 
     25 NS_IMPL_ADDREF_INHERITED(SVGAnimationElement, SVGAnimationElementBase)
     26 NS_IMPL_RELEASE_INHERITED(SVGAnimationElement, SVGAnimationElementBase)
     27 
     28 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGAnimationElement)
     29  NS_INTERFACE_MAP_ENTRY(mozilla::dom::SVGTests)
     30 NS_INTERFACE_MAP_END_INHERITING(SVGAnimationElementBase)
     31 
     32 NS_IMPL_CYCLE_COLLECTION_INHERITED(SVGAnimationElement, SVGAnimationElementBase,
     33                                   mHrefTarget, mTimedElement)
     34 
     35 //----------------------------------------------------------------------
     36 // Implementation
     37 
     38 SVGAnimationElement::SVGAnimationElement(
     39    already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
     40    : SVGAnimationElementBase(std::move(aNodeInfo)), mHrefTarget(this) {}
     41 
     42 nsresult SVGAnimationElement::Init() {
     43  nsresult rv = SVGAnimationElementBase::Init();
     44  NS_ENSURE_SUCCESS(rv, rv);
     45 
     46  mTimedElement.SetAnimationElement(this);
     47  AnimationFunction().SetAnimationElement(this);
     48  mTimedElement.SetTimeClient(&AnimationFunction());
     49 
     50  return NS_OK;
     51 }
     52 
     53 //----------------------------------------------------------------------
     54 
     55 Element* SVGAnimationElement::GetTargetElementContent() {
     56  if (HasAttr(kNameSpaceID_XLink, nsGkAtoms::href) ||
     57      HasAttr(nsGkAtoms::href)) {
     58    return mHrefTarget.get();
     59  }
     60  MOZ_ASSERT(!mHrefTarget.get(),
     61             "We shouldn't have a href target "
     62             "if we don't have an xlink:href or href attribute");
     63 
     64  // No "href" or "xlink:href" attribute --> I should target my parent.
     65  //
     66  // Note that we want to use GetParentElement instead of the flattened tree to
     67  // allow <use><animate>, for example.
     68  return GetParentElement();
     69 }
     70 
     71 bool SVGAnimationElement::GetTargetAttributeName(int32_t* aNamespaceID,
     72                                                 nsAtom** aLocalName) const {
     73  const nsAttrValue* nameAttr = mAttrs.GetAttr(nsGkAtoms::attributeName);
     74 
     75  if (!nameAttr) return false;
     76 
     77  NS_ASSERTION(nameAttr->Type() == nsAttrValue::eAtom,
     78               "attributeName should have been parsed as an atom");
     79 
     80  return NS_SUCCEEDED(nsContentUtils::SplitQName(
     81      this, nsDependentAtomString(nameAttr->GetAtomValue()), aNamespaceID,
     82      aLocalName));
     83 }
     84 
     85 SMILTimedElement& SVGAnimationElement::TimedElement() { return mTimedElement; }
     86 
     87 SVGElement* SVGAnimationElement::GetTargetElement() {
     88  FlushAnimations();
     89 
     90  // We'll just call the other GetTargetElement method, and QI to the right type
     91  return SVGElement::FromNodeOrNull(GetTargetElementContent());
     92 }
     93 
     94 float SVGAnimationElement::GetStartTime(ErrorResult& aRv) {
     95  FlushAnimations();
     96 
     97  SMILTimeValue startTime = mTimedElement.GetStartTime();
     98  if (!startTime.IsDefinite()) {
     99    aRv.ThrowInvalidStateError("Indefinite start time");
    100    return 0.f;
    101  }
    102 
    103  return float(double(startTime.GetMillis()) / PR_MSEC_PER_SEC);
    104 }
    105 
    106 float SVGAnimationElement::GetCurrentTimeAsFloat() {
    107  // Not necessary to call FlushAnimations() for this
    108 
    109  SMILTimeContainer* root = GetTimeContainer();
    110  if (root) {
    111    return float(double(root->GetCurrentTimeAsSMILTime()) / PR_MSEC_PER_SEC);
    112  }
    113 
    114  return 0.0f;
    115 }
    116 
    117 float SVGAnimationElement::GetSimpleDuration(ErrorResult& aRv) {
    118  // Not necessary to call FlushAnimations() for this
    119 
    120  SMILTimeValue simpleDur = mTimedElement.GetSimpleDuration();
    121  if (!simpleDur.IsDefinite()) {
    122    aRv.ThrowNotSupportedError("Duration is indefinite");
    123    return 0.f;
    124  }
    125 
    126  return float(double(simpleDur.GetMillis()) / PR_MSEC_PER_SEC);
    127 }
    128 
    129 //----------------------------------------------------------------------
    130 // nsIContent methods
    131 
    132 nsresult SVGAnimationElement::BindToTree(BindContext& aContext,
    133                                         nsINode& aParent) {
    134  MOZ_ASSERT(!mHrefTarget.get(),
    135             "Shouldn't have href-target yet (or it should've been cleared)");
    136  nsresult rv = SVGAnimationElementBase::BindToTree(aContext, aParent);
    137  NS_ENSURE_SUCCESS(rv, rv);
    138 
    139  // Add myself to the animation controller's master set of animation elements.
    140  if (Document* doc = aContext.GetComposedDoc()) {
    141    if (SMILAnimationController* controller = doc->GetAnimationController()) {
    142      controller->RegisterAnimationElement(this);
    143    }
    144    const nsAttrValue* href =
    145        HasAttr(nsGkAtoms::href)
    146            ? mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_None)
    147            : mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink);
    148    if (href) {
    149      nsAutoString hrefStr;
    150      href->ToString(hrefStr);
    151 
    152      UpdateHrefTarget(hrefStr);
    153    }
    154 
    155    mTimedElement.BindToTree(*this);
    156  }
    157 
    158  mTimedElement.SetIsDisabled(IsDisabled());
    159  AnimationNeedsResample();
    160 
    161  return NS_OK;
    162 }
    163 
    164 void SVGAnimationElement::UnbindFromTree(UnbindContext& aContext) {
    165  SMILAnimationController* controller = OwnerDoc()->GetAnimationController();
    166  if (controller) {
    167    controller->UnregisterAnimationElement(this);
    168  }
    169 
    170  mHrefTarget.Unlink();
    171  mTimedElement.DissolveReferences();
    172 
    173  AnimationNeedsResample();
    174 
    175  SVGAnimationElementBase::UnbindFromTree(aContext);
    176 }
    177 
    178 bool SVGAnimationElement::ParseAttribute(int32_t aNamespaceID,
    179                                         nsAtom* aAttribute,
    180                                         const nsAString& aValue,
    181                                         nsIPrincipal* aMaybeScriptedPrincipal,
    182                                         nsAttrValue& aResult) {
    183  if (aNamespaceID == kNameSpaceID_None) {
    184    // Deal with target-related attributes here
    185    if (aAttribute == nsGkAtoms::attributeName) {
    186      aResult.ParseAtom(aValue);
    187      AnimationNeedsResample();
    188      return true;
    189    }
    190 
    191    nsresult rv = NS_ERROR_FAILURE;
    192 
    193    // First let the animation function try to parse it...
    194    bool foundMatch =
    195        AnimationFunction().SetAttr(aAttribute, aValue, aResult, &rv);
    196 
    197    // ... and if that didn't recognize the attribute, let the timed element
    198    // try to parse it.
    199    if (!foundMatch) {
    200      foundMatch =
    201          mTimedElement.SetAttr(aAttribute, aValue, aResult, *this, &rv);
    202    }
    203 
    204    if (foundMatch) {
    205      AnimationNeedsResample();
    206      if (NS_FAILED(rv)) {
    207        ReportAttributeParseFailure(OwnerDoc(), aAttribute, aValue);
    208        return false;
    209      }
    210      return true;
    211    }
    212  }
    213 
    214  return SVGAnimationElementBase::ParseAttribute(
    215      aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult);
    216 }
    217 
    218 void SVGAnimationElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
    219                                       const nsAttrValue* aValue,
    220                                       const nsAttrValue* aOldValue,
    221                                       nsIPrincipal* aSubjectPrincipal,
    222                                       bool aNotify) {
    223  if (!aValue && aNamespaceID == kNameSpaceID_None) {
    224    // Attribute is being removed.
    225    if (AnimationFunction().UnsetAttr(aName) ||
    226        mTimedElement.UnsetAttr(aName)) {
    227      AnimationNeedsResample();
    228    }
    229  }
    230 
    231  SVGAnimationElementBase::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue,
    232                                        aSubjectPrincipal, aNotify);
    233 
    234  if (SVGTests::IsConditionalProcessingAttribute(aName)) {
    235    if (mTimedElement.SetIsDisabled(IsDisabled())) {
    236      AnimationNeedsResample();
    237    }
    238  }
    239 
    240  if (!IsInComposedDoc()) {
    241    return;
    242  }
    243 
    244  if (!((aNamespaceID == kNameSpaceID_None ||
    245         aNamespaceID == kNameSpaceID_XLink) &&
    246        aName == nsGkAtoms::href)) {
    247    return;
    248  }
    249 
    250  if (!aValue) {
    251    if (aNamespaceID == kNameSpaceID_None) {
    252      mHrefTarget.Unlink();
    253      AnimationTargetChanged();
    254 
    255      // After unsetting href, we may still have xlink:href, so we
    256      // should try to add it back.
    257      const nsAttrValue* xlinkHref =
    258          mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink);
    259      if (xlinkHref) {
    260        UpdateHrefTarget(nsAttrValueOrString(xlinkHref).String());
    261      }
    262    } else if (!HasAttr(nsGkAtoms::href)) {
    263      mHrefTarget.Unlink();
    264      AnimationTargetChanged();
    265    }  // else: we unset xlink:href, but we still have href attribute, so keep
    266       // mHrefTarget linking to href.
    267  } else if (!(aNamespaceID == kNameSpaceID_XLink &&
    268               HasAttr(nsGkAtoms::href))) {
    269    // Note: "href" takes priority over xlink:href. So if "xlink:href" is being
    270    // set here, we only let that update our target if "href" is *unset*.
    271    MOZ_ASSERT(aValue->Type() == nsAttrValue::eString ||
    272                   aValue->Type() == nsAttrValue::eAtom,
    273               "Expected href attribute to be string or atom type");
    274    UpdateHrefTarget(nsAttrValueOrString(aValue).String());
    275  }  // else: we're not yet in a document -- we'll update the target on
    276     // next BindToTree call.
    277 }
    278 
    279 bool SVGAnimationElement::IsDisabled() {
    280  if (!SVGTests::PassesConditionalProcessingTests()) {
    281    return true;
    282  }
    283  nsIContent* child = this;
    284  while (nsIContent* parent = child->GetFlattenedTreeParent()) {
    285    if (!parent->IsSVGElement()) {
    286      return false;
    287    }
    288    if (auto* svgSwitch = SVGSwitchElement::FromNodeOrNull(parent)) {
    289      nsIFrame* frame = svgSwitch->GetPrimaryFrame();
    290      // If we've been reflowed then the active child has been determined,
    291      // otherwise we'll have to calculate whether this is the active child.
    292      if (frame && !frame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
    293        if (child != svgSwitch->GetActiveChild()) {
    294          return true;
    295        }
    296      } else {
    297        if (child != SVGTests::FindActiveSwitchChild(svgSwitch)) {
    298          return true;
    299        }
    300      }
    301    } else if (auto* svgGraphics = SVGGraphicsElement::FromNode(parent)) {
    302      if (!svgGraphics->PassesConditionalProcessingTests()) {
    303        return true;
    304      }
    305    }
    306    child = parent;
    307  }
    308  return false;
    309 }
    310 
    311 //----------------------------------------------------------------------
    312 // SVG utility methods
    313 
    314 void SVGAnimationElement::ActivateByHyperlink() {
    315  FlushAnimations();
    316 
    317  // The behavior for when the target is an animation element is defined in
    318  // SMIL Animation:
    319  //   http://www.w3.org/TR/smil-animation/#HyperlinkSemantics
    320  SMILTimeValue seekTime = mTimedElement.GetHyperlinkTime();
    321  if (seekTime.IsDefinite()) {
    322    SMILTimeContainer* timeContainer = GetTimeContainer();
    323    if (timeContainer) {
    324      timeContainer->SetCurrentTime(seekTime.GetMillis());
    325      AnimationNeedsResample();
    326      // As with SVGSVGElement::SetCurrentTime, we need to trigger
    327      // a synchronous sample now.
    328      FlushAnimations();
    329    }
    330    // else, silently fail. We mustn't be part of an SVG document fragment that
    331    // is attached to the document tree so there's nothing we can do here
    332  } else {
    333    BeginElement(IgnoreErrors());
    334  }
    335 }
    336 
    337 //----------------------------------------------------------------------
    338 // Implementation helpers
    339 
    340 SMILTimeContainer* SVGAnimationElement::GetTimeContainer() {
    341  SVGSVGElement* element = SVGContentUtils::GetOuterSVGElement(this);
    342 
    343  if (element) {
    344    return element->GetTimedDocumentRoot();
    345  }
    346 
    347  return nullptr;
    348 }
    349 
    350 void SVGAnimationElement::BeginElementAt(float offset, ErrorResult& aRv) {
    351  // Make sure the timegraph is up-to-date
    352  FlushAnimations();
    353 
    354  // This will fail if we're not attached to a time container (SVG document
    355  // fragment).
    356  aRv = mTimedElement.BeginElementAt(offset);
    357  if (aRv.Failed()) return;
    358 
    359  AnimationNeedsResample();
    360  // Force synchronous sample so that events resulting from this call arrive in
    361  // the expected order and we get an up-to-date paint.
    362  FlushAnimations();
    363 }
    364 
    365 void SVGAnimationElement::EndElementAt(float offset, ErrorResult& aRv) {
    366  // Make sure the timegraph is up-to-date
    367  FlushAnimations();
    368 
    369  aRv = mTimedElement.EndElementAt(offset);
    370  if (aRv.Failed()) return;
    371 
    372  AnimationNeedsResample();
    373  // Force synchronous sample
    374  FlushAnimations();
    375 }
    376 
    377 bool SVGAnimationElement::IsEventAttributeNameInternal(nsAtom* aName) {
    378  return nsContentUtils::IsEventAttributeName(aName, EventNameType_SMIL);
    379 }
    380 
    381 void SVGAnimationElement::UpdateHrefTarget(const nsAString& aHrefStr) {
    382  if (nsContentUtils::IsLocalRefURL(aHrefStr)) {
    383    mHrefTarget.ResetToLocalFragmentID(*this, aHrefStr);
    384  } else {
    385    mHrefTarget.Unlink();
    386  }
    387  AnimationTargetChanged();
    388 }
    389 
    390 void SVGAnimationElement::AnimationTargetChanged() {
    391  mTimedElement.HandleTargetElementChange(GetTargetElementContent());
    392  AnimationNeedsResample();
    393 }
    394 
    395 }  // namespace mozilla::dom