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..33ad58f9f38a3ea3535a75f2d58effc2fdbf1b9f |
--- /dev/null |
+++ b/third_party/WebKit/Source/core/dom/IntersectionObserver.cpp |
@@ -0,0 +1,255 @@ |
+// 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 { |
+ |
+IntersectionObserver* IntersectionObserver::create(Document* owningDocument, const IntersectionObserverInit& observerInit, 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>(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(owningDocument, callback, observerInit.root().get(), rootMargin, thresholds); |
+} |
+ |
+IntersectionObserver::IntersectionObserver(Document* owningDocument, IntersectionObserverCallback* callback, PassRefPtrWillBeRawPtr<Element> root, int rootMargin, const Vector<float>& thresholds) |
+ : m_callback(callback) |
+ , m_owningDocument(owningDocument->createWeakPtr()) |
+ , 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()); |
+ } |
+} |
+ |
+bool IntersectionObserver::isDescendantOfRoot(Element* target) |
+{ |
+ Element* rootElement = root(); |
+ if (target == rootElement || !target->inDocument()) |
+ return false; |
+ if (!hasRoot()) |
+ return true; |
+ 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(&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() |
+{ |
+ 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() |
+{ |
+ IntersectionObserverEntryVector entries; |
+ entries.swap(*m_entries); |
+ return entries; |
+} |
+ |
+void IntersectionObserver::enqueueIntersectionObserverEntry(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); |
+} |
+ |
+IntersectionObservation* IntersectionObserver::createObservation(Element* target) |
+{ |
+ IntersectionObservation* observation = new IntersectionObservation(*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); |
+ visitor->trace(m_owningDocument); |
+ visitor->trace(m_trackingDocument); |
+ visitor->trace(m_observations); |
+ visitor->trace(m_entries); |
+} |
+ |
+} // namespace blink |