123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- /*
- * Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #include <AK/QuickSort.h>
- #include <LibWeb/Bindings/Intrinsics.h>
- #include <LibWeb/DOM/Document.h>
- #include <LibWeb/DOM/Element.h>
- #include <LibWeb/IntersectionObserver/IntersectionObserver.h>
- namespace Web::IntersectionObserver {
- // https://w3c.github.io/IntersectionObserver/#dom-intersectionobserver-intersectionobserver
- WebIDL::ExceptionOr<JS::NonnullGCPtr<IntersectionObserver>> IntersectionObserver::construct_impl(JS::Realm& realm, JS::GCPtr<WebIDL::CallbackType> callback, IntersectionObserverInit const& options)
- {
- // 4. Let thresholds be a list equal to options.threshold.
- Vector<double> thresholds;
- if (options.threshold.has<double>()) {
- thresholds.append(options.threshold.get<double>());
- } else {
- VERIFY(options.threshold.has<Vector<double>>());
- thresholds = options.threshold.get<Vector<double>>();
- }
- // 5. If any value in thresholds is less than 0.0 or greater than 1.0, throw a RangeError exception.
- for (auto value : thresholds) {
- if (value < 0.0 || value > 1.0)
- return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "Threshold values must be between 0.0 and 1.0 inclusive"sv };
- }
- // 6. Sort thresholds in ascending order.
- quick_sort(thresholds, [](double left, double right) {
- return left < right;
- });
- // 1. Let this be a new IntersectionObserver object
- // 2. Set this’s internal [[callback]] slot to callback.
- // 8. The thresholds attribute getter will return this sorted thresholds list.
- // 9. Return this.
- return realm.heap().allocate<IntersectionObserver>(realm, realm, callback, options.root, move(thresholds));
- }
- IntersectionObserver::IntersectionObserver(JS::Realm& realm, JS::GCPtr<WebIDL::CallbackType> callback, Optional<Variant<JS::Handle<DOM::Element>, JS::Handle<DOM::Document>>> const& root, Vector<double>&& thresholds)
- : PlatformObject(realm)
- , m_callback(callback)
- , m_root(root)
- , m_thresholds(move(thresholds))
- {
- intersection_root().visit([this](auto& node) {
- node->document().register_intersection_observer({}, *this);
- });
- }
- IntersectionObserver::~IntersectionObserver() = default;
- void IntersectionObserver::finalize()
- {
- intersection_root().visit([this](auto& node) {
- node->document().unregister_intersection_observer({}, *this);
- });
- }
- void IntersectionObserver::initialize(JS::Realm& realm)
- {
- Base::initialize(realm);
- set_prototype(&Bindings::ensure_web_prototype<Bindings::IntersectionObserverPrototype>(realm, "IntersectionObserver"));
- }
- void IntersectionObserver::visit_edges(JS::Cell::Visitor& visitor)
- {
- Base::visit_edges(visitor);
- visitor.visit(m_callback);
- for (auto& entry : m_queued_entries)
- visitor.visit(entry);
- for (auto& target : m_observation_targets)
- visitor.visit(target);
- }
- // https://w3c.github.io/IntersectionObserver/#dom-intersectionobserver-observe
- void IntersectionObserver::observe(DOM::Element& target)
- {
- // Run the observe a target Element algorithm, providing this and target.
- // https://www.w3.org/TR/intersection-observer/#observe-a-target-element
- // 1. If target is in observer’s internal [[ObservationTargets]] slot, return.
- if (m_observation_targets.contains_slow(JS::NonnullGCPtr { target }))
- return;
- // 2. Let intersectionObserverRegistration be an IntersectionObserverRegistration record with an observer
- // property set to observer, a previousThresholdIndex property set to -1, and a previousIsIntersecting
- // property set to false.
- auto intersection_observer_registration = IntersectionObserverRegistration {
- .observer = JS::make_handle(*this),
- .previous_threshold_index = OptionalNone {},
- .previous_is_intersecting = false,
- };
- // 3. Append intersectionObserverRegistration to target’s internal [[RegisteredIntersectionObservers]] slot.
- target.register_intersection_observer({}, move(intersection_observer_registration));
- // 4. Add target to observer’s internal [[ObservationTargets]] slot.
- m_observation_targets.append(target);
- }
- // https://w3c.github.io/IntersectionObserver/#dom-intersectionobserver-unobserve
- void IntersectionObserver::unobserve(DOM::Element& target)
- {
- // Run the unobserve a target Element algorithm, providing this and target.
- // https://www.w3.org/TR/intersection-observer/#unobserve-a-target-element
- // 1. Remove the IntersectionObserverRegistration record whose observer property is equal to this from target’s internal [[RegisteredIntersectionObservers]] slot, if present.
- target.unregister_intersection_observer({}, *this);
- // 2. Remove target from this’s internal [[ObservationTargets]] slot, if present
- m_observation_targets.remove_first_matching([&target](JS::NonnullGCPtr<DOM::Element> const& entry) {
- return entry.ptr() == ⌖
- });
- }
- // https://w3c.github.io/IntersectionObserver/#dom-intersectionobserver-disconnect
- void IntersectionObserver::disconnect()
- {
- // For each target in this’s internal [[ObservationTargets]] slot:
- // 1. Remove the IntersectionObserverRegistration record whose observer property is equal to this from target’s internal
- // [[RegisteredIntersectionObservers]] slot.
- // 2. Remove target from this’s internal [[ObservationTargets]] slot.
- for (auto& target : m_observation_targets) {
- target->unregister_intersection_observer({}, *this);
- }
- m_observation_targets.clear();
- }
- // https://www.w3.org/TR/intersection-observer/#dom-intersectionobserver-takerecords
- Vector<JS::Handle<IntersectionObserverEntry>> IntersectionObserver::take_records()
- {
- // 1. Let queue be a copy of this’s internal [[QueuedEntries]] slot.
- Vector<JS::Handle<IntersectionObserverEntry>> queue;
- for (auto& entry : m_queued_entries)
- queue.append(*entry);
- // 2. Clear this’s internal [[QueuedEntries]] slot.
- m_queued_entries.clear();
- // 3. Return queue.
- return queue;
- }
- Variant<JS::Handle<DOM::Element>, JS::Handle<DOM::Document>, Empty> IntersectionObserver::root() const
- {
- if (!m_root.has_value())
- return Empty {};
- return m_root.value();
- }
- Variant<JS::Handle<DOM::Element>, JS::Handle<DOM::Document>> IntersectionObserver::intersection_root() const
- {
- if (!m_root.has_value())
- return JS::make_handle(global_object().browsing_context()->top_level_browsing_context().active_document());
- return m_root.value();
- }
- // https://www.w3.org/TR/intersection-observer/#intersectionobserver-root-intersection-rectangle
- CSSPixelRect IntersectionObserver::root_intersection_rectangle() const
- {
- // If the IntersectionObserver is an implicit root observer,
- // it’s treated as if the root were the top-level browsing context’s document, according to the following rule for document.
- auto intersection_root = this->intersection_root();
- CSSPixelRect rect;
- // If the intersection root is a document,
- // it’s the size of the document's viewport (note that this processing step can only be reached if the document is fully active).
- if (intersection_root.has<JS::Handle<DOM::Document>>()) {
- auto document = intersection_root.get<JS::Handle<DOM::Document>>();
- // Since the spec says that this is only reach if the document is fully active, that means it must have a navigable.
- VERIFY(document->navigable());
- // NOTE: This rect is the *size* of the viewport. The viewport *offset* is not relevant,
- // as intersections are computed using viewport-relative element rects.
- rect = CSSPixelRect { CSSPixelPoint { 0, 0 }, document->viewport_rect().size() };
- } else {
- VERIFY(intersection_root.has<JS::Handle<DOM::Element>>());
- auto element = intersection_root.get<JS::Handle<DOM::Element>>();
- // FIXME: Otherwise, if the intersection root has a content clip,
- // it’s the element’s content area.
- // Otherwise,
- // it’s the result of getting the bounding box for the intersection root.
- auto bounding_client_rect = element->get_bounding_client_rect();
- rect = CSSPixelRect(bounding_client_rect->x(), bounding_client_rect->y(), bounding_client_rect->width(), bounding_client_rect->height());
- }
- // FIXME: When calculating the root intersection rectangle for a same-origin-domain target, the rectangle is then
- // expanded according to the offsets in the IntersectionObserver’s [[rootMargin]] slot in a manner similar
- // to CSS’s margin property, with the four values indicating the amount the top, right, bottom, and left
- // edges, respectively, are offset by, with positive lengths indicating an outward offset. Percentages
- // are resolved relative to the width of the undilated rectangle.
- return rect;
- }
- void IntersectionObserver::queue_entry(Badge<DOM::Document>, JS::NonnullGCPtr<IntersectionObserverEntry> entry)
- {
- m_queued_entries.append(entry);
- }
- }
|