blob: e9cae0f56f44aa4ecdfb048b6a38f31d1b4bef32 [file] [log] [blame]
// Copyright 2016 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 "core/dom/IntersectionObserver.h"
#include "bindings/core/v8/ExceptionState.h"
#include "core/css/parser/CSSParserTokenRange.h"
#include "core/css/parser/CSSTokenizer.h"
#include "core/dom/Element.h"
#include "core/dom/ElementIntersectionObserverData.h"
#include "core/dom/ExceptionCode.h"
#include "core/dom/ExecutionContext.h"
#include "core/dom/IntersectionObserverCallback.h"
#include "core/dom/IntersectionObserverController.h"
#include "core/dom/IntersectionObserverEntry.h"
#include "core/dom/IntersectionObserverInit.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalDOMWindow.h"
#include "core/frame/LocalFrame.h"
#include "core/inspector/ConsoleMessage.h"
#include "core/layout/LayoutView.h"
#include "core/timing/DOMWindowPerformance.h"
#include "core/timing/Performance.h"
#include "platform/Timer.h"
#include <algorithm>
namespace blink {
namespace {
// Internal implementation of IntersectionObserverCallback when using
// IntersectionObserver with an EventCallback.
class IntersectionObserverCallbackImpl final
: public IntersectionObserverCallback {
WTF_MAKE_NONCOPYABLE(IntersectionObserverCallbackImpl);
public:
IntersectionObserverCallbackImpl(
ExecutionContext* context,
std::unique_ptr<IntersectionObserver::EventCallback> callback)
: m_context(context), m_callback(std::move(callback)) {}
void handleEvent(const HeapVector<Member<IntersectionObserverEntry>>& entries,
IntersectionObserver&) override {
(*m_callback.get())(entries);
}
ExecutionContext* getExecutionContext() const override { return m_context; }
DEFINE_INLINE_TRACE() {
IntersectionObserverCallback::trace(visitor);
visitor->trace(m_context);
}
private:
WeakMember<ExecutionContext> m_context;
std::unique_ptr<IntersectionObserver::EventCallback> m_callback;
};
void parseRootMargin(String rootMarginParameter,
Vector<Length>& rootMargin,
ExceptionState& exceptionState) {
// TODO(szager): Make sure this exact syntax and behavior is spec-ed
// somewhere.
// The root margin argument accepts syntax similar to that for CSS margin:
//
// "1px" = top/right/bottom/left
// "1px 2px" = top/bottom left/right
// "1px 2px 3px" = top left/right bottom
// "1px 2px 3px 4px" = top left right bottom
CSSTokenizer tokenizer(rootMarginParameter);
CSSParserTokenRange tokenRange = tokenizer.tokenRange();
while (tokenRange.peek().type() != EOFToken &&
!exceptionState.hadException()) {
if (rootMargin.size() == 4) {
exceptionState.throwDOMException(
SyntaxError, "Extra text found at the end of rootMargin.");
break;
}
const CSSParserToken& token = tokenRange.consumeIncludingWhitespace();
switch (token.type()) {
case PercentageToken:
rootMargin.push_back(Length(token.numericValue(), Percent));
break;
case DimensionToken:
switch (token.unitType()) {
case CSSPrimitiveValue::UnitType::Pixels:
rootMargin.push_back(
Length(static_cast<int>(floor(token.numericValue())), Fixed));
break;
case CSSPrimitiveValue::UnitType::Percentage:
rootMargin.push_back(Length(token.numericValue(), Percent));
break;
default:
exceptionState.throwDOMException(
SyntaxError,
"rootMargin must be specified in pixels or percent.");
}
break;
default:
exceptionState.throwDOMException(
SyntaxError, "rootMargin must be specified in pixels or percent.");
}
}
}
void parseThresholds(const DoubleOrDoubleSequence& thresholdParameter,
Vector<float>& thresholds,
ExceptionState& exceptionState) {
if (thresholdParameter.isDouble()) {
thresholds.push_back(static_cast<float>(thresholdParameter.getAsDouble()));
} else {
for (auto thresholdValue : thresholdParameter.getAsDoubleSequence())
thresholds.push_back(static_cast<float>(thresholdValue));
}
for (auto thresholdValue : thresholds) {
if (std::isnan(thresholdValue) || thresholdValue < 0.0 ||
thresholdValue > 1.0) {
exceptionState.throwRangeError(
"Threshold values must be numbers between 0 and 1");
break;
}
}
std::sort(thresholds.begin(), thresholds.end());
}
} // anonymous namespace
IntersectionObserver* IntersectionObserver::create(
const IntersectionObserverInit& observerInit,
IntersectionObserverCallback& callback,
ExceptionState& exceptionState) {
Element* root = observerInit.root();
Vector<Length> rootMargin;
parseRootMargin(observerInit.rootMargin(), rootMargin, exceptionState);
if (exceptionState.hadException())
return nullptr;
Vector<float> thresholds;
parseThresholds(observerInit.threshold(), thresholds, exceptionState);
if (exceptionState.hadException())
return nullptr;
return new IntersectionObserver(callback, root, rootMargin, thresholds);
}
IntersectionObserver* IntersectionObserver::create(
const Vector<Length>& rootMargin,
const Vector<float>& thresholds,
Document* document,
std::unique_ptr<EventCallback> callback,
ExceptionState& exceptionState) {
IntersectionObserverCallbackImpl* intersectionObserverCallback =
new IntersectionObserverCallbackImpl(document, std::move(callback));
return new IntersectionObserver(*intersectionObserverCallback, nullptr,
rootMargin, thresholds);
}
IntersectionObserver::IntersectionObserver(
IntersectionObserverCallback& callback,
Element* root,
const Vector<Length>& rootMargin,
const Vector<float>& thresholds)
: m_callback(&callback),
m_root(root),
m_thresholds(thresholds),
m_topMargin(Fixed),
m_rightMargin(Fixed),
m_bottomMargin(Fixed),
m_leftMargin(Fixed),
m_rootIsImplicit(root ? 0 : 1) {
switch (rootMargin.size()) {
case 0:
break;
case 1:
m_topMargin = m_rightMargin = m_bottomMargin = m_leftMargin =
rootMargin[0];
break;
case 2:
m_topMargin = m_bottomMargin = rootMargin[0];
m_rightMargin = m_leftMargin = rootMargin[1];
break;
case 3:
m_topMargin = rootMargin[0];
m_rightMargin = m_leftMargin = rootMargin[1];
m_bottomMargin = rootMargin[2];
break;
case 4:
m_topMargin = rootMargin[0];
m_rightMargin = rootMargin[1];
m_bottomMargin = rootMargin[2];
m_leftMargin = rootMargin[3];
break;
default:
NOTREACHED();
break;
}
if (root)
root->ensureIntersectionObserverData().addObserver(*this);
trackingDocument().ensureIntersectionObserverController().addTrackedObserver(
*this);
}
void IntersectionObserver::clearWeakMembers(Visitor* visitor) {
if (ThreadHeap::isHeapObjectAlive(root()))
return;
DummyExceptionStateForTesting exceptionState;
disconnect(exceptionState);
m_root = nullptr;
}
bool IntersectionObserver::rootIsValid() const {
return rootIsImplicit() || root();
}
Document& IntersectionObserver::trackingDocument() const {
if (rootIsImplicit()) {
DCHECK(m_callback->getExecutionContext());
return *toDocument(m_callback->getExecutionContext());
}
DCHECK(root());
return root()->document();
}
void IntersectionObserver::observe(Element* target,
ExceptionState& exceptionState) {
if (!rootIsValid())
return;
if (!target || root() == target)
return;
LocalFrame* targetFrame = target->document().frame();
if (!targetFrame)
return;
if (target->ensureIntersectionObserverData().getObservationFor(*this))
return;
IntersectionObservation* observation =
new IntersectionObservation(*this, *target);
target->ensureIntersectionObserverData().addObservation(*observation);
m_observations.insert(observation);
if (FrameView* frameView = targetFrame->view())
frameView->scheduleAnimation();
}
void IntersectionObserver::unobserve(Element* target,
ExceptionState& exceptionState) {
if (!target || !target->intersectionObserverData())
return;
if (IntersectionObservation* observation =
target->intersectionObserverData()->getObservationFor(*this)) {
observation->disconnect();
m_observations.erase(observation);
}
}
void IntersectionObserver::computeIntersectionObservations() {
if (!rootIsValid())
return;
Document* callbackDocument = toDocument(m_callback->getExecutionContext());
if (!callbackDocument)
return;
LocalDOMWindow* callbackDOMWindow = callbackDocument->domWindow();
if (!callbackDOMWindow)
return;
DOMHighResTimeStamp timestamp =
DOMWindowPerformance::performance(*callbackDOMWindow)->now();
for (auto& observation : m_observations)
observation->computeIntersectionObservations(timestamp);
}
void IntersectionObserver::disconnect(ExceptionState& exceptionState) {
for (auto& observation : m_observations)
observation->disconnect();
m_observations.clear();
m_entries.clear();
}
HeapVector<Member<IntersectionObserverEntry>> IntersectionObserver::takeRecords(
ExceptionState& exceptionState) {
HeapVector<Member<IntersectionObserverEntry>> entries;
entries.swap(m_entries);
return entries;
}
static void appendLength(StringBuilder& stringBuilder, const Length& length) {
stringBuilder.appendNumber(length.intValue());
if (length.type() == Percent)
stringBuilder.append('%');
else
stringBuilder.append("px", 2);
}
String IntersectionObserver::rootMargin() const {
StringBuilder stringBuilder;
appendLength(stringBuilder, m_topMargin);
stringBuilder.append(' ');
appendLength(stringBuilder, m_rightMargin);
stringBuilder.append(' ');
appendLength(stringBuilder, m_bottomMargin);
stringBuilder.append(' ');
appendLength(stringBuilder, m_leftMargin);
return stringBuilder.toString();
}
void IntersectionObserver::enqueueIntersectionObserverEntry(
IntersectionObserverEntry& entry) {
m_entries.push_back(&entry);
toDocument(m_callback->getExecutionContext())
->ensureIntersectionObserverController()
.scheduleIntersectionObserverForDelivery(*this);
}
unsigned IntersectionObserver::firstThresholdGreaterThan(float ratio) const {
unsigned result = 0;
while (result < m_thresholds.size() && m_thresholds[result] <= ratio)
++result;
return result;
}
void IntersectionObserver::deliver() {
if (m_entries.isEmpty())
return;
HeapVector<Member<IntersectionObserverEntry>> entries;
entries.swap(m_entries);
m_callback->handleEvent(entries, *this);
}
DEFINE_TRACE(IntersectionObserver) {
visitor->template registerWeakMembers<
IntersectionObserver, &IntersectionObserver::clearWeakMembers>(this);
visitor->trace(m_callback);
visitor->trace(m_observations);
visitor->trace(m_entries);
}
} // namespace blink