Chromium Code Reviews| 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..ed59ce9a52ca9f728efe4b81f68c5d67accf85f7 |
| --- /dev/null |
| +++ b/third_party/WebKit/Source/core/dom/IntersectionObserver.cpp |
| @@ -0,0 +1,259 @@ |
| +// 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/html/HTMLFrameOwnerElement.h" |
| +#include "platform/Timer.h" |
| +#include "wtf/MainThread.h" |
| +#include <algorithm> |
| + |
| +namespace blink { |
| + |
| +PassRefPtrWillBeRawPtr<IntersectionObserver> IntersectionObserver::create(const IntersectionObserverInit& observerInit, PassOwnPtrWillBeRawPtr<IntersectionObserverCallback> callback, ExceptionState& exceptionState) |
| +{ |
| + ASSERT(isMainThread()); |
| + |
| + 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>(token.numericValue()); |
| + break; |
| + case DimensionToken: |
| + switch (token.unitType()) { |
| + case CSSPrimitiveValue::UnitType::Number: |
| + case CSSPrimitiveValue::UnitType::Pixels: |
| + rootMargin = static_cast<int>(token.numericValue()); |
|
eae
2015/11/16 23:34:44
Is flooring the number the desired result? If so c
szager1
2015/11/19 19:15:38
Done in next patch.
|
| + 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 PassRefPtrWillBeRawPtr<IntersectionObserver>(); |
| + |
| + 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 PassRefPtrWillBeRawPtr<IntersectionObserver>(); |
| + |
| + return adoptRefWillBeNoop(new IntersectionObserver(callback, observerInit.root(), rootMargin, thresholds)); |
| +} |
| + |
| +IntersectionObserver::IntersectionObserver(PassOwnPtrWillBeRawPtr<IntersectionObserverCallback> callback, RefPtrWillBeRawPtr<Element> root, int rootMargin, const Vector<float>& thresholds) |
| + : m_callback(callback) |
| + , m_thresholds(thresholds) |
| + , m_rootMargin(rootMargin) |
| + , m_hasRoot(root) |
| +{ |
| + if (root) { |
| + // This is a phony observation, created just to ensure the root element keeps |
| + // a reference to the observer. That reference will be used to disconnect |
| + // the observer when the root element is destroyed. |
| + m_root = root->createWeakPtr(); |
| + m_trackingDocument = root->document().createWeakPtr(); |
| + createObservation(root.get()); |
| + } |
| +} |
| + |
| +IntersectionObserver::~IntersectionObserver() |
| +{ |
| + ASSERT(!m_observations.size()); |
| + ASSERT(!m_entries.size()); |
| +} |
| + |
| +bool IntersectionObserver::checkTargetHierarchy(Element* target) |
|
eae
2015/11/16 23:34:45
hasSameRoot or sharesAncestor might be better name
szager1
2015/11/19 19:15:38
Maybe isValidTarget? Or isDescendantOfRoot? I'll
|
| +{ |
| + if (!target->inDocument()) |
| + return false; |
| + if (!hasRoot()) |
| + return true; |
| + Element* rootElement = root(); |
| + if (!rootElement || !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); |
| +} |
| + |
| +Document& IntersectionObserver::getTrackingDocumentForTarget(Element* target) |
| +{ |
| + if (root()) |
| + return root()->document(); |
| + ASSERT(target); |
| + Frame* frame = target->document().frame(); |
| + ASSERT(frame); |
| + frame = frame->tree().top(); |
| + ASSERT(frame && frame->isLocalFrame() && toLocalFrame(frame)->document()); |
| + return *toLocalFrame(frame)->document(); |
| +} |
| + |
| +void IntersectionObserver::observe(Element* target, ExceptionState& exceptionState) |
| +{ |
| + if (!target) |
| + exceptionState.throwTypeError("Observation target must be an element."); |
| + else if ((m_hasRoot && !root()) || (m_observations.size() && !trackingDocument())) |
| + exceptionState.throwDOMException(HierarchyRequestError, "Invalid observer: root element or containing document has been deleted."); |
| + else if (root() == 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; |
| + |
| + // If there's no root and this is the first target, then we didn't know until |
| + // now which document will track the observation and generate notifications. |
| + if (!trackingDocument()) { |
| + ASSERT(!m_root && !m_observations.size()); |
| + m_trackingDocument = getTrackingDocumentForTarget(target).createWeakPtr(); |
| + } else if (m_trackingDocument.get() != &getTrackingDocumentForTarget(target)) { |
| + exceptionState.throwDOMException(HierarchyRequestError, "All target elements must be in the same frame tree."); |
| + return; |
| + } |
| + |
| + createObservation(target); |
| +} |
| + |
| +void IntersectionObserver::unobserve(Element* target, ExceptionState&) |
| +{ |
| + if (!target) |
| + return; |
| + IntersectionObservation::HashSet::iterator observationIterator = IntersectionObservation::find(m_observations, this, target); |
| + if (observationIterator == m_observations.end()) |
| + return; |
| + (*observationIterator)->disconnect(); |
| +} |
| + |
| +void IntersectionObserver::disconnect(IntersectionObservation& observation) |
| +{ |
| + // This will get called during Element destruction, so we must be careful |
| + // to check the weak pointers (root, target, trackingDocument). |
| + m_observations.remove(adoptRef(&observation)); |
| + if (observation.target()) |
| + observation.target()->removeIntersectionObservation(observation); |
| + if (root()) |
| + root()->removeIntersectionObservation(observation); |
| + // TODO: This will probably break things if there are unsent observations. |
| + if (trackingDocument()) |
| + trackingDocument()->intersectionObservationRegistry()->removeObservation(observation); |
| +} |
| + |
| +void IntersectionObserver::disconnect() |
| +{ |
| + WillBeHeapVector<RefPtr<IntersectionObservation>> toDisconnect; |
| + for (auto& observation: m_observations) |
| + toDisconnect.append(observation); |
| + for (auto& observation: toDisconnect) |
| + observation->disconnect(); |
| + ASSERT(!m_observations.size()); |
| +} |
| + |
| +IntersectionObserverEntryVector IntersectionObserver::takeRecords() |
| +{ |
| + IntersectionObserverEntryVector entries; |
| + entries.swap(m_entries); |
| + return entries; |
| +} |
| + |
| +void IntersectionObserver::enqueueIntersectionObserverEntry(PassRefPtrWillBeRawPtr<IntersectionObserverEntry> entry) |
| +{ |
| + ASSERT(isMainThread()); |
| + m_entries.append(entry); |
| + m_trackingDocument->intersectionObservationRegistry()->activateIntersectionObserver(*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() |
| +{ |
| + ASSERT(!shouldBeSuspended()); |
| + |
| + if (m_entries.isEmpty()) |
| + return; |
| + |
| + IntersectionObserverEntryVector entries; |
| + entries.swap(m_entries); |
| + m_callback->handleEvent(entries, this); |
| +} |
| + |
| +PassRefPtr<IntersectionObservation> IntersectionObserver::createObservation(Element* target) |
| +{ |
| + RefPtrWillBeRawPtr<IntersectionObservation> observation = IntersectionObservation::create(*this, target->createWeakPtr(), false); |
| + m_observations.add(observation); |
| + m_trackingDocument->intersectionObservationRegistry()->addObservation(*observation); |
| + target->addIntersectionObservation(*observation); |
| + return observation; |
| +} |
| + |
| +DEFINE_TRACE(IntersectionObserver) |
| +{ |
| + visitor->trace(m_callback); |
| + visitor->trace(m_root); |
| + // TODO: Uncommenting the next line causes a compilation failure. Fix it. |
| + // visitor->trace(m_observations); |
|
haraken
2015/11/16 00:41:34
You need to use:
#if ENABLE(OILPAN)
visitor->tr
szager1
2015/11/19 19:15:38
Working on it.
|
| + visitor->trace(m_entries); |
| +} |
| + |
| +} // namespace blink |