Index: Source/WebCore/page/IntersectionObserver.cpp |
diff --git a/Source/WebCore/page/IntersectionObserver.cpp b/Source/WebCore/page/IntersectionObserver.cpp |
index 07fa8a889c4845b29a1d7a823b2665c5ffae56c4..12cf01260dc711104b3524d2b1a502132204b3f5 100644 |
--- a/Source/WebCore/page/IntersectionObserver.cpp |
+++ b/Source/WebCore/page/IntersectionObserver.cpp |
@@ -28,41 +28,152 @@ |
#if ENABLE(INTERSECTION_OBSERVER) |
#include "IntersectionObserver.h" |
+#include "CSSParser.h" |
+#include "CSSPropertyParser.h" |
+#include "CSSTokenizer.h" |
+#include "DOMWindow.h" |
#include "Element.h" |
+#include "ExceptionCode.h" |
#include "IntersectionObserverCallback.h" |
#include "IntersectionObserverEntry.h" |
+#include "Logging.h" |
+#include "StyleResolver.h" |
+#include <wtf/text/TextStream.h> |
#include <wtf/Vector.h> |
+#include <algorithm> |
+#include <functional> |
namespace WebCore { |
-IntersectionObserver::IntersectionObserver(Ref<IntersectionObserverCallback>&& callback, Init&& init) |
- : m_root(init.root) |
+namespace { |
+ |
+bool isThresholdWithinRange(double threshold) { |
+ return threshold >= 0.0 && threshold <= 1.0; |
+} |
+ |
+} // namespace |
+ |
+ExceptionOr<Ref<IntersectionObserver>> IntersectionObserver::create(Document& document, Ref<IntersectionObserverCallback>&& callback, IntersectionObserver::Init&& init) |
+{ |
+ CSSParserContext context(HTMLStandardMode); |
+ CSSTokenizer tokenizer(init.rootMargin); |
+ ParsedPropertyVector properties; |
+ bool important = false; |
+ if (!CSSPropertyParser::parseValue(CSSPropertyMargin, important, tokenizer.tokenRange(), context, properties, StyleRule::Style)) { |
+ return Exception { SyntaxError, "Failed to construct 'IntersectionObserver': rootMargin must be specified in pixels or percent." }; |
+ } |
+ StyleResolver resolver(document); |
+ resolver.state().setStyle(RenderStyle::createPtr()); |
+ StringBuilder builder; |
+ for (auto& prop : properties) { |
+ if (!builder.isEmpty()) { |
+ builder.append(" "); |
+ } |
+ builder.append(prop.value()->cssText()); |
+ resolver.applyPropertyToCurrentStyle(prop.id(), prop.value()); |
+ } |
+ init.rootMargin = builder.toString(); |
+ LengthBox marginBox(Length(resolver.style()->marginTop()), |
+ Length(resolver.style()->marginRight()), |
+ Length(resolver.style()->marginBottom()), |
+ Length(resolver.style()->marginLeft())); |
+ |
+ bool foundOutOfRangeThreshold = false; |
+ if (WTF::holds_alternative<double>(init.threshold)) |
+ foundOutOfRangeThreshold = !isThresholdWithinRange(WTF::get<double>(init.threshold)); |
+ else { |
+ for (auto& threshold : WTF::get<Vector<double>>(init.threshold)) |
+ foundOutOfRangeThreshold |= !isThresholdWithinRange(threshold); |
+ } |
+ if (foundOutOfRangeThreshold) { |
+ return Exception { RangeError, "Failed to construct 'IntersectionObserver': all thresholds must lie in the range [0.0, 1.0]." }; |
+ } |
+ |
+ return adoptRef(*new IntersectionObserver(document, WTFMove(callback), WTFMove(init), WTFMove(marginBox))); |
+} |
+ |
+// FIXME: need to stop observing if the root is deleted. |
+IntersectionObserver::IntersectionObserver(Document& document, Ref<IntersectionObserverCallback>&& callback, Init&& init, LengthBox&& marginBox) |
+ : m_document(document) |
+ , m_root(init.root) |
, m_rootMargin(WTFMove(init.rootMargin)) |
, m_callback(WTFMove(callback)) |
+ , m_marginBox(marginBox) |
{ |
if (WTF::holds_alternative<double>(init.threshold)) |
m_thresholds.append(WTF::get<double>(init.threshold)); |
else |
m_thresholds = WTF::get<Vector<double>>(WTFMove(init.threshold)); |
+ std::sort(m_thresholds.begin(), m_thresholds.end(), std::less<double>()); |
+} |
+ |
+IntersectionObserver::~IntersectionObserver() |
+{ |
+ disconnect(); |
} |
-void IntersectionObserver::observe(Element&) |
+ExceptionOr<void> IntersectionObserver::observe(Element& element) |
{ |
+ if (&element.document() != &m_document) { |
+ m_document.domWindow()->printErrorMessage("IntersectionObserver.observe() on an element from another document is not allowed."); |
+ return Exception { WrongDocumentError }; |
+ } |
+ |
+ element.startObservingIntersections(*this); |
+ return { }; |
} |
-void IntersectionObserver::unobserve(Element&) |
+void IntersectionObserver::unobserve(Element& element) |
{ |
+ if (&element.document() != &m_document) |
+ return; |
+ |
+ element.endObservingIntersections(*this); |
} |
void IntersectionObserver::disconnect() |
{ |
+ for (auto* target : m_observationTargets) |
+ unobserve(*target); |
} |
-Vector<RefPtr<IntersectionObserverEntry>> IntersectionObserver::takeRecords() |
+Vector<Ref<IntersectionObserverEntry>> IntersectionObserver::takeRecords() |
{ |
- return { }; |
+ return WTFMove(m_queuedEntries); |
} |
+void IntersectionObserver::addObservationTarget(Element& element) |
+{ |
+ ASSERT(!m_observationTargets.contains(&element)); |
+ m_observationTargets.append(&element); |
+} |
+ |
+void IntersectionObserver::removeObservationTarget(Element& element) |
+{ |
+ m_observationTargets.removeFirst(&element); |
+} |
+ |
+bool IntersectionObserver::hasObservationTarget(Element& element) const |
+{ |
+ return m_observationTargets.contains(&element); |
+} |
+ |
+void IntersectionObserver::appendQueuedEntry(Ref<IntersectionObserverEntry>&& entry) |
+{ |
+ LOG_WITH_STREAM(IntersectionObserver, stream << " adding queued entry " << entry.get()); |
+ |
+ m_queuedEntries.append(WTFMove(entry)); |
+} |
+ |
+void IntersectionObserver::notify() |
+{ |
+ if (m_queuedEntries.isEmpty()) |
+ return; |
+ |
+ LOG(IntersectionObserver, "IntersectionObserver %p callback(): %lu records", this, m_queuedEntries.size()); |
+ |
+ m_callback->handleEvent(takeRecords(), *this); |
+} |
} // namespace WebCore |