Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(176)

Unified Diff: third_party/WebKit/Source/core/dom/IntersectionObserver.cpp

Issue 1449623002: IntersectionObserver: second cut. (Closed) Base URL: https://chromium.googlesource.com/chromium/src@master
Patch Set: Clarify the tear-down path for observers and observations. Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698