| /* |
| * Copyright (C) 2008 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/core/svg/animation/svg_smil_element.h" |
| |
| #include <algorithm> |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_event_listener.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/events/event.h" |
| #include "third_party/blink/renderer/core/dom/events/event_listener.h" |
| #include "third_party/blink/renderer/core/dom/id_target_observer.h" |
| #include "third_party/blink/renderer/core/frame/use_counter.h" |
| #include "third_party/blink/renderer/core/svg/animation/smil_time_container.h" |
| #include "third_party/blink/renderer/core/svg/svg_svg_element.h" |
| #include "third_party/blink/renderer/core/svg/svg_uri_reference.h" |
| #include "third_party/blink/renderer/core/xlink_names.h" |
| #include "third_party/blink/renderer/platform/heap/handle.h" |
| #include "third_party/blink/renderer/platform/wtf/functional.h" |
| #include "third_party/blink/renderer/platform/wtf/math_extras.h" |
| #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" |
| #include "third_party/blink/renderer/platform/wtf/vector.h" |
| |
| namespace blink { |
| |
| class RepeatEvent final : public Event { |
| public: |
| static RepeatEvent* Create(const AtomicString& type, int repeat) { |
| return new RepeatEvent(type, Bubbles::kNo, Cancelable::kNo, repeat); |
| } |
| |
| ~RepeatEvent() override = default; |
| |
| int Repeat() const { return repeat_; } |
| |
| void Trace(blink::Visitor* visitor) override { Event::Trace(visitor); } |
| |
| protected: |
| RepeatEvent(const AtomicString& type, |
| Bubbles bubbles, |
| Cancelable cancelable, |
| int repeat = -1) |
| : Event(type, bubbles, cancelable), repeat_(repeat) {} |
| |
| private: |
| int repeat_; |
| }; |
| |
| inline RepeatEvent* ToRepeatEvent(Event* event) { |
| SECURITY_DCHECK(!event || event->type() == "repeatn"); |
| return static_cast<RepeatEvent*>(event); |
| } |
| |
| // This is used for duration type time values that can't be negative. |
| static const double kInvalidCachedTime = -1.; |
| |
| class ConditionEventListener final : public EventListener { |
| public: |
| static ConditionEventListener* Create(SVGSMILElement* animation, |
| SVGSMILElement::Condition* condition) { |
| return new ConditionEventListener(animation, condition); |
| } |
| |
| static const ConditionEventListener* Cast(const EventListener* listener) { |
| return listener->GetType() == kConditionEventListenerType |
| ? static_cast<const ConditionEventListener*>(listener) |
| : nullptr; |
| } |
| |
| bool operator==(const EventListener& other) const override; |
| |
| void DisconnectAnimation() { animation_ = nullptr; } |
| |
| void Trace(blink::Visitor* visitor) override { |
| visitor->Trace(animation_); |
| visitor->Trace(condition_); |
| EventListener::Trace(visitor); |
| } |
| |
| private: |
| ConditionEventListener(SVGSMILElement* animation, |
| SVGSMILElement::Condition* condition) |
| : EventListener(kConditionEventListenerType), |
| animation_(animation), |
| condition_(condition) {} |
| |
| void Invoke(ExecutionContext*, Event*) override; |
| |
| Member<SVGSMILElement> animation_; |
| Member<SVGSMILElement::Condition> condition_; |
| }; |
| |
| bool ConditionEventListener::operator==(const EventListener& listener) const { |
| if (const ConditionEventListener* condition_event_listener = |
| ConditionEventListener::Cast(&listener)) |
| return animation_ == condition_event_listener->animation_ && |
| condition_ == condition_event_listener->condition_; |
| return false; |
| } |
| |
| void ConditionEventListener::Invoke(ExecutionContext*, Event* event) { |
| if (!animation_) |
| return; |
| if (event->type() == "repeatn" && |
| ToRepeatEvent(event)->Repeat() != condition_->Repeat()) |
| return; |
| animation_->AddInstanceTime(condition_->GetBeginOrEnd(), |
| animation_->Elapsed() + condition_->Offset()); |
| } |
| |
| SVGSMILElement::Condition::Condition(Type type, |
| BeginOrEnd begin_or_end, |
| const AtomicString& base_id, |
| const AtomicString& name, |
| SMILTime offset, |
| int repeat) |
| : type_(type), |
| begin_or_end_(begin_or_end), |
| base_id_(base_id), |
| name_(name), |
| offset_(offset), |
| repeat_(repeat) {} |
| |
| SVGSMILElement::Condition::~Condition() = default; |
| |
| void SVGSMILElement::Condition::Trace(blink::Visitor* visitor) { |
| visitor->Trace(base_element_); |
| visitor->Trace(base_id_observer_); |
| visitor->Trace(event_listener_); |
| } |
| |
| void SVGSMILElement::Condition::ConnectSyncBase(SVGSMILElement& timed_element) { |
| DCHECK(!base_id_.IsEmpty()); |
| DCHECK_EQ(type_, kSyncbase); |
| Element* element = timed_element.GetTreeScope().getElementById(base_id_); |
| if (!element || !IsSVGSMILElement(*element)) { |
| base_element_ = nullptr; |
| return; |
| } |
| base_element_ = ToSVGSMILElement(element); |
| ToSVGSMILElement(*element).AddSyncBaseDependent(timed_element); |
| } |
| |
| void SVGSMILElement::Condition::DisconnectSyncBase( |
| SVGSMILElement& timed_element) { |
| DCHECK_EQ(type_, kSyncbase); |
| if (!base_element_) |
| return; |
| ToSVGSMILElement(*base_element_).RemoveSyncBaseDependent(timed_element); |
| base_element_ = nullptr; |
| } |
| |
| void SVGSMILElement::Condition::ConnectEventBase( |
| SVGSMILElement& timed_element) { |
| DCHECK_EQ(type_, kEventBase); |
| DCHECK(!base_element_); |
| Element* target; |
| if (base_id_.IsEmpty()) { |
| target = timed_element.targetElement(); |
| } else { |
| target = SVGURIReference::ObserveTarget( |
| base_id_observer_, timed_element.GetTreeScope(), base_id_, |
| WTF::BindRepeating(&SVGSMILElement::BuildPendingResource, |
| WrapWeakPersistent(&timed_element))); |
| } |
| if (!target || !target->IsSVGElement()) |
| return; |
| DCHECK(!event_listener_); |
| event_listener_ = ConditionEventListener::Create(&timed_element, this); |
| base_element_ = ToSVGElement(target); |
| base_element_->addEventListener(name_, event_listener_, false); |
| timed_element.AddReferenceTo(base_element_); |
| } |
| |
| void SVGSMILElement::Condition::DisconnectEventBase( |
| SVGSMILElement& timed_element) { |
| DCHECK_EQ(type_, kEventBase); |
| SVGURIReference::UnobserveTarget(base_id_observer_); |
| if (!event_listener_) |
| return; |
| base_element_->removeEventListener(name_, event_listener_, false); |
| base_element_ = nullptr; |
| event_listener_->DisconnectAnimation(); |
| event_listener_ = nullptr; |
| } |
| |
| SVGSMILElement::SVGSMILElement(const QualifiedName& tag_name, Document& doc) |
| : SVGElement(tag_name, doc), |
| SVGTests(this), |
| attribute_name_(AnyQName()), |
| target_element_(nullptr), |
| sync_base_conditions_connected_(false), |
| has_end_event_conditions_(false), |
| is_waiting_for_first_interval_(true), |
| is_scheduled_(false), |
| interval_(SMILInterval(SMILTime::Unresolved(), SMILTime::Unresolved())), |
| previous_interval_begin_(SMILTime::Unresolved()), |
| active_state_(kInactive), |
| restart_(kRestartAlways), |
| fill_(kFillRemove), |
| last_percent_(0), |
| last_repeat_(0), |
| next_progress_time_(0), |
| document_order_index_(0), |
| cached_dur_(kInvalidCachedTime), |
| cached_repeat_dur_(kInvalidCachedTime), |
| cached_repeat_count_(kInvalidCachedTime), |
| cached_min_(kInvalidCachedTime), |
| cached_max_(kInvalidCachedTime) { |
| ResolveFirstInterval(); |
| } |
| |
| SVGSMILElement::~SVGSMILElement() = default; |
| |
| void SVGSMILElement::ClearResourceAndEventBaseReferences() { |
| SVGURIReference::UnobserveTarget(target_id_observer_); |
| RemoveAllOutgoingReferences(); |
| } |
| |
| void SVGSMILElement::ClearConditions() { |
| DisconnectSyncBaseConditions(); |
| DisconnectEventBaseConditions(); |
| conditions_.clear(); |
| } |
| |
| void SVGSMILElement::BuildPendingResource() { |
| ClearResourceAndEventBaseReferences(); |
| DisconnectEventBaseConditions(); |
| |
| if (!isConnected()) { |
| // Reset the target element if we are no longer in the document. |
| SetTargetElement(nullptr); |
| return; |
| } |
| |
| const AtomicString& href = SVGURIReference::LegacyHrefString(*this); |
| Element* target; |
| if (href.IsEmpty()) { |
| target = parentElement(); |
| } else { |
| target = SVGURIReference::ObserveTarget(target_id_observer_, *this, href); |
| } |
| SVGElement* svg_target = |
| target && target->IsSVGElement() ? ToSVGElement(target) : nullptr; |
| |
| if (svg_target && !svg_target->isConnected()) |
| svg_target = nullptr; |
| |
| if (svg_target != targetElement()) |
| SetTargetElement(svg_target); |
| |
| if (svg_target) { |
| // Register us with the target in the dependencies map. Any change of |
| // hrefElement that leads to relayout/repainting now informs us, so we can |
| // react to it. |
| AddReferenceTo(svg_target); |
| } |
| ConnectEventBaseConditions(); |
| } |
| |
| static inline void ClearTimesWithDynamicOrigins( |
| Vector<SMILTimeWithOrigin>& time_list) { |
| for (int i = time_list.size() - 1; i >= 0; --i) { |
| if (time_list[i].OriginIsScript()) |
| time_list.EraseAt(i); |
| } |
| } |
| |
| void SVGSMILElement::Reset() { |
| ClearAnimatedType(); |
| |
| active_state_ = kInactive; |
| is_waiting_for_first_interval_ = true; |
| interval_.begin = SMILTime::Unresolved(); |
| interval_.end = SMILTime::Unresolved(); |
| previous_interval_begin_ = SMILTime::Unresolved(); |
| last_percent_ = 0; |
| last_repeat_ = 0; |
| next_progress_time_ = 0; |
| ResolveFirstInterval(); |
| } |
| |
| Node::InsertionNotificationRequest SVGSMILElement::InsertedInto( |
| ContainerNode& root_parent) { |
| SVGElement::InsertedInto(root_parent); |
| |
| if (!root_parent.isConnected()) |
| return kInsertionDone; |
| |
| UseCounter::Count(GetDocument(), WebFeature::kSVGSMILElementInDocument); |
| if (GetDocument().IsLoadCompleted()) { |
| UseCounter::Count(&GetDocument(), |
| WebFeature::kSVGSMILElementInsertedAfterLoad); |
| } |
| |
| SVGSVGElement* owner = ownerSVGElement(); |
| if (!owner) |
| return kInsertionDone; |
| |
| time_container_ = owner->TimeContainer(); |
| DCHECK(time_container_); |
| time_container_->SetDocumentOrderIndexesDirty(); |
| |
| // "If no attribute is present, the default begin value (an offset-value of 0) |
| // must be evaluated." |
| if (!FastHasAttribute(svg_names::kBeginAttr)) |
| begin_times_.push_back(SMILTimeWithOrigin()); |
| |
| if (is_waiting_for_first_interval_) |
| ResolveFirstInterval(); |
| |
| if (time_container_) |
| time_container_->NotifyIntervalsChanged(); |
| |
| BuildPendingResource(); |
| |
| return kInsertionDone; |
| } |
| |
| void SVGSMILElement::RemovedFrom(ContainerNode& root_parent) { |
| if (root_parent.isConnected()) { |
| ClearResourceAndEventBaseReferences(); |
| ClearConditions(); |
| SetTargetElement(nullptr); |
| AnimationAttributeChanged(); |
| time_container_ = nullptr; |
| } |
| |
| SVGElement::RemovedFrom(root_parent); |
| } |
| |
| SMILTime SVGSMILElement::ParseOffsetValue(const String& data) { |
| bool ok; |
| double result = 0; |
| String parse = data.StripWhiteSpace(); |
| if (parse.EndsWith('h')) |
| result = parse.Left(parse.length() - 1).ToDouble(&ok) * 60 * 60; |
| else if (parse.EndsWith("min")) |
| result = parse.Left(parse.length() - 3).ToDouble(&ok) * 60; |
| else if (parse.EndsWith("ms")) |
| result = parse.Left(parse.length() - 2).ToDouble(&ok) / 1000; |
| else if (parse.EndsWith('s')) |
| result = parse.Left(parse.length() - 1).ToDouble(&ok); |
| else |
| result = parse.ToDouble(&ok); |
| if (!ok || !SMILTime(result).IsFinite()) |
| return SMILTime::Unresolved(); |
| return result; |
| } |
| |
| SMILTime SVGSMILElement::ParseClockValue(const String& data) { |
| if (data.IsNull()) |
| return SMILTime::Unresolved(); |
| |
| String parse = data.StripWhiteSpace(); |
| |
| DEFINE_STATIC_LOCAL(const AtomicString, indefinite_value, ("indefinite")); |
| if (parse == indefinite_value) |
| return SMILTime::Indefinite(); |
| |
| double result = 0; |
| bool ok; |
| wtf_size_t double_point_one = parse.find(':'); |
| wtf_size_t double_point_two = parse.find(':', double_point_one + 1); |
| if (double_point_one == 2 && double_point_two == 5 && parse.length() >= 8) { |
| result += parse.Substring(0, 2).ToUIntStrict(&ok) * 60 * 60; |
| if (!ok) |
| return SMILTime::Unresolved(); |
| result += parse.Substring(3, 2).ToUIntStrict(&ok) * 60; |
| if (!ok) |
| return SMILTime::Unresolved(); |
| result += parse.Substring(6).ToDouble(&ok); |
| } else if (double_point_one == 2 && double_point_two == kNotFound && |
| parse.length() >= 5) { |
| result += parse.Substring(0, 2).ToUIntStrict(&ok) * 60; |
| if (!ok) |
| return SMILTime::Unresolved(); |
| result += parse.Substring(3).ToDouble(&ok); |
| } else |
| return ParseOffsetValue(parse); |
| |
| if (!ok || !SMILTime(result).IsFinite()) |
| return SMILTime::Unresolved(); |
| return result; |
| } |
| |
| static void SortTimeList(Vector<SMILTimeWithOrigin>& time_list) { |
| std::sort(time_list.begin(), time_list.end()); |
| } |
| |
| bool SVGSMILElement::ParseCondition(const String& value, |
| BeginOrEnd begin_or_end) { |
| String parse_string = value.StripWhiteSpace(); |
| |
| double sign = 1.; |
| bool ok; |
| wtf_size_t pos = parse_string.find('+'); |
| if (pos == kNotFound) { |
| pos = parse_string.find('-'); |
| if (pos != kNotFound) |
| sign = -1.; |
| } |
| String condition_string; |
| SMILTime offset = 0; |
| if (pos == kNotFound) |
| condition_string = parse_string; |
| else { |
| condition_string = parse_string.Left(pos).StripWhiteSpace(); |
| String offset_string = parse_string.Substring(pos + 1).StripWhiteSpace(); |
| offset = ParseOffsetValue(offset_string); |
| if (offset.IsUnresolved()) |
| return false; |
| offset = offset * sign; |
| } |
| if (condition_string.IsEmpty()) |
| return false; |
| pos = condition_string.find('.'); |
| |
| String base_id; |
| String name_string; |
| if (pos == kNotFound) |
| name_string = condition_string; |
| else { |
| base_id = condition_string.Left(pos); |
| name_string = condition_string.Substring(pos + 1); |
| } |
| if (name_string.IsEmpty()) |
| return false; |
| |
| Condition::Type type; |
| int repeat = -1; |
| if (name_string.StartsWith("repeat(") && name_string.EndsWith(')')) { |
| repeat = |
| name_string.Substring(7, name_string.length() - 8).ToUIntStrict(&ok); |
| if (!ok) |
| return false; |
| name_string = "repeatn"; |
| type = Condition::kEventBase; |
| } else if (name_string == "begin" || name_string == "end") { |
| if (base_id.IsEmpty()) |
| return false; |
| UseCounter::Count(&GetDocument(), |
| WebFeature::kSVGSMILBeginOrEndSyncbaseValue); |
| type = Condition::kSyncbase; |
| } else if (name_string.StartsWith("accesskey(")) { |
| // FIXME: accesskey() support. |
| type = Condition::kAccessKey; |
| } else { |
| UseCounter::Count(&GetDocument(), WebFeature::kSVGSMILBeginOrEndEventValue); |
| type = Condition::kEventBase; |
| } |
| |
| conditions_.push_back( |
| Condition::Create(type, begin_or_end, AtomicString(base_id), |
| AtomicString(name_string), offset, repeat)); |
| |
| if (type == Condition::kEventBase && begin_or_end == kEnd) |
| has_end_event_conditions_ = true; |
| |
| return true; |
| } |
| |
| void SVGSMILElement::ParseBeginOrEnd(const String& parse_string, |
| BeginOrEnd begin_or_end) { |
| Vector<SMILTimeWithOrigin>& time_list = |
| begin_or_end == kBegin ? begin_times_ : end_times_; |
| if (begin_or_end == kEnd) |
| has_end_event_conditions_ = false; |
| HashSet<SMILTime> existing; |
| for (unsigned n = 0; n < time_list.size(); ++n) { |
| if (!time_list[n].Time().IsUnresolved()) |
| existing.insert(time_list[n].Time().Value()); |
| } |
| Vector<String> split_string; |
| parse_string.Split(';', split_string); |
| for (unsigned n = 0; n < split_string.size(); ++n) { |
| SMILTime value = ParseClockValue(split_string[n]); |
| if (value.IsUnresolved()) |
| ParseCondition(split_string[n], begin_or_end); |
| else if (!existing.Contains(value.Value())) |
| time_list.push_back( |
| SMILTimeWithOrigin(value, SMILTimeWithOrigin::kParserOrigin)); |
| } |
| SortTimeList(time_list); |
| } |
| |
| void SVGSMILElement::ParseAttribute(const AttributeModificationParams& params) { |
| const QualifiedName& name = params.name; |
| const AtomicString& value = params.new_value; |
| if (name == svg_names::kBeginAttr) { |
| if (!conditions_.IsEmpty()) { |
| ClearConditions(); |
| ParseBeginOrEnd(FastGetAttribute(svg_names::kEndAttr), kEnd); |
| } |
| ParseBeginOrEnd(value.GetString(), kBegin); |
| if (isConnected()) { |
| ConnectSyncBaseConditions(); |
| ConnectEventBaseConditions(); |
| BeginListChanged(Elapsed()); |
| } |
| AnimationAttributeChanged(); |
| } else if (name == svg_names::kEndAttr) { |
| if (!conditions_.IsEmpty()) { |
| ClearConditions(); |
| ParseBeginOrEnd(FastGetAttribute(svg_names::kBeginAttr), kBegin); |
| } |
| ParseBeginOrEnd(value.GetString(), kEnd); |
| if (isConnected()) { |
| ConnectSyncBaseConditions(); |
| ConnectEventBaseConditions(); |
| EndListChanged(Elapsed()); |
| } |
| AnimationAttributeChanged(); |
| } else if (name == svg_names::kOnbeginAttr) { |
| SetAttributeEventListener(event_type_names::kBeginEvent, |
| CreateAttributeEventListener(this, name, value)); |
| } else if (name == svg_names::kOnendAttr) { |
| SetAttributeEventListener(event_type_names::kEndEvent, |
| CreateAttributeEventListener(this, name, value)); |
| } else if (name == svg_names::kOnrepeatAttr) { |
| SetAttributeEventListener(event_type_names::kRepeatEvent, |
| CreateAttributeEventListener(this, name, value)); |
| } else if (name == svg_names::kRestartAttr) { |
| if (value == "never") |
| restart_ = kRestartNever; |
| else if (value == "whenNotActive") |
| restart_ = kRestartWhenNotActive; |
| else |
| restart_ = kRestartAlways; |
| } else if (name == svg_names::kFillAttr) { |
| fill_ = value == "freeze" ? kFillFreeze : kFillRemove; |
| } else { |
| SVGElement::ParseAttribute(params); |
| } |
| } |
| |
| void SVGSMILElement::SvgAttributeChanged(const QualifiedName& attr_name) { |
| if (attr_name == svg_names::kDurAttr) { |
| cached_dur_ = kInvalidCachedTime; |
| } else if (attr_name == svg_names::kRepeatDurAttr) { |
| cached_repeat_dur_ = kInvalidCachedTime; |
| } else if (attr_name == svg_names::kRepeatCountAttr) { |
| cached_repeat_count_ = kInvalidCachedTime; |
| } else if (attr_name == svg_names::kMinAttr) { |
| cached_min_ = kInvalidCachedTime; |
| } else if (attr_name == svg_names::kMaxAttr) { |
| cached_max_ = kInvalidCachedTime; |
| } else if (attr_name.Matches(svg_names::kHrefAttr) || |
| attr_name.Matches(xlink_names::kHrefAttr)) { |
| // TODO(fs): Could be smarter here when 'href' is specified and 'xlink:href' |
| // is changed. |
| SVGElement::InvalidationGuard invalidation_guard(this); |
| BuildPendingResource(); |
| } else { |
| SVGElement::SvgAttributeChanged(attr_name); |
| return; |
| } |
| |
| AnimationAttributeChanged(); |
| } |
| |
| void SVGSMILElement::ConnectSyncBaseConditions() { |
| if (sync_base_conditions_connected_) |
| DisconnectSyncBaseConditions(); |
| sync_base_conditions_connected_ = true; |
| for (Condition* condition : conditions_) { |
| if (condition->GetType() == Condition::kSyncbase) |
| condition->ConnectSyncBase(*this); |
| } |
| } |
| |
| void SVGSMILElement::DisconnectSyncBaseConditions() { |
| if (!sync_base_conditions_connected_) |
| return; |
| sync_base_conditions_connected_ = false; |
| for (Condition* condition : conditions_) { |
| if (condition->GetType() == Condition::kSyncbase) |
| condition->DisconnectSyncBase(*this); |
| } |
| } |
| |
| void SVGSMILElement::ConnectEventBaseConditions() { |
| DisconnectEventBaseConditions(); |
| for (Condition* condition : conditions_) { |
| if (condition->GetType() == Condition::kEventBase) |
| condition->ConnectEventBase(*this); |
| } |
| } |
| |
| void SVGSMILElement::DisconnectEventBaseConditions() { |
| for (Condition* condition : conditions_) { |
| if (condition->GetType() == Condition::kEventBase) |
| condition->DisconnectEventBase(*this); |
| } |
| } |
| |
| void SVGSMILElement::SetTargetElement(SVGElement* target) { |
| WillChangeAnimationTarget(); |
| |
| // Clear values that may depend on the previous target. |
| if (target_element_) |
| DisconnectSyncBaseConditions(); |
| |
| // If the animation state is not Inactive, always reset to a clear state |
| // before leaving the old target element. |
| if (GetActiveState() != kInactive) |
| EndedActiveInterval(); |
| |
| target_element_ = target; |
| DidChangeAnimationTarget(); |
| |
| // If the animation is scheduled and there's an active interval, then |
| // revalidate the animation value. |
| if (GetActiveState() != kInactive && is_scheduled_) |
| StartedActiveInterval(); |
| } |
| |
| SMILTime SVGSMILElement::Elapsed() const { |
| return time_container_ ? time_container_->Elapsed() : 0; |
| } |
| |
| bool SVGSMILElement::IsFrozen() const { |
| return GetActiveState() == kFrozen; |
| } |
| |
| SMILTime SVGSMILElement::Dur() const { |
| if (cached_dur_ != kInvalidCachedTime) |
| return cached_dur_; |
| const AtomicString& value = FastGetAttribute(svg_names::kDurAttr); |
| SMILTime clock_value = ParseClockValue(value); |
| return cached_dur_ = clock_value <= 0 ? SMILTime::Unresolved() : clock_value; |
| } |
| |
| SMILTime SVGSMILElement::RepeatDur() const { |
| if (cached_repeat_dur_ != kInvalidCachedTime) |
| return cached_repeat_dur_; |
| const AtomicString& value = FastGetAttribute(svg_names::kRepeatDurAttr); |
| SMILTime clock_value = ParseClockValue(value); |
| cached_repeat_dur_ = clock_value <= 0 ? SMILTime::Unresolved() : clock_value; |
| return cached_repeat_dur_; |
| } |
| |
| // So a count is not really a time but let just all pretend we did not notice. |
| SMILTime SVGSMILElement::RepeatCount() const { |
| if (cached_repeat_count_ != kInvalidCachedTime) |
| return cached_repeat_count_; |
| SMILTime computed_repeat_count = SMILTime::Unresolved(); |
| const AtomicString& value = FastGetAttribute(svg_names::kRepeatCountAttr); |
| if (!value.IsNull()) { |
| DEFINE_STATIC_LOCAL(const AtomicString, indefinite_value, ("indefinite")); |
| if (value == indefinite_value) { |
| computed_repeat_count = SMILTime::Indefinite(); |
| } else { |
| bool ok; |
| double result = value.ToDouble(&ok); |
| if (ok && result > 0) |
| computed_repeat_count = result; |
| } |
| } |
| cached_repeat_count_ = computed_repeat_count; |
| return cached_repeat_count_; |
| } |
| |
| SMILTime SVGSMILElement::MaxValue() const { |
| if (cached_max_ != kInvalidCachedTime) |
| return cached_max_; |
| const AtomicString& value = FastGetAttribute(svg_names::kMaxAttr); |
| SMILTime result = ParseClockValue(value); |
| return cached_max_ = (result.IsUnresolved() || result <= 0) |
| ? SMILTime::Indefinite() |
| : result; |
| } |
| |
| SMILTime SVGSMILElement::MinValue() const { |
| if (cached_min_ != kInvalidCachedTime) |
| return cached_min_; |
| const AtomicString& value = FastGetAttribute(svg_names::kMinAttr); |
| SMILTime result = ParseClockValue(value); |
| return cached_min_ = (result.IsUnresolved() || result < 0) ? 0 : result; |
| } |
| |
| SMILTime SVGSMILElement::SimpleDuration() const { |
| return std::min(Dur(), SMILTime::Indefinite()); |
| } |
| |
| void SVGSMILElement::AddInstanceTime(BeginOrEnd begin_or_end, |
| SMILTime time, |
| SMILTimeWithOrigin::Origin origin) { |
| SMILTime elapsed = this->Elapsed(); |
| if (elapsed.IsUnresolved()) |
| return; |
| Vector<SMILTimeWithOrigin>& list = |
| begin_or_end == kBegin ? begin_times_ : end_times_; |
| list.push_back(SMILTimeWithOrigin(time, origin)); |
| SortTimeList(list); |
| if (begin_or_end == kBegin) |
| BeginListChanged(elapsed); |
| else |
| EndListChanged(elapsed); |
| } |
| |
| inline bool CompareTimes(const SMILTimeWithOrigin& left, |
| const SMILTimeWithOrigin& right) { |
| return left.Time() < right.Time(); |
| } |
| |
| SMILTime SVGSMILElement::FindInstanceTime(BeginOrEnd begin_or_end, |
| SMILTime minimum_time, |
| bool equals_minimum_ok) const { |
| const Vector<SMILTimeWithOrigin>& list = |
| begin_or_end == kBegin ? begin_times_ : end_times_; |
| int size_of_list = list.size(); |
| |
| if (!size_of_list) |
| return begin_or_end == kBegin ? SMILTime::Unresolved() |
| : SMILTime::Indefinite(); |
| |
| const SMILTimeWithOrigin dummy_time_with_origin( |
| minimum_time, SMILTimeWithOrigin::kParserOrigin); |
| const SMILTimeWithOrigin* result = std::lower_bound( |
| list.begin(), list.end(), dummy_time_with_origin, CompareTimes); |
| int index_of_result = static_cast<int>(result - list.begin()); |
| if (index_of_result == size_of_list) |
| return SMILTime::Unresolved(); |
| const SMILTime& current_time = list[index_of_result].Time(); |
| |
| // The special value "indefinite" does not yield an instance time in the begin |
| // list. |
| if (current_time.IsIndefinite() && begin_or_end == kBegin) |
| return SMILTime::Unresolved(); |
| |
| if (current_time > minimum_time) |
| return current_time; |
| |
| DCHECK(current_time == minimum_time); |
| if (equals_minimum_ok) |
| return current_time; |
| |
| // If the equals is not accepted, return the next bigger item in the list. |
| SMILTime next_time = current_time; |
| while (index_of_result < size_of_list - 1) { |
| next_time = list[index_of_result + 1].Time(); |
| if (next_time > minimum_time) |
| return next_time; |
| ++index_of_result; |
| } |
| |
| return begin_or_end == kBegin ? SMILTime::Unresolved() |
| : SMILTime::Indefinite(); |
| } |
| |
| SMILTime SVGSMILElement::RepeatingDuration() const { |
| // Computing the active duration |
| // http://www.w3.org/TR/SMIL2/smil-timing.html#Timing-ComputingActiveDur |
| SMILTime repeat_count = this->RepeatCount(); |
| SMILTime repeat_dur = this->RepeatDur(); |
| SMILTime simple_duration = this->SimpleDuration(); |
| if (!simple_duration || |
| (repeat_dur.IsUnresolved() && repeat_count.IsUnresolved())) |
| return simple_duration; |
| repeat_dur = std::min(repeat_dur, SMILTime::Indefinite()); |
| SMILTime repeat_count_duration = simple_duration * repeat_count; |
| if (!repeat_count_duration.IsUnresolved()) |
| return std::min(repeat_dur, repeat_count_duration); |
| return repeat_dur; |
| } |
| |
| SMILTime SVGSMILElement::ResolveActiveEnd(SMILTime resolved_begin, |
| SMILTime resolved_end) const { |
| // Computing the active duration |
| // http://www.w3.org/TR/SMIL2/smil-timing.html#Timing-ComputingActiveDur |
| SMILTime preliminary_active_duration; |
| if (!resolved_end.IsUnresolved() && Dur().IsUnresolved() && |
| RepeatDur().IsUnresolved() && RepeatCount().IsUnresolved()) |
| preliminary_active_duration = resolved_end - resolved_begin; |
| else if (!resolved_end.IsFinite()) |
| preliminary_active_duration = RepeatingDuration(); |
| else |
| preliminary_active_duration = |
| std::min(RepeatingDuration(), resolved_end - resolved_begin); |
| |
| SMILTime min_value = this->MinValue(); |
| SMILTime max_value = this->MaxValue(); |
| if (min_value > max_value) { |
| // Ignore both. |
| // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#MinMax |
| min_value = 0; |
| max_value = SMILTime::Indefinite(); |
| } |
| return resolved_begin + |
| std::min(max_value, std::max(min_value, preliminary_active_duration)); |
| } |
| |
| SMILInterval SVGSMILElement::ResolveInterval( |
| IntervalSelector interval_selector) const { |
| bool first = interval_selector == kFirstInterval; |
| // See the pseudocode in http://www.w3.org/TR/SMIL3/smil-timing.html#q90. |
| SMILTime begin_after = |
| first ? -std::numeric_limits<double>::infinity() : interval_.end; |
| SMILTime last_interval_temp_end = std::numeric_limits<double>::infinity(); |
| while (true) { |
| bool equals_minimum_ok = !first || interval_.end > interval_.begin; |
| SMILTime temp_begin = |
| FindInstanceTime(kBegin, begin_after, equals_minimum_ok); |
| if (temp_begin.IsUnresolved()) |
| break; |
| SMILTime temp_end; |
| if (end_times_.IsEmpty()) |
| temp_end = ResolveActiveEnd(temp_begin, SMILTime::Indefinite()); |
| else { |
| temp_end = FindInstanceTime(kEnd, temp_begin, true); |
| if ((first && temp_begin == temp_end && |
| temp_end == last_interval_temp_end) || |
| (!first && temp_end == interval_.end)) |
| temp_end = FindInstanceTime(kEnd, temp_begin, false); |
| if (temp_end.IsUnresolved()) { |
| if (!end_times_.IsEmpty() && !has_end_event_conditions_) |
| break; |
| } |
| temp_end = ResolveActiveEnd(temp_begin, temp_end); |
| } |
| if (!first || (temp_end > 0 || (!temp_begin.Value() && !temp_end.Value()))) |
| return SMILInterval(temp_begin, temp_end); |
| |
| begin_after = temp_end; |
| last_interval_temp_end = temp_end; |
| } |
| return SMILInterval(SMILTime::Unresolved(), SMILTime::Unresolved()); |
| } |
| |
| void SVGSMILElement::ResolveFirstInterval() { |
| SMILInterval first_interval = ResolveInterval(kFirstInterval); |
| DCHECK(!first_interval.begin.IsIndefinite()); |
| |
| if (!first_interval.begin.IsUnresolved() && first_interval != interval_) { |
| interval_ = first_interval; |
| NotifyDependentsIntervalChanged(); |
| next_progress_time_ = next_progress_time_.IsUnresolved() |
| ? interval_.begin |
| : std::min(next_progress_time_, interval_.begin); |
| |
| if (time_container_) |
| time_container_->NotifyIntervalsChanged(); |
| } |
| } |
| |
| bool SVGSMILElement::ResolveNextInterval() { |
| SMILInterval next_interval = ResolveInterval(kNextInterval); |
| DCHECK(!next_interval.begin.IsIndefinite()); |
| |
| if (!next_interval.begin.IsUnresolved() && |
| next_interval.begin != interval_.begin) { |
| interval_ = next_interval; |
| NotifyDependentsIntervalChanged(); |
| next_progress_time_ = next_progress_time_.IsUnresolved() |
| ? interval_.begin |
| : std::min(next_progress_time_, interval_.begin); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| SMILTime SVGSMILElement::NextProgressTime() const { |
| return next_progress_time_; |
| } |
| |
| void SVGSMILElement::BeginListChanged(SMILTime event_time) { |
| if (is_waiting_for_first_interval_) { |
| ResolveFirstInterval(); |
| } else if (GetRestart() != kRestartNever) { |
| SMILTime new_begin = FindInstanceTime(kBegin, event_time, true); |
| if (new_begin.IsFinite() && |
| (interval_.end <= event_time || new_begin < interval_.begin)) { |
| // Begin time changed, re-resolve the interval. |
| SMILTime old_begin = interval_.begin; |
| interval_.end = event_time; |
| interval_ = ResolveInterval(kNextInterval); |
| DCHECK(!interval_.begin.IsUnresolved()); |
| if (interval_.begin != old_begin) { |
| if (GetActiveState() == kActive && interval_.begin > event_time) { |
| active_state_ = DetermineActiveState(event_time); |
| if (GetActiveState() != kActive) |
| EndedActiveInterval(); |
| } |
| NotifyDependentsIntervalChanged(); |
| } |
| } |
| } |
| next_progress_time_ = Elapsed(); |
| |
| if (time_container_) |
| time_container_->NotifyIntervalsChanged(); |
| } |
| |
| void SVGSMILElement::EndListChanged(SMILTime) { |
| SMILTime elapsed = this->Elapsed(); |
| if (is_waiting_for_first_interval_) { |
| ResolveFirstInterval(); |
| } else if (elapsed < interval_.end && interval_.begin.IsFinite()) { |
| SMILTime new_end = FindInstanceTime(kEnd, interval_.begin, false); |
| if (new_end < interval_.end) { |
| new_end = ResolveActiveEnd(interval_.begin, new_end); |
| if (new_end != interval_.end) { |
| interval_.end = new_end; |
| NotifyDependentsIntervalChanged(); |
| } |
| } |
| } |
| next_progress_time_ = elapsed; |
| |
| if (time_container_) |
| time_container_->NotifyIntervalsChanged(); |
| } |
| |
| SVGSMILElement::RestartedInterval SVGSMILElement::MaybeRestartInterval( |
| double elapsed) { |
| DCHECK(!is_waiting_for_first_interval_); |
| DCHECK(elapsed >= interval_.begin); |
| |
| Restart restart = GetRestart(); |
| if (restart == kRestartNever) |
| return kDidNotRestartInterval; |
| |
| if (elapsed < interval_.end) { |
| if (restart != kRestartAlways) |
| return kDidNotRestartInterval; |
| SMILTime next_begin = FindInstanceTime(kBegin, interval_.begin, false); |
| if (next_begin < interval_.end) { |
| interval_.end = next_begin; |
| NotifyDependentsIntervalChanged(); |
| } |
| } |
| |
| if (elapsed >= interval_.end) { |
| if (ResolveNextInterval() && elapsed >= interval_.begin) |
| return kDidRestartInterval; |
| } |
| return kDidNotRestartInterval; |
| } |
| |
| void SVGSMILElement::SeekToIntervalCorrespondingToTime(double elapsed) { |
| DCHECK(!is_waiting_for_first_interval_); |
| DCHECK(elapsed >= interval_.begin); |
| |
| // Manually seek from interval to interval, just as if the animation would run |
| // regulary. |
| while (true) { |
| // Figure out the next value in the begin time list after the current |
| // interval begin. |
| SMILTime next_begin = FindInstanceTime(kBegin, interval_.begin, false); |
| |
| // If the 'nextBegin' time is unresolved (eg. just one defined interval), |
| // we're done seeking. |
| if (next_begin.IsUnresolved()) |
| return; |
| |
| // If the 'nextBegin' time is larger than or equal to the current interval |
| // end time, we're done seeking. If the 'elapsed' time is smaller than the |
| // next begin interval time, we're done seeking. |
| if (next_begin < interval_.end && elapsed >= next_begin) { |
| // End current interval, and start a new interval from the 'nextBegin' |
| // time. |
| interval_.end = next_begin; |
| if (!ResolveNextInterval()) |
| break; |
| continue; |
| } |
| |
| // If the desired 'elapsed' time is past the current interval, advance to |
| // the next. |
| if (elapsed >= interval_.end) { |
| if (!ResolveNextInterval()) |
| break; |
| continue; |
| } |
| |
| return; |
| } |
| } |
| |
| float SVGSMILElement::CalculateAnimationPercentAndRepeat( |
| double elapsed, |
| unsigned& repeat) const { |
| SMILTime simple_duration = this->SimpleDuration(); |
| repeat = 0; |
| if (simple_duration.IsIndefinite()) { |
| repeat = 0; |
| return 0.f; |
| } |
| if (!simple_duration) { |
| repeat = 0; |
| return 1.f; |
| } |
| DCHECK(interval_.begin.IsFinite()); |
| DCHECK(simple_duration.IsFinite()); |
| double active_time = elapsed - interval_.begin.Value(); |
| SMILTime repeating_duration = this->RepeatingDuration(); |
| if (elapsed >= interval_.end || active_time > repeating_duration) { |
| repeat = static_cast<unsigned>(repeating_duration.Value() / |
| simple_duration.Value()); |
| if (!fmod(repeating_duration.Value(), simple_duration.Value())) |
| repeat--; |
| |
| // Use the interval to compute the interval position if we've passed the |
| // interval end, otherwise use the "repeating duration". This prevents a |
| // stale interval (with for instance an 'indefinite' end) from yielding an |
| // invalid interval position. |
| double last_active_duration = |
| elapsed >= interval_.end |
| ? interval_.end.Value() - interval_.begin.Value() |
| : repeating_duration.Value(); |
| double percent = last_active_duration / simple_duration.Value(); |
| percent = percent - floor(percent); |
| if (percent < std::numeric_limits<float>::epsilon() || |
| 1 - percent < std::numeric_limits<float>::epsilon()) |
| return 1.0f; |
| return clampTo<float>(percent); |
| } |
| repeat = static_cast<unsigned>(active_time / simple_duration.Value()); |
| double simple_time = fmod(active_time, simple_duration.Value()); |
| return clampTo<float>(simple_time / simple_duration.Value()); |
| } |
| |
| SMILTime SVGSMILElement::CalculateNextProgressTime(double elapsed) const { |
| if (GetActiveState() == kActive) { |
| // If duration is indefinite the value does not actually change over time. |
| // Same is true for <set>. |
| SMILTime simple_duration = this->SimpleDuration(); |
| if (simple_duration.IsIndefinite() || IsSVGSetElement(*this)) { |
| SMILTime repeating_duration_end = interval_.begin + RepeatingDuration(); |
| // We are supposed to do freeze semantics when repeating ends, even if the |
| // element is still active. |
| // Take care that we get a timer callback at that point. |
| if (elapsed < repeating_duration_end && |
| repeating_duration_end < interval_.end && |
| repeating_duration_end.IsFinite()) |
| return repeating_duration_end; |
| return interval_.end; |
| } |
| return elapsed + 0.025; |
| } |
| return interval_.begin >= elapsed ? interval_.begin : SMILTime::Unresolved(); |
| } |
| |
| SVGSMILElement::ActiveState SVGSMILElement::DetermineActiveState( |
| SMILTime elapsed) const { |
| if (elapsed >= interval_.begin && elapsed < interval_.end) |
| return kActive; |
| |
| return Fill() == kFillFreeze ? kFrozen : kInactive; |
| } |
| |
| bool SVGSMILElement::IsContributing(double elapsed) const { |
| // Animation does not contribute during the active time if it is past its |
| // repeating duration and has fill=remove. |
| return (GetActiveState() == kActive && |
| (Fill() == kFillFreeze || |
| elapsed <= interval_.begin + RepeatingDuration())) || |
| GetActiveState() == kFrozen; |
| } |
| |
| bool SVGSMILElement::Progress(double elapsed, bool seek_to_time) { |
| DCHECK(time_container_); |
| DCHECK(is_waiting_for_first_interval_ || interval_.begin.IsFinite()); |
| |
| if (!sync_base_conditions_connected_) |
| ConnectSyncBaseConditions(); |
| |
| if (!interval_.begin.IsFinite()) { |
| DCHECK_EQ(GetActiveState(), kInactive); |
| next_progress_time_ = SMILTime::Unresolved(); |
| return false; |
| } |
| |
| if (elapsed < interval_.begin) { |
| DCHECK_NE(GetActiveState(), kActive); |
| next_progress_time_ = interval_.begin; |
| // If the animation is frozen, it's still contributing. |
| return GetActiveState() == kFrozen; |
| } |
| |
| previous_interval_begin_ = interval_.begin; |
| |
| if (is_waiting_for_first_interval_) { |
| is_waiting_for_first_interval_ = false; |
| ResolveFirstInterval(); |
| } |
| |
| // This call may obtain a new interval -- never call |
| // calculateAnimationPercentAndRepeat() before! |
| if (seek_to_time) { |
| SeekToIntervalCorrespondingToTime(elapsed); |
| if (elapsed < interval_.begin) { |
| // elapsed is not within an interval. |
| next_progress_time_ = interval_.begin; |
| return false; |
| } |
| } |
| |
| unsigned repeat = 0; |
| float percent = CalculateAnimationPercentAndRepeat(elapsed, repeat); |
| RestartedInterval restarted_interval = MaybeRestartInterval(elapsed); |
| |
| ActiveState old_active_state = GetActiveState(); |
| active_state_ = DetermineActiveState(elapsed); |
| bool animation_is_contributing = IsContributing(elapsed); |
| |
| if (animation_is_contributing) { |
| if (old_active_state == kInactive || |
| restarted_interval == kDidRestartInterval) { |
| ScheduleEvent(event_type_names::kBeginEvent); |
| StartedActiveInterval(); |
| } |
| |
| if (repeat && repeat != last_repeat_) |
| ScheduleRepeatEvents(repeat); |
| |
| last_percent_ = percent; |
| last_repeat_ = repeat; |
| } |
| |
| if ((old_active_state == kActive && GetActiveState() != kActive) || |
| restarted_interval == kDidRestartInterval) { |
| ScheduleEvent(event_type_names::kEndEvent); |
| EndedActiveInterval(); |
| } |
| |
| // Triggering all the pending events if the animation timeline is changed. |
| if (seek_to_time) { |
| if (GetActiveState() == kInactive) |
| ScheduleEvent(event_type_names::kBeginEvent); |
| |
| if (repeat) { |
| for (unsigned repeat_event_count = 1; repeat_event_count < repeat; |
| repeat_event_count++) |
| ScheduleRepeatEvents(repeat_event_count); |
| if (GetActiveState() == kInactive) |
| ScheduleRepeatEvents(repeat); |
| } |
| |
| if (GetActiveState() == kInactive || GetActiveState() == kFrozen) |
| ScheduleEvent(event_type_names::kEndEvent); |
| } |
| |
| next_progress_time_ = CalculateNextProgressTime(elapsed); |
| return animation_is_contributing; |
| } |
| |
| void SVGSMILElement::NotifyDependentsIntervalChanged() { |
| DCHECK(interval_.begin.IsFinite()); |
| // |loopBreaker| is used to avoid infinite recursions which may be caused by: |
| // |notifyDependentsIntervalChanged| -> |createInstanceTimesFromSyncbase| -> |
| // |add{Begin,End}Time| -> |{begin,end}TimeChanged| -> |
| // |notifyDependentsIntervalChanged| |
| // |
| // As the set here records SVGSMILElements on the stack, it is acceptable to |
| // use a HashSet of untraced heap references -- any conservative GC which |
| // strikes before unwinding will find these elements on the stack. |
| DEFINE_STATIC_LOCAL(HashSet<UntracedMember<SVGSMILElement>>, loop_breaker, |
| ()); |
| if (!loop_breaker.insert(this).is_new_entry) |
| return; |
| |
| for (SVGSMILElement* element : sync_base_dependents_) |
| element->CreateInstanceTimesFromSyncbase(*this); |
| |
| loop_breaker.erase(this); |
| } |
| |
| void SVGSMILElement::CreateInstanceTimesFromSyncbase( |
| SVGSMILElement& sync_base) { |
| // FIXME: To be really correct, this should handle updating exising interval |
| // by changing the associated times instead of creating new ones. |
| for (Condition* condition : conditions_) { |
| if (condition->GetType() == Condition::kSyncbase && |
| condition->SyncBaseEquals(sync_base)) { |
| DCHECK(condition->GetName() == "begin" || condition->GetName() == "end"); |
| // No nested time containers in SVG, no need for crazy time space |
| // conversions. Phew! |
| SMILTime time = 0; |
| if (condition->GetName() == "begin") |
| time = sync_base.interval_.begin + condition->Offset(); |
| else |
| time = sync_base.interval_.end + condition->Offset(); |
| if (!time.IsFinite()) |
| continue; |
| AddInstanceTime(condition->GetBeginOrEnd(), time); |
| } |
| } |
| } |
| |
| void SVGSMILElement::AddSyncBaseDependent(SVGSMILElement& animation) { |
| sync_base_dependents_.insert(&animation); |
| if (interval_.begin.IsFinite()) |
| animation.CreateInstanceTimesFromSyncbase(*this); |
| } |
| |
| void SVGSMILElement::RemoveSyncBaseDependent(SVGSMILElement& animation) { |
| sync_base_dependents_.erase(&animation); |
| } |
| |
| void SVGSMILElement::BeginByLinkActivation() { |
| AddInstanceTime(kBegin, Elapsed()); |
| } |
| |
| void SVGSMILElement::EndedActiveInterval() { |
| ClearTimesWithDynamicOrigins(begin_times_); |
| ClearTimesWithDynamicOrigins(end_times_); |
| } |
| |
| void SVGSMILElement::ScheduleRepeatEvents(unsigned count) { |
| repeat_event_count_list_.push_back(count); |
| ScheduleEvent(event_type_names::kRepeatEvent); |
| ScheduleEvent(AtomicString("repeatn")); |
| } |
| |
| void SVGSMILElement::ScheduleEvent(const AtomicString& event_type) { |
| GetDocument() |
| .GetTaskRunner(TaskType::kDOMManipulation) |
| ->PostTask(FROM_HERE, WTF::Bind(&SVGSMILElement::DispatchPendingEvent, |
| WrapPersistent(this), event_type)); |
| } |
| |
| void SVGSMILElement::DispatchPendingEvent(const AtomicString& event_type) { |
| DCHECK(event_type == event_type_names::kEndEvent || |
| event_type == event_type_names::kBeginEvent || |
| event_type == event_type_names::kRepeatEvent || |
| event_type == "repeatn"); |
| if (event_type == "repeatn") { |
| unsigned repeat_event_count = repeat_event_count_list_.front(); |
| repeat_event_count_list_.EraseAt(0); |
| DispatchEvent(*RepeatEvent::Create(event_type, repeat_event_count)); |
| } else { |
| DispatchEvent(*Event::Create(event_type)); |
| } |
| } |
| |
| bool SVGSMILElement::HasValidTarget() { |
| return targetElement() && targetElement()->InActiveDocument(); |
| } |
| |
| void SVGSMILElement::WillChangeAnimationTarget() { |
| if (!is_scheduled_) |
| return; |
| DCHECK(time_container_); |
| DCHECK(target_element_); |
| time_container_->Unschedule(this, target_element_, attribute_name_); |
| is_scheduled_ = false; |
| } |
| |
| void SVGSMILElement::DidChangeAnimationTarget() { |
| DCHECK(!is_scheduled_); |
| if (!time_container_ || !HasValidTarget()) |
| return; |
| time_container_->Schedule(this, target_element_, attribute_name_); |
| is_scheduled_ = true; |
| } |
| |
| void SVGSMILElement::Trace(blink::Visitor* visitor) { |
| visitor->Trace(target_element_); |
| visitor->Trace(target_id_observer_); |
| visitor->Trace(time_container_); |
| visitor->Trace(conditions_); |
| visitor->Trace(sync_base_dependents_); |
| SVGElement::Trace(visitor); |
| SVGTests::Trace(visitor); |
| } |
| |
| } // namespace blink |