blob: d63409c1c28538aa56481e4368d7790b3d2559ae [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/animation/css/css_scroll_timeline.h"
#include "third_party/blink/renderer/core/animation/document_animations.h"
#include "third_party/blink/renderer/core/css/css_element_offset_value.h"
#include "third_party/blink/renderer/core/css/css_id_selector_value.h"
#include "third_party/blink/renderer/core/css/css_identifier_value.h"
#include "third_party/blink/renderer/core/css/css_value_list.h"
#include "third_party/blink/renderer/core/css/style_rule.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
namespace blink {
namespace {
bool IsIdentifier(const CSSValue* value, CSSValueID value_id) {
if (const auto* ident = DynamicTo<CSSIdentifierValue>(value))
return ident->GetValueID() == value_id;
return false;
}
bool IsAuto(const CSSValue* value) {
return IsIdentifier(value, CSSValueID::kAuto);
}
bool IsNone(const CSSValue* value) {
return IsIdentifier(value, CSSValueID::kNone);
}
bool IsStart(const CSSValue* value) {
return IsIdentifier(value, CSSValueID::kStart);
}
bool IsEnd(const CSSValue* value) {
return IsIdentifier(value, CSSValueID::kEnd);
}
const cssvalue::CSSIdSelectorValue* GetIdSelectorValue(const CSSValue* value) {
if (const auto* selector = DynamicTo<CSSFunctionValue>(value)) {
if (selector->FunctionType() != CSSValueID::kSelector)
return nullptr;
DCHECK_EQ(selector->length(), 1u);
return DynamicTo<cssvalue::CSSIdSelectorValue>(selector->Item(0));
}
return nullptr;
}
Element* ComputeScrollSource(Document& document, const CSSValue* value) {
if (const auto* id = GetIdSelectorValue(value))
return document.getElementById(id->Id());
if (IsNone(value))
return nullptr;
DCHECK(!value || IsAuto(value));
return document.scrollingElement();
}
Element* ComputeElementOffsetTarget(Document& document, const CSSValue* value) {
if (const auto* id = GetIdSelectorValue(value))
return document.getElementById(id->Id());
return nullptr;
}
String ComputeElementOffsetEdge(const CSSValue* value) {
if (!value || IsStart(value))
return "start";
DCHECK(IsEnd(value));
return "end";
}
double ComputeElementOffsetThreshold(const CSSValue* value) {
if (auto* primitive_value = DynamicTo<CSSPrimitiveValue>(value)) {
DCHECK(primitive_value->IsNumber());
return primitive_value->GetDoubleValue();
}
return 0;
}
ScrollTimelineElementBasedOffset* ComputeElementBasedOffset(
Document& document,
const cssvalue::CSSElementOffsetValue* value) {
auto* offset = ScrollTimelineElementBasedOffset::Create();
Element* target = ComputeElementOffsetTarget(document, value->Target());
if (target)
offset->setTarget(target);
offset->setEdge(ComputeElementOffsetEdge(value->Edge()));
offset->setThreshold(ComputeElementOffsetThreshold(value->Threshold()));
return offset;
}
ScrollTimeline::ScrollDirection ComputeScrollDirection(const CSSValue* value) {
CSSValueID value_id = CSSValueID::kAuto;
if (const auto* identifier = DynamicTo<CSSIdentifierValue>(value))
value_id = identifier->GetValueID();
switch (value_id) {
case CSSValueID::kInline:
return ScrollTimeline::Inline;
case CSSValueID::kHorizontal:
return ScrollTimeline::Horizontal;
case CSSValueID::kVertical:
return ScrollTimeline::Vertical;
case CSSValueID::kAuto:
case CSSValueID::kBlock:
default:
return ScrollTimeline::Block;
}
}
ScrollTimelineOffset* ComputeScrollOffset(Document& document,
const CSSValue* value) {
if (auto* primitive_value = DynamicTo<CSSPrimitiveValue>(value))
return MakeGarbageCollected<ScrollTimelineOffset>(primitive_value);
if (auto* offset = DynamicTo<cssvalue::CSSElementOffsetValue>(value)) {
auto* element_based = ComputeElementBasedOffset(document, offset);
return MakeGarbageCollected<ScrollTimelineOffset>(element_based);
}
DCHECK(!value || IsAuto(value));
return MakeGarbageCollected<ScrollTimelineOffset>();
}
HeapVector<Member<ScrollTimelineOffset>> ComputeScrollOffsets(
Document& document,
const CSSValue* start,
const CSSValue* end) {
HeapVector<Member<ScrollTimelineOffset>> offsets;
const bool start_is_auto = !start || IsAuto(start);
const bool end_is_auto = !end || IsAuto(end);
// TODO(crbug.com/1094014): scroll_offsets will replace start and end
// offsets once spec decision on multiple scroll offsets is finalized.
// https://github.com/w3c/csswg-drafts/issues/4912
if (!start_is_auto)
offsets.push_back(ComputeScrollOffset(document, start));
if (!end_is_auto || !start_is_auto)
offsets.push_back(ComputeScrollOffset(document, end));
return offsets;
}
base::Optional<double> ComputeTimeRange(const CSSValue* value) {
if (auto* primitive = DynamicTo<CSSPrimitiveValue>(value))
return primitive->ComputeSeconds() * 1000.0;
// TODO(crbug.com/1097041): Support 'auto' value.
return base::nullopt;
}
class ElementReferenceObserver : public IdTargetObserver {
public:
ElementReferenceObserver(Document* document,
const AtomicString& id,
CSSScrollTimeline* timeline)
: IdTargetObserver(document->GetIdTargetObserverRegistry(), id),
timeline_(timeline) {}
void Trace(Visitor* visitor) const override {
visitor->Trace(timeline_);
IdTargetObserver::Trace(visitor);
}
private:
void IdTargetChanged() override {
if (timeline_)
timeline_->InvalidateEffectTargetStyle();
}
WeakMember<CSSScrollTimeline> timeline_;
};
HeapVector<Member<IdTargetObserver>> CreateElementReferenceObservers(
Document* document,
StyleRuleScrollTimeline* rule,
CSSScrollTimeline* timeline) {
HeapVector<Member<IdTargetObserver>> observers;
if (const auto* id = GetIdSelectorValue(rule->GetSource())) {
observers.push_back(MakeGarbageCollected<ElementReferenceObserver>(
document, id->Id(), timeline));
}
// TODO(crbug.com/1094014): The 'offsets' descriptor will replace the 'start'
// and 'end' descriptors eventually.
HeapVector<Member<const CSSValue>> offsets = {rule->GetStart(),
rule->GetEnd()};
for (const CSSValue* offset : offsets) {
const auto* element_offset =
DynamicTo<cssvalue::CSSElementOffsetValue>(offset);
if (!element_offset)
continue;
if (const auto* id = GetIdSelectorValue(element_offset->Target())) {
observers.push_back(MakeGarbageCollected<ElementReferenceObserver>(
document, id->Id(), timeline));
}
}
return observers;
}
} // anonymous namespace
CSSScrollTimeline::Options::Options(Element* element,
StyleRuleScrollTimeline& rule)
: source_(ComputeScrollSource(element->GetDocument(), rule.GetSource())),
direction_(ComputeScrollDirection(rule.GetOrientation())),
offsets_(ComputeScrollOffsets(element->GetDocument(),
rule.GetStart(),
rule.GetEnd())),
time_range_(ComputeTimeRange(rule.GetTimeRange())),
rule_(&rule) {}
CSSScrollTimeline::CSSScrollTimeline(Document* document, Options&& options)
: ScrollTimeline(document,
options.source_,
options.direction_,
std::move(options.offsets_),
*options.time_range_),
rule_(options.rule_) {
DCHECK(options.IsValid());
DCHECK(rule_);
document->GetDocumentAnimations().CacheCSSScrollTimeline(*this);
}
const AtomicString& CSSScrollTimeline::Name() const {
return rule_->GetName();
}
bool CSSScrollTimeline::Matches(const Options& options) const {
return (scrollSource() == options.source_) &&
(GetOrientation() == options.direction_) &&
(ScrollOffsetsEqual(options.offsets_)) &&
(GetTimeRange() == options.time_range_) && (rule_ == options.rule_);
}
void CSSScrollTimeline::AnimationAttached(Animation* animation) {
ScrollTimeline::AnimationAttached(animation);
if (AttachedAnimationsCount() == 1)
SetObservers(CreateElementReferenceObservers(GetDocument(), rule_, this));
}
void CSSScrollTimeline::AnimationDetached(Animation* animation) {
ScrollTimeline::AnimationDetached(animation);
if (AttachedAnimationsCount() == 0)
SetObservers(HeapVector<Member<IdTargetObserver>>());
}
void CSSScrollTimeline::Trace(Visitor* visitor) const {
visitor->Trace(rule_);
visitor->Trace(observers_);
ScrollTimeline::Trace(visitor);
}
void CSSScrollTimeline::SetObservers(
HeapVector<Member<IdTargetObserver>> observers) {
for (IdTargetObserver* observer : observers_)
observer->Unregister();
observers_ = std::move(observers);
}
} // namespace blink