From 2b6d9cca1ad16559a88cb951be9aab0a43c36038 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Mon, 6 Mar 2023 20:53:49 +0000 Subject: [PATCH] LibWeb/HTML: Port Window.scroll() to IDL ...with various changes required due to ScrollBehavior / ScrollOptions moving from Element.idl to Window.idl. --- Userland/Libraries/LibWeb/DOM/Element.h | 9 +- Userland/Libraries/LibWeb/DOM/Element.idl | 6 +- Userland/Libraries/LibWeb/HTML/Window.cpp | 162 +++++++++++++--------- Userland/Libraries/LibWeb/HTML/Window.h | 14 +- Userland/Libraries/LibWeb/HTML/Window.idl | 14 ++ 5 files changed, 126 insertions(+), 79 deletions(-) diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h index 5beb5257bf..86a5104843 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.h +++ b/Userland/Libraries/LibWeb/DOM/Element.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -33,13 +35,8 @@ struct ShadowRootInit { bool delegates_focus = false; }; -// https://w3c.github.io/csswg-drafts/cssom-view-1/#dictdef-scrolloptions -struct ScrollOptions { - Bindings::ScrollBehavior behavior { Bindings::ScrollBehavior::Auto }; -}; - // https://w3c.github.io/csswg-drafts/cssom-view-1/#dictdef-scrollintoviewoptions -struct ScrollIntoViewOptions : public ScrollOptions { +struct ScrollIntoViewOptions : public HTML::ScrollOptions { Bindings::ScrollLogicalPosition block { Bindings::ScrollLogicalPosition::Start }; Bindings::ScrollLogicalPosition inline_ { Bindings::ScrollLogicalPosition::Nearest }; }; diff --git a/Userland/Libraries/LibWeb/DOM/Element.idl b/Userland/Libraries/LibWeb/DOM/Element.idl index 34ea92ca9f..6614c8c841 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.idl +++ b/Userland/Libraries/LibWeb/DOM/Element.idl @@ -11,11 +11,7 @@ #import #import #import - -enum ScrollBehavior { "auto", "smooth" }; -dictionary ScrollOptions { - ScrollBehavior behavior = "auto"; -}; +#import enum ScrollLogicalPosition { "start", "center", "end", "nearest" }; dictionary ScrollIntoViewOptions : ScrollOptions { diff --git a/Userland/Libraries/LibWeb/HTML/Window.cpp b/Userland/Libraries/LibWeb/HTML/Window.cpp index b6ed58537c..bb3a4ad633 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.cpp +++ b/Userland/Libraries/LibWeb/HTML/Window.cpp @@ -1017,8 +1017,6 @@ WebIDL::ExceptionOr Window::initialize_web_interfaces(Badge element, Bindings::ScrollBehavior behavior) +{ + // FIXME: 1. Abort any ongoing smooth scroll for box. + // 2. If the user agent honors the scroll-behavior property and one of the following are true: + // - behavior is "auto" and element is not null and its computed value of the scroll-behavior property is smooth + // - behavior is smooth + // ...then perform a smooth scroll of box to position. Once the position has finished updating, emit the scrollend + // event. Otherwise, perform an instant scroll of box to position. After an instant scroll emit the scrollend event. + // FIXME: Support smooth scrolling. + (void)element; + (void)behavior; + page.client().page_did_request_scroll_to({ x, y }); +} + +// https://w3c.github.io/csswg-drafts/cssom-view/#dom-window-scroll +void Window::scroll(ScrollToOptions const& options) +{ + // 4. If there is no viewport, abort these steps. + auto* page = this->page(); + if (!page) + return; + auto const& top_level_browsing_context = page->top_level_browsing_context(); + + // 1. If invoked with one argument, follow these substeps: + + // 1. Let options be the argument. + auto viewport_rect = top_level_browsing_context.viewport_rect().to_type(); + + // 2. Let x be the value of the left dictionary member of options, if present, or the viewport’s current scroll + // position on the x axis otherwise. + auto x = options.left.value_or(viewport_rect.x()); + + // 3. Let y be the value of the top dictionary member of options, if present, or the viewport’s current scroll + // position on the y axis otherwise. + auto y = options.top.value_or(viewport_rect.y()); + + // 3. Normalize non-finite values for x and y. + x = JS::Value(x).is_finite_number() ? x : 0; + y = JS::Value(y).is_finite_number() ? y : 0; + + // 5. Let viewport width be the width of the viewport excluding the width of the scroll bar, if any. + auto viewport_width = viewport_rect.width(); + + // 6. Let viewport height be the height of the viewport excluding the height of the scroll bar, if any. + auto viewport_height = viewport_rect.height(); + + (void)viewport_width; + (void)viewport_height; + + // FIXME: 7. + // -> If the viewport has rightward overflow direction + // Let x be max(0, min(x, viewport scrolling area width - viewport width)). + // -> If the viewport has leftward overflow direction + // Let x be min(0, max(x, viewport width - viewport scrolling area width)). + + // FIXME: 8. + // -> If the viewport has downward overflow direction + // Let y be max(0, min(y, viewport scrolling area height - viewport height)). + // -> If the viewport has upward overflow direction + // Let y be min(0, max(y, viewport height - viewport scrolling area height)). + + // FIXME: 9. Let position be the scroll position the viewport would have by aligning the x-coordinate x of the viewport + // scrolling area with the left of the viewport and aligning the y-coordinate y of the viewport scrolling area + // with the top of the viewport. + + // FIXME: 10. If position is the same as the viewport’s current scroll position, and the viewport does not have an ongoing + // smooth scroll, abort these steps. + + // 11. Let document be the viewport’s associated Document. + auto const* document = top_level_browsing_context.active_document(); + + // 12. Perform a scroll of the viewport to position, document’s root element as the associated element, if there is + // one, or null otherwise, and the scroll behavior being the value of the behavior dictionary member of options. + auto element = JS::GCPtr { document ? &document->root() : nullptr }; + perform_a_scroll(*page, x, y, element, options.behavior); +} + +// https://w3c.github.io/csswg-drafts/cssom-view/#dom-window-scroll +void Window::scroll(double x, double y) +{ + // 2. If invoked with two arguments, follow these substeps: + + // 1. Let options be null converted to a ScrollToOptions dictionary. [WEBIDL] + auto options = ScrollToOptions {}; + + // 2. Let x and y be the arguments, respectively. + options.left = x; + options.top = y; + + scroll(options); +} + // https://w3c.github.io/hr-time/#dom-windoworworkerglobalscope-performance WebIDL::ExceptionOr> Window::performance() { @@ -1552,69 +1643,6 @@ JS_DEFINE_NATIVE_FUNCTION(Window::get_selection) return impl->associated_document().get_selection(); } -enum class ScrollBehavior { - Auto, - Smooth -}; - -// https://www.w3.org/TR/cssom-view/#perform-a-scroll -static void perform_a_scroll(Page& page, double x, double y, ScrollBehavior) -{ - // FIXME: Stop any existing smooth-scrolls - // FIXME: Implement smooth-scroll - page.client().page_did_request_scroll_to({ x, y }); -} - -// https://www.w3.org/TR/cssom-view/#dom-window-scroll -JS_DEFINE_NATIVE_FUNCTION(Window::scroll) -{ - auto* impl = TRY(impl_from(vm)); - if (!impl->page()) - return JS::js_undefined(); - auto& page = *impl->page(); - - auto viewport_rect = page.top_level_browsing_context().viewport_rect().to_type(); - auto x_value = JS::Value(viewport_rect.x()); - auto y_value = JS::Value(viewport_rect.y()); - DeprecatedString behavior_string = "auto"; - - if (vm.argument_count() == 1) { - auto* options = TRY(vm.argument(0).to_object(vm)); - auto left = TRY(options->get("left")); - if (!left.is_undefined()) - x_value = left; - - auto top = TRY(options->get("top")); - if (!top.is_undefined()) - y_value = top; - - auto behavior_string_value = TRY(options->get("behavior")); - if (!behavior_string_value.is_undefined()) - behavior_string = TRY(behavior_string_value.to_deprecated_string(vm)); - if (behavior_string != "smooth" && behavior_string != "auto") - return vm.throw_completion("Behavior is not one of 'smooth' or 'auto'"sv); - - } else if (vm.argument_count() >= 2) { - // We ignore arguments 2+ in line with behavior of Chrome and Firefox - x_value = vm.argument(0); - y_value = vm.argument(1); - } - - ScrollBehavior behavior = (behavior_string == "smooth") ? ScrollBehavior::Smooth : ScrollBehavior::Auto; - - double x = TRY(x_value.to_double(vm)); - x = JS::Value(x).is_finite_number() ? x : 0.0; - - double y = TRY(y_value.to_double(vm)); - y = JS::Value(y).is_finite_number() ? y : 0.0; - - // FIXME: Are we calculating the viewport in the way this function expects? - // FIXME: Handle overflow-directions other than top-left to bottom-right - - perform_a_scroll(page, x, y, behavior); - return JS::js_undefined(); -} - // https://www.w3.org/TR/cssom-view/#dom-window-scrollby JS_DEFINE_NATIVE_FUNCTION(Window::scroll_by) { @@ -1656,12 +1684,12 @@ JS_DEFINE_NATIVE_FUNCTION(Window::scroll_by) auto behavior_string = behavior_string_value.is_undefined() ? "auto" : TRY(behavior_string_value.to_deprecated_string(vm)); if (behavior_string != "smooth" && behavior_string != "auto") return vm.throw_completion("Behavior is not one of 'smooth' or 'auto'"sv); - ScrollBehavior behavior = (behavior_string == "smooth") ? ScrollBehavior::Smooth : ScrollBehavior::Auto; + auto behavior = (behavior_string == "smooth") ? Bindings::ScrollBehavior::Smooth : Bindings::ScrollBehavior::Auto; // FIXME: Spec wants us to call scroll(options) here. // The only difference is that would invoke the viewport calculations that scroll() // is not actually doing yet, so this is the same for now. - perform_a_scroll(page, left, top, behavior); + perform_a_scroll(page, left, top, nullptr, behavior); return JS::js_undefined(); } diff --git a/Userland/Libraries/LibWeb/HTML/Window.h b/Userland/Libraries/LibWeb/HTML/Window.h index 7673e0a5c5..3e89d3ae28 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.h +++ b/Userland/Libraries/LibWeb/HTML/Window.h @@ -33,6 +33,17 @@ class IdleCallback; // https://html.spec.whatwg.org/#timerhandler using TimerHandler = Variant, DeprecatedString>; +// https://w3c.github.io/csswg-drafts/cssom-view/#dictdef-scrolloptions +struct ScrollOptions { + Bindings::ScrollBehavior behavior { Bindings::ScrollBehavior::Auto }; +}; + +// https://w3c.github.io/csswg-drafts/cssom-view/#dictdef-scrolltooptions +struct ScrollToOptions : public ScrollOptions { + Optional left; + Optional top; +}; + class Window final : public DOM::EventTarget , public HTML::GlobalEventHandlers @@ -164,6 +175,8 @@ public: double scroll_x() const; double scroll_y() const; + void scroll(ScrollToOptions const&); + void scroll(double x, double y); WebIDL::ExceptionOr> performance(); @@ -233,7 +246,6 @@ private: JS_DECLARE_NATIVE_FUNCTION(device_pixel_ratio_getter); - JS_DECLARE_NATIVE_FUNCTION(scroll); JS_DECLARE_NATIVE_FUNCTION(scroll_by); JS_DECLARE_NATIVE_FUNCTION(screen_x_getter); diff --git a/Userland/Libraries/LibWeb/HTML/Window.idl b/Userland/Libraries/LibWeb/HTML/Window.idl index 68766e1cb8..b7ba328233 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.idl +++ b/Userland/Libraries/LibWeb/HTML/Window.idl @@ -57,6 +57,10 @@ interface Window : EventTarget { [Replaceable, ImplementedAs=scroll_x] readonly attribute double pageXOffset; [Replaceable] readonly attribute double scrollY; [Replaceable, ImplementedAs=scroll_y] readonly attribute double pageYOffset; + undefined scroll(optional ScrollToOptions options = {}); + undefined scroll(unrestricted double x, unrestricted double y); + [ImplementedAs=scroll] undefined scrollTo(optional ScrollToOptions options = {}); + [ImplementedAs=scroll] undefined scrollTo(unrestricted double x, unrestricted double y); // FIXME: Everything from here on should be shared through WindowOrWorkerGlobalScope // https://w3c.github.io/hr-time/#the-performance-attribute @@ -68,3 +72,13 @@ interface Window : EventTarget { Window includes GlobalEventHandlers; Window includes WindowEventHandlers; Window includes WindowOrWorkerGlobalScope; + +enum ScrollBehavior { "auto", "instant", "smooth" }; + +dictionary ScrollOptions { + ScrollBehavior behavior = "auto"; +}; +dictionary ScrollToOptions : ScrollOptions { + unrestricted double left; + unrestricted double top; +};