From fb8edcea001607c399b027ab51f746d3fe496e3a Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Mon, 19 Feb 2024 05:06:39 +0100 Subject: [PATCH] LibWeb: Flesh out implementation of ResizeObserver interfaces Adds the initial implementation for interfaces defined in the ResizeObserver specification. These interfaces will be used to construct and send observation events in the upcoming changes. --- Userland/Libraries/LibWeb/CMakeLists.txt | 3 + Userland/Libraries/LibWeb/DOM/Document.cpp | 17 +++ Userland/Libraries/LibWeb/DOM/Document.h | 5 + .../ResizeObserver/ResizeObservation.cpp | 54 ++++++++++ .../LibWeb/ResizeObserver/ResizeObservation.h | 41 +++++++ .../LibWeb/ResizeObserver/ResizeObserver.cpp | 69 ++++++++++-- .../LibWeb/ResizeObserver/ResizeObserver.h | 23 +++- .../LibWeb/ResizeObserver/ResizeObserver.idl | 4 +- .../ResizeObserver/ResizeObserverEntry.cpp | 102 ++++++++++++++++++ .../ResizeObserver/ResizeObserverEntry.h | 56 ++++++++++ .../ResizeObserver/ResizeObserverEntry.idl | 15 +++ .../ResizeObserver/ResizeObserverSize.cpp | 68 ++++++++++++ .../ResizeObserver/ResizeObserverSize.h | 42 ++++++++ .../ResizeObserver/ResizeObserverSize.idl | 6 ++ Userland/Libraries/LibWeb/idl_files.cmake | 2 + 15 files changed, 494 insertions(+), 13 deletions(-) create mode 100644 Userland/Libraries/LibWeb/ResizeObserver/ResizeObservation.cpp create mode 100644 Userland/Libraries/LibWeb/ResizeObserver/ResizeObservation.h create mode 100644 Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverEntry.cpp create mode 100644 Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverEntry.h create mode 100644 Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverEntry.idl create mode 100644 Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverSize.cpp create mode 100644 Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverSize.h create mode 100644 Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverSize.idl diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 6a025ff0f0..5984a6f376 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -539,7 +539,10 @@ set(SOURCES ReferrerPolicy/AbstractOperations.cpp ReferrerPolicy/ReferrerPolicy.cpp RequestIdleCallback/IdleDeadline.cpp + ResizeObserver/ResizeObservation.cpp ResizeObserver/ResizeObserver.cpp + ResizeObserver/ResizeObserverEntry.cpp + ResizeObserver/ResizeObserverSize.cpp SecureContexts/AbstractOperations.cpp SRI/SRI.cpp Streams/AbstractOperations.cpp diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 25e60707a3..9eb02dbddf 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -95,6 +95,8 @@ #include #include #include +#include +#include #include #include #include @@ -453,6 +455,9 @@ void Document::visit_edges(Cell::Visitor& visitor) for (auto& observer : m_intersection_observers) visitor.visit(observer); + for (auto& observer : m_resize_observers) + visitor.visit(observer); + for (auto& image : m_shared_image_requests) visitor.visit(image.value); @@ -3161,6 +3166,18 @@ void Document::unregister_intersection_observer(Badge, ResizeObserver::ResizeObserver& observer) +{ + m_resize_observers.append(observer); +} + +void Document::unregister_resize_observer(Badge, ResizeObserver::ResizeObserver& observer) +{ + m_resize_observers.remove_first_matching([&](auto& registered_observer) { + return registered_observer.ptr() == &observer; + }); +} + // https://www.w3.org/TR/intersection-observer/#queue-an-intersection-observer-task void Document::queue_intersection_observer_task() { diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h index e08d5a27d8..55a040d5a8 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.h +++ b/Userland/Libraries/LibWeb/DOM/Document.h @@ -525,6 +525,9 @@ public: void register_intersection_observer(Badge, IntersectionObserver::IntersectionObserver&); void unregister_intersection_observer(Badge, IntersectionObserver::IntersectionObserver&); + void register_resize_observer(Badge, ResizeObserver::ResizeObserver&); + void unregister_resize_observer(Badge, ResizeObserver::ResizeObserver&); + void run_the_update_intersection_observations_steps(HighResolutionTime::DOMHighResTimeStamp time); void start_intersection_observing_a_lazy_loading_element(Element&); @@ -782,6 +785,8 @@ private: // Each Document has a lazy load intersection observer, initially set to null but can be set to an IntersectionObserver instance. JS::GCPtr m_lazy_load_intersection_observer; + Vector> m_resize_observers; + // https://html.spec.whatwg.org/multipage/semantics.html#will-declaratively-refresh // A Document object has an associated will declaratively refresh (a boolean). It is initially false. bool m_will_declaratively_refresh { false }; diff --git a/Userland/Libraries/LibWeb/ResizeObserver/ResizeObservation.cpp b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObservation.cpp new file mode 100644 index 0000000000..0a6ed721d5 --- /dev/null +++ b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObservation.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Web::ResizeObserver { + +JS_DEFINE_ALLOCATOR(ResizeObservation); + +WebIDL::ExceptionOr> ResizeObservation::create(JS::Realm& realm, DOM::Element& target, Bindings::ResizeObserverBoxOptions observed_box) +{ + return realm.heap().allocate(realm, realm, target, observed_box); +} + +ResizeObservation::ResizeObservation(JS::Realm& realm, DOM::Element& target, Bindings::ResizeObserverBoxOptions observed_box) + : m_realm(realm) + , m_target(target) + , m_observed_box(observed_box) +{ + auto computed_size = realm.heap().allocate(realm, realm); + m_last_reported_sizes.append(computed_size); +} + +void ResizeObservation::visit_edges(JS::Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_target); + for (auto& size : m_last_reported_sizes) + visitor.visit(size); +} + +// https://drafts.csswg.org/resize-observer-1/#dom-resizeobservation-isactive +bool ResizeObservation::is_active() +{ + // 1. Set currentSize by calculate box size given target and observedBox. + auto current_size = ResizeObserverSize::calculate_box_size(m_realm, m_target, m_observed_box); + + // 2. Return true if currentSize is not equal to the first entry in this.lastReportedSizes. + VERIFY(!m_last_reported_sizes.is_empty()); + if (!m_last_reported_sizes.first()->equals(*current_size)) + return true; + + // 3. Return false. + return false; +} + +} diff --git a/Userland/Libraries/LibWeb/ResizeObserver/ResizeObservation.h b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObservation.h new file mode 100644 index 0000000000..0beeb82ddc --- /dev/null +++ b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObservation.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Web::ResizeObserver { + +// https://drafts.csswg.org/resize-observer-1/#resize-observation-interface +class ResizeObservation : public JS::Cell { + JS_CELL(ResizeObservation, JS::Cell); + JS_DECLARE_ALLOCATOR(ResizeObservation); + +public: + static WebIDL::ExceptionOr> create(JS::Realm&, DOM::Element&, Bindings::ResizeObserverBoxOptions); + + bool is_active(); + + JS::NonnullGCPtr target() const { return m_target; } + Bindings::ResizeObserverBoxOptions observed_box() const { return m_observed_box; } + + Vector>& last_reported_sizes() { return m_last_reported_sizes; } + + explicit ResizeObservation(JS::Realm& realm, DOM::Element& target, Bindings::ResizeObserverBoxOptions observed_box); + +private: + virtual void visit_edges(JS::Cell::Visitor&) override; + + JS::NonnullGCPtr m_realm; + JS::NonnullGCPtr m_target; + Bindings::ResizeObserverBoxOptions m_observed_box; + Vector> m_last_reported_sizes; +}; + +} diff --git a/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserver.cpp b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserver.cpp index 379bbdc3e1..83ec505478 100644 --- a/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserver.cpp +++ b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserver.cpp @@ -1,12 +1,16 @@ /* * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2024, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ #include -#include +#include +#include +#include #include +#include namespace Web::ResizeObserver { @@ -15,14 +19,16 @@ JS_DEFINE_ALLOCATOR(ResizeObserver); // https://drafts.csswg.org/resize-observer/#dom-resizeobserver-resizeobserver WebIDL::ExceptionOr> ResizeObserver::construct_impl(JS::Realm& realm, WebIDL::CallbackType* callback) { - // FIXME: Implement - (void)callback; - return realm.heap().allocate(realm, realm); + return realm.heap().allocate(realm, realm, callback); } -ResizeObserver::ResizeObserver(JS::Realm& realm) +ResizeObserver::ResizeObserver(JS::Realm& realm, WebIDL::CallbackType* callback) : PlatformObject(realm) + , m_callback(callback) { + auto navigable = verify_cast(HTML::relevant_global_object(*this)).navigable(); + m_document = navigable->active_document().ptr(); + m_document->register_resize_observer({}, *this); } ResizeObserver::~ResizeObserver() = default; @@ -33,12 +39,40 @@ void ResizeObserver::initialize(JS::Realm& realm) set_prototype(&Bindings::ensure_web_prototype(realm, "ResizeObserver"_fly_string)); } -// https://drafts.csswg.org/resize-observer/#dom-resizeobserver-observe +void ResizeObserver::visit_edges(JS::Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_callback); + for (auto& observation : m_observation_targets) + visitor.visit(observation); + for (auto& observation : m_active_targets) + visitor.visit(observation); + for (auto& observation : m_skipped_targets) + visitor.visit(observation); +} + +void ResizeObserver::finalize() +{ + if (m_document) + m_document->unregister_resize_observer({}, *this); +} + +// https://drafts.csswg.org/resize-observer-1/#dom-resizeobserver-observe void ResizeObserver::observe(DOM::Element& target, ResizeObserverOptions options) { - // FIXME: Implement - (void)target; - (void)options; + // 1. If target is in [[observationTargets]] slot, call unobserve() with argument target. + auto observation = m_observation_targets.find_if([&](auto& observation) { return observation->target().ptr() == ⌖ }); + if (!observation.is_end()) + unobserve(target); + + // 2. Let observedBox be the value of the box dictionary member of options. + auto observed_box = options.box; + + // 3. Let resizeObservation be new ResizeObservation(target, observedBox). + auto resize_observation = MUST(ResizeObservation::create(realm(), target, observed_box)); + + // 4. Add the resizeObservation to the [[observationTargets]] slot. + m_observation_targets.append(resize_observation); } // https://drafts.csswg.org/resize-observer/#dom-resizeobserver-unobserve @@ -54,4 +88,21 @@ void ResizeObserver::disconnect() // FIXME: Implement } +void ResizeObserver::invoke_callback(Vector>& entries) const +{ + auto& callback = *m_callback; + auto& realm = callback.callback_context->realm(); + + auto wrapped_records = MUST(JS::Array::create(realm, 0)); + for (size_t i = 0; i < entries.size(); ++i) { + auto& record = entries.at(i); + auto property_index = JS::PropertyKey { i }; + MUST(wrapped_records->create_data_property(property_index, record.ptr())); + } + + auto result = WebIDL::invoke_callback(callback, JS::js_undefined(), wrapped_records); + if (result.is_abrupt()) + HTML::report_exception(result, realm); +} + } diff --git a/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserver.h b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserver.h index 4a05669aea..0d0f0891c4 100644 --- a/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserver.h +++ b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserver.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2024, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,6 +8,8 @@ #pragma once #include +#include +#include namespace Web::ResizeObserver { @@ -14,7 +17,7 @@ struct ResizeObserverOptions { Bindings::ResizeObserverBoxOptions box; }; -// https://drafts.csswg.org/resize-observer/#resize-observer-interface +// https://drafts.csswg.org/resize-observer-1/#resize-observer-interface class ResizeObserver : public Bindings::PlatformObject { WEB_PLATFORM_OBJECT(ResizeObserver, Bindings::PlatformObject); JS_DECLARE_ALLOCATOR(ResizeObserver); @@ -28,10 +31,26 @@ public: void unobserve(DOM::Element& target); void disconnect(); + void invoke_callback(Vector>& entries) const; + + Vector>& observation_targets() { return m_observation_targets; } + Vector>& active_targets() { return m_active_targets; } + Vector>& skipped_targets() { return m_skipped_targets; } + private: - explicit ResizeObserver(JS::Realm&); + explicit ResizeObserver(JS::Realm&, WebIDL::CallbackType* callback); virtual void initialize(JS::Realm&) override; + virtual void visit_edges(JS::Cell::Visitor&) override; + virtual void finalize() override; + + JS::GCPtr m_callback; + Vector> m_observation_targets; + Vector> m_active_targets; + Vector> m_skipped_targets; + + // AD-HOC: This is the document where we've registered the observer. + WeakPtr m_document; }; } diff --git a/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserver.idl b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserver.idl index e0d964d7c7..695ee742b3 100644 --- a/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserver.idl +++ b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserver.idl @@ -1,6 +1,6 @@ #import -// https://drafts.csswg.org/resize-observer/#resize-observer-interface +// https://drafts.csswg.org/resize-observer-1/#resize-observer-interface [Exposed=(Window)] interface ResizeObserver { @@ -19,5 +19,5 @@ dictionary ResizeObserverOptions { ResizeObserverBoxOptions box = "content-box"; }; -// https://drafts.csswg.org/resize-observer/#resize-observer-callback +// https://drafts.csswg.org/resize-observer-1/#resize-observer-callback callback ResizeObserverCallback = undefined (sequence entries, ResizeObserver observer); diff --git a/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverEntry.cpp b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverEntry.cpp new file mode 100644 index 0000000000..9f04005fa9 --- /dev/null +++ b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverEntry.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace Web::ResizeObserver { + +JS_DEFINE_ALLOCATOR(ResizeObserverEntry); + +// https://drafts.csswg.org/resize-observer-1/#create-and-populate-resizeobserverentry-h +WebIDL::ExceptionOr> ResizeObserverEntry::create_and_populate(JS::Realm& realm, DOM::Element& target) +{ + // 1. Let this be a new ResizeObserverEntry. + // 2. Set this.target slot to target. + auto resize_observer_entry = realm.heap().allocate(realm, realm, target); + + // 3. Set this.borderBoxSize slot to result of calculating box size given target and observedBox of "border-box". + auto border_box_size = ResizeObserverSize::calculate_box_size(realm, target, Bindings::ResizeObserverBoxOptions::BorderBox); + resize_observer_entry->m_border_box_size.append(border_box_size); + + // 4. Set this.contentBoxSize slot to result of calculating box size given target and observedBox of "content-box". + auto content_box_size = ResizeObserverSize::calculate_box_size(realm, target, Bindings::ResizeObserverBoxOptions::ContentBox); + resize_observer_entry->m_content_box_size.append(content_box_size); + + // 5. Set this.devicePixelContentBoxSize slot to result of calculating box size given target and observedBox of "device-pixel-content-box". + auto device_pixel_content_box_size = ResizeObserverSize::calculate_box_size(realm, target, Bindings::ResizeObserverBoxOptions::DevicePixelContentBox); + resize_observer_entry->m_device_pixel_content_box_size.append(device_pixel_content_box_size); + + // 6. Set this.contentRect to logical this.contentBoxSize given target and observedBox of "content-box". + double x = 0; + double y = 0; + double width = content_box_size->inline_size(); + double height = content_box_size->block_size(); + + // 7. If target is not an SVG element or target is an SVG element with an associated CSS layout box do these steps: + if (!target.is_svg_element() && target.paintable_box()) { + auto const& paintable_box = *target.paintable_box(); + auto absolute_padding_rect = paintable_box.absolute_padding_box_rect(); + // Set this.contentRect.top to target.padding top. + y = absolute_padding_rect.y().to_double(); + // Set this.contentRect.left to target.padding left. + x = absolute_padding_rect.x().to_double(); + } else if (target.is_svg_element() && target.paintable_box()) { + // 8. If target is an SVG element without an associated CSS layout box do these steps: + // Set this.contentRect.top and this.contentRect.left to 0. + // NOTE: This is already done by the default constructor. + } + resize_observer_entry->m_content_rect = MUST(Geometry::DOMRectReadOnly::construct_impl(realm, x, y, width, height)); + + return resize_observer_entry; +} + +void ResizeObserverEntry::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + set_prototype(&Bindings::ensure_web_prototype(realm, "ResizeObserverEntry"_fly_string)); +} + +void ResizeObserverEntry::visit_edges(JS::Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_target); + for (auto& size : m_content_box_size) + visitor.visit(size); + for (auto& size : m_border_box_size) + visitor.visit(size); +} + +static JS::NonnullGCPtr to_js_array(JS::Realm& realm, Vector> const& sizes) +{ + Vector vector; + for (auto const& size : sizes) + vector.append(JS::Value(size.ptr())); + auto array = JS::Array::create_from(realm, vector); + MUST(array->set_integrity_level(JS::Object::IntegrityLevel::Frozen)); + return array; +} + +JS::NonnullGCPtr ResizeObserverEntry::border_box_size_js_array() const +{ + return to_js_array(realm(), m_border_box_size); +} + +JS::NonnullGCPtr ResizeObserverEntry::content_box_size_js_array() const +{ + return to_js_array(realm(), m_content_box_size); +} + +JS::NonnullGCPtr ResizeObserverEntry::device_pixel_content_box_size_js_array() const +{ + return to_js_array(realm(), m_device_pixel_content_box_size); +} + +} diff --git a/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverEntry.h b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverEntry.h new file mode 100644 index 0000000000..12aa1f8016 --- /dev/null +++ b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverEntry.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Web::ResizeObserver { + +// https://drafts.csswg.org/resize-observer-1/#resize-observer-entry-interface +class ResizeObserverEntry : public Bindings::PlatformObject { + WEB_PLATFORM_OBJECT(ResizeObserverEntry, Bindings::PlatformObject); + JS_DECLARE_ALLOCATOR(ResizeObserverEntry); + +public: + static WebIDL::ExceptionOr> create_and_populate(JS::Realm&, DOM::Element& target); + + JS::NonnullGCPtr content_rect() const { return *m_content_rect; } + JS::NonnullGCPtr target() const { return m_target; } + + Vector> const& border_box_size() const { return m_border_box_size; } + Vector> const& content_box_size() const { return m_content_box_size; } + Vector> const& device_pixel_content_box_size() const { return m_device_pixel_content_box_size; } + + JS::NonnullGCPtr border_box_size_js_array() const; + JS::NonnullGCPtr content_box_size_js_array() const; + JS::NonnullGCPtr device_pixel_content_box_size_js_array() const; + +private: + explicit ResizeObserverEntry(JS::Realm& realm, DOM::Element& target) + : PlatformObject(realm) + , m_target(target) + { + } + + virtual void initialize(JS::Realm&) override; + virtual void visit_edges(JS::Cell::Visitor&) override; + + JS::NonnullGCPtr m_target; + + Vector> m_content_box_size; + Vector> m_border_box_size; + Vector> m_device_pixel_content_box_size; + + JS::GCPtr m_content_rect; +}; + +} diff --git a/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverEntry.idl b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverEntry.idl new file mode 100644 index 0000000000..8ff54e12df --- /dev/null +++ b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverEntry.idl @@ -0,0 +1,15 @@ +#import +#import + +// https://drafts.csswg.org/resize-observer-1/#resize-observer-entry-interface +[Exposed=Window] +interface ResizeObserverEntry { + readonly attribute Element target; + readonly attribute DOMRectReadOnly contentRect; + // FIXME: Return FrozenArray instead of any. + [ImplementedAs=border_box_size_js_array] readonly attribute any borderBoxSize; + // FIXME: Return FrozenArray instead of any. + [ImplementedAs=content_box_size_js_array] readonly attribute any contentBoxSize; + // FIXME: Return FrozenArray instead of any. + [ImplementedAs=device_pixel_content_box_size_js_array] readonly attribute any devicePixelContentBoxSize; +}; diff --git a/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverSize.cpp b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverSize.cpp new file mode 100644 index 0000000000..85d3533575 --- /dev/null +++ b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverSize.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Web::ResizeObserver { + +JS_DEFINE_ALLOCATOR(ResizeObserverSize); + +void ResizeObserverSize::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + set_prototype(&Bindings::ensure_web_prototype(realm, "ResizeObserverSize"_fly_string)); +} + +// https://drafts.csswg.org/resize-observer-1/#calculate-box-size +JS::NonnullGCPtr ResizeObserverSize::calculate_box_size(JS::Realm& realm, DOM::Element& target, Bindings::ResizeObserverBoxOptions observed_box) +{ + // 1. Let computedSize be a new ResizeObserverSize object. + auto computed_size = realm.heap().allocate(realm, realm); + + // FIXME: 2. If target is an SVGGraphicsElement that does not have an associated CSS layout box: + // Otherwise: + if (target.paintable_box()) { + auto const& paintable_box = *target.paintable_box(); + switch (observed_box) { + case Bindings::ResizeObserverBoxOptions::BorderBox: + // 1. Set computedSize’s inlineSize attribute to target’s border area inline length. + computed_size->set_inline_size(paintable_box.border_box_width().to_double()); + // 2. Set computedSize’s blockSize attribute to target’s border area block length. + computed_size->set_block_size(paintable_box.border_box_height().to_double()); + break; + case Bindings::ResizeObserverBoxOptions::ContentBox: + // 1. Set computedSize’s inlineSize attribute to target’s content area inline length. + computed_size->set_inline_size(paintable_box.content_width().to_double()); + // 2. Set computedSize’s blockSize attribute to target’s content area block length. + computed_size->set_block_size(paintable_box.content_height().to_double()); + break; + case Bindings::ResizeObserverBoxOptions::DevicePixelContentBox: { + auto device_pixel_ratio = target.document().window().device_pixel_ratio(); + // 1. Set computedSize’s inlineSize attribute to target’s content area inline length, in integral device pixels. + computed_size->set_inline_size(paintable_box.border_box_width().to_double() * device_pixel_ratio); + // 2. Set computedSize’s blockSize attribute to target’s content area block length, in integral device pixels. + computed_size->set_block_size(paintable_box.border_box_height().to_double() * device_pixel_ratio); + break; + } + default: + VERIFY_NOT_REACHED(); + } + } + + // 3. Return computedSize.s + return computed_size; +} + +bool ResizeObserverSize::equals(ResizeObserverSize const& other) const +{ + return m_inline_size == other.m_inline_size && m_block_size == other.m_block_size; +} + +} diff --git a/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverSize.h b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverSize.h new file mode 100644 index 0000000000..3fa74feece --- /dev/null +++ b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverSize.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::ResizeObserver { + +// https://drafts.csswg.org/resize-observer-1/#resizeobserversize +class ResizeObserverSize : public Bindings::PlatformObject { + WEB_PLATFORM_OBJECT(ResizeObserverSize, Bindings::PlatformObject); + JS_DECLARE_ALLOCATOR(ResizeObserverSize); + +public: + static JS::NonnullGCPtr calculate_box_size(JS::Realm& realm, DOM::Element& target, Bindings::ResizeObserverBoxOptions observed_box); + + double inline_size() const { return m_inline_size; } + void set_inline_size(double inline_size) { m_inline_size = inline_size; } + + double block_size() const { return m_block_size; } + void set_block_size(double block_size) { m_block_size = block_size; } + + bool equals(ResizeObserverSize const& other) const; + +private: + explicit ResizeObserverSize(JS::Realm& realm) + : PlatformObject(realm) + { + } + + virtual void initialize(JS::Realm&) override; + + double m_inline_size { 0 }; + double m_block_size { 0 }; +}; + +} diff --git a/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverSize.idl b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverSize.idl new file mode 100644 index 0000000000..490714ec76 --- /dev/null +++ b/Userland/Libraries/LibWeb/ResizeObserver/ResizeObserverSize.idl @@ -0,0 +1,6 @@ +// https://drafts.csswg.org/resize-observer-1/#resizeobserversize +[Exposed=Window] +interface ResizeObserverSize { + readonly attribute unrestricted double inlineSize; + readonly attribute unrestricted double blockSize; +}; diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index 7039a11b54..f78a802b25 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -216,6 +216,8 @@ libweb_js_bindings(PerformanceTimeline/PerformanceObserver) libweb_js_bindings(PerformanceTimeline/PerformanceObserverEntryList) libweb_js_bindings(RequestIdleCallback/IdleDeadline) libweb_js_bindings(ResizeObserver/ResizeObserver) +libweb_js_bindings(ResizeObserver/ResizeObserverEntry) +libweb_js_bindings(ResizeObserver/ResizeObserverSize) libweb_js_bindings(Streams/ByteLengthQueuingStrategy) libweb_js_bindings(Streams/CountQueuingStrategy) libweb_js_bindings(Streams/ReadableByteStreamController)