| Index: third_party/WebKit/Source/core/dom/IntersectionObserver.cpp
|
| diff --git a/third_party/WebKit/Source/core/dom/IntersectionObserver.cpp b/third_party/WebKit/Source/core/dom/IntersectionObserver.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..943d9357ddd568acaa49cb3bfcf854eb91bd6bb6
|
| --- /dev/null
|
| +++ b/third_party/WebKit/Source/core/dom/IntersectionObserver.cpp
|
| @@ -0,0 +1,267 @@
|
| +// Copyright 2015 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 "config.h"
|
| +#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/ExceptionCode.h"
|
| +#include "core/dom/ExecutionContext.h"
|
| +#include "core/dom/IntersectionObserverCallback.h"
|
| +#include "core/dom/IntersectionObserverEntry.h"
|
| +#include "core/dom/IntersectionObserverInit.h"
|
| +#include "core/dom/IntersectionObserverRegistry.h"
|
| +#include "core/html/HTMLFrameOwnerElement.h"
|
| +#include "platform/Timer.h"
|
| +#include "wtf/MainThread.h"
|
| +#include <algorithm>
|
| +
|
| +namespace blink {
|
| +
|
| +IntersectionObserver* IntersectionObserver::create(const IntersectionObserverInit& observerInit, IntersectionObserverCallback* callback, ExceptionState& exceptionState)
|
| +{
|
| + ASSERT(isMainThread());
|
| +
|
| + RefPtrWillBeRawPtr<Element> root = observerInit.root();
|
| + if (!root) {
|
| + ASSERT(callback);
|
| + ExecutionContext* context = callback->executionContext();
|
| + if (context->isDocument()) {
|
| + Frame* mainFrame = static_cast<Document*>(context)->frame()->tree().top();
|
| + if (mainFrame && mainFrame->isLocalFrame())
|
| + root = toLocalFrame(mainFrame)->document()->documentElement();
|
| + }
|
| + }
|
| + if (!root) {
|
| + exceptionState.throwDOMException(HierarchyRequestError, "Unable to get root element in main frame to track.");
|
| + return nullptr;
|
| + }
|
| +
|
| + int rootMargin = 0;
|
| + if (observerInit.hasRootMargin()) {
|
| + CSSParserTokenRange tokenRange = CSSTokenizer::Scope(observerInit.rootMargin()).tokenRange();
|
| + const CSSParserToken& token = tokenRange.consumeIncludingWhitespace();
|
| + switch (token.type()) {
|
| + case EOFToken:
|
| + break;
|
| + case NumberToken:
|
| + rootMargin = static_cast<int>(floor(token.numericValue()));
|
| + break;
|
| + case DimensionToken:
|
| + switch (token.unitType()) {
|
| + case CSSPrimitiveValue::UnitType::Number:
|
| + case CSSPrimitiveValue::UnitType::Pixels:
|
| + rootMargin = static_cast<int>(token.numericValue());
|
| + break;
|
| + default:
|
| + exceptionState.throwTypeError("rootMargin must be specified in pixels.");
|
| + break;
|
| + }
|
| + break;
|
| + default:
|
| + exceptionState.throwTypeError("rootMargin must be specified in pixels.");
|
| + }
|
| + }
|
| + if (exceptionState.hadException())
|
| + return nullptr;
|
| +
|
| + Vector<float> thresholds;
|
| + if (observerInit.hasThreshold()) {
|
| + const DoubleOrDoubleArray& thresholdParam = observerInit.threshold();
|
| + if (thresholdParam.isDouble()) {
|
| + thresholds.append(static_cast<float>(thresholdParam.getAsDouble()));
|
| + } else {
|
| + for (auto thresholdValue: thresholdParam.getAsDoubleArray())
|
| + thresholds.append(static_cast<float>(thresholdValue));
|
| + }
|
| + } else {
|
| + thresholds.append(0);
|
| + }
|
| + for (auto thresholdValue: thresholds) {
|
| + if (thresholdValue < 0.0 || thresholdValue > 1.0) {
|
| + exceptionState.throwTypeError("Threshold values must be between 0 and 1");
|
| + break;
|
| + }
|
| + }
|
| + std::sort(thresholds.begin(), thresholds.end());
|
| + if (exceptionState.hadException())
|
| + return nullptr;
|
| +
|
| + return new IntersectionObserver(callback, root.get(), rootMargin, thresholds);
|
| +}
|
| +
|
| +IntersectionObserver::IntersectionObserver(IntersectionObserverCallback* callback, Element* root, int rootMargin, const Vector<float>& thresholds)
|
| + : m_callback(callback)
|
| + , m_root(root->createWeakPtr())
|
| + , m_observations(new IntersectionObservation::WeakHashSet())
|
| + , m_entries(new IntersectionObserverEntryVector())
|
| + , m_thresholds(thresholds)
|
| + , m_rootMargin(rootMargin)
|
| +{
|
| + root->document().intersectionObserverRegistry()->addTrackedObserver(*this);
|
| +}
|
| +
|
| +bool IntersectionObserver::isDescendantOfRoot(Element* target)
|
| +{
|
| + checkRootAndDetachIfNecessary();
|
| + Element* rootElement = m_root.get();
|
| + if (!rootElement)
|
| + return false;
|
| +
|
| + if (target == rootElement || !target->inDocument() || !rootElement->inDocument())
|
| + return false;
|
| + LocalFrame* rootFrame = rootElement->document().frame();
|
| + LocalFrame* targetFrame = target->document().frame();
|
| + if (!rootFrame || !targetFrame || !targetFrame->tree().isDescendantOf(rootFrame))
|
| + return false;
|
| + while (targetFrame != rootFrame) {
|
| + FrameOwner* targetOwner = targetFrame->owner();
|
| + if (!targetOwner || !targetOwner->isLocal())
|
| + return false;
|
| + target = toHTMLFrameOwnerElement(targetOwner);
|
| + ASSERT(target);
|
| + targetFrame = target->document().frame();
|
| + if (!targetFrame)
|
| + return false;
|
| + }
|
| + return target->isDescendantOf(rootElement);
|
| +}
|
| +
|
| +void IntersectionObserver::observe(Element* target, ExceptionState& exceptionState)
|
| +{
|
| + checkRootAndDetachIfNecessary();
|
| + if (!m_root)
|
| + exceptionState.throwDOMException(HierarchyRequestError, "Invalid observer: root element or containing document has been deleted.");
|
| + else if (!target)
|
| + exceptionState.throwTypeError("Observation target must be an element.");
|
| + else if (m_root.get() == target)
|
| + exceptionState.throwDOMException(HierarchyRequestError, "Cannot use the same element for root and target.");
|
| + if (exceptionState.hadException())
|
| + return;
|
| +
|
| + if (IntersectionObservation::contains(*m_observations, this, target))
|
| + return;
|
| +
|
| + createObservation(target);
|
| +}
|
| +
|
| +void IntersectionObserver::unobserve(Element* target, ExceptionState&)
|
| +{
|
| + checkRootAndDetachIfNecessary();
|
| + if (!target)
|
| + return;
|
| + IntersectionObservation::WeakHashSet::iterator observationIterator = IntersectionObservation::find(*m_observations, this, target);
|
| + // TODO: unobserve callback
|
| + if (observationIterator != m_observations->end())
|
| + (*observationIterator)->disconnect();
|
| +}
|
| +
|
| +void IntersectionObserver::computeIntersectionObservations(int timestamp)
|
| +{
|
| + checkRootAndDetachIfNecessary();
|
| + if (!m_root)
|
| + return;
|
| + for (auto& observation: *m_observations)
|
| + observation->computeIntersectionObservations(timestamp);
|
| +}
|
| +
|
| +void IntersectionObserver::disconnect(IntersectionObservation& observation)
|
| +{
|
| + m_observations->remove(&observation);
|
| +}
|
| +
|
| +void IntersectionObserver::disconnect()
|
| +{
|
| + checkRootAndDetachIfNecessary();
|
| + HeapVector<Member<IntersectionObservation>> toDisconnect;
|
| + for (auto& observation: *m_observations)
|
| + toDisconnect.append(observation);
|
| + for (auto& observation: toDisconnect)
|
| + observation->disconnect();
|
| + ASSERT(!m_observations->size());
|
| +}
|
| +
|
| +IntersectionObserverEntryVector IntersectionObserver::takeRecords()
|
| +{
|
| + checkRootAndDetachIfNecessary();
|
| + IntersectionObserverEntryVector entries;
|
| + entries.swap(*m_entries);
|
| + return entries;
|
| +}
|
| +
|
| +void IntersectionObserver::enqueueIntersectionObserverEntry(IntersectionObserverEntry* entry)
|
| +{
|
| + ASSERT(isMainThread());
|
| + m_entries->append(entry);
|
| + static_cast<Document*>(m_callback->executionContext())->intersectionObserverRegistry()->scheduleIntersectionObserverForDelivery(*this);
|
| +}
|
| +
|
| +size_t IntersectionObserver::firstThresholdGreaterThan(float ratio) const
|
| +{
|
| + size_t result = 0;
|
| + size_t max = m_thresholds.size();
|
| + while (result < max && m_thresholds[result] < ratio)
|
| + ++result;
|
| + return result;
|
| +}
|
| +
|
| +bool IntersectionObserver::shouldBeSuspended() const
|
| +{
|
| + return m_callback->executionContext() && m_callback->executionContext()->activeDOMObjectsAreSuspended();
|
| +}
|
| +
|
| +void IntersectionObserver::deliver()
|
| +{
|
| + checkRootAndDetachIfNecessary();
|
| +
|
| + ASSERT(!shouldBeSuspended());
|
| +
|
| + if (m_entries->isEmpty())
|
| + return;
|
| +
|
| + IntersectionObserverEntryVector entries;
|
| + entries.swap(*m_entries);
|
| + m_callback->handleEvent(entries, this);
|
| +}
|
| +
|
| +void IntersectionObserver::setActive(bool active)
|
| +{
|
| + checkRootAndDetachIfNecessary();
|
| + for (auto& observation: *m_observations)
|
| + observation->setActive(m_root && active && isDescendantOfRoot(observation->target()));
|
| +}
|
| +
|
| +IntersectionObservation* IntersectionObserver::createObservation(Element* target)
|
| +{
|
| + IntersectionObservation* observation = new IntersectionObservation(*this, target);
|
| + m_observations->add(observation);
|
| + return observation;
|
| +}
|
| +
|
| +void IntersectionObserver::checkRootAndDetachIfNecessary()
|
| +{
|
| + if (m_root)
|
| + return;
|
| + m_callback.clear();
|
| + HeapVector<Member<IntersectionObservation>> toDisconnect;
|
| + for (auto& observation: *m_observations)
|
| + toDisconnect.append(observation);
|
| + for (auto& observation: toDisconnect)
|
| + observation->disconnect();
|
| + ASSERT(!m_observations->size());
|
| + // TODO: should we deliver pending notifications?
|
| + m_entries.clear();
|
| +}
|
| +
|
| +DEFINE_TRACE(IntersectionObserver)
|
| +{
|
| + visitor->trace(m_callback);
|
| + visitor->trace(m_root);
|
| + visitor->trace(m_observations);
|
| + visitor->trace(m_entries);
|
| +}
|
| +
|
| +} // namespace blink
|
|
|