LibWeb/HTML: Return Promises from Window scroll methods

Corresponds to part of:
c548a9a1d4
This commit is contained in:
Sam Atkins 2025-12-18 16:45:18 +00:00 committed by Alexander Kalenik
parent cae526286a
commit a610639119
Notes: github-actions[bot] 2025-12-23 13:25:39 +00:00
7 changed files with 95 additions and 56 deletions

View file

@ -2784,7 +2784,7 @@ RefPtr<Gfx::SkiaBackendContext> Navigable::skia_backend_context() const
}
// https://drafts.csswg.org/cssom-view/#viewport-perform-a-scroll
void Navigable::scroll_viewport_by_delta(CSSPixelPoint delta)
GC::Ref<WebIDL::Promise> Navigable::scroll_viewport_by_delta(CSSPixelPoint delta)
{
// 1. Let doc be the viewports associated Document.
auto doc = active_document();
@ -2828,9 +2828,13 @@ void Navigable::scroll_viewport_by_delta(CSSPixelPoint delta)
// 13. Let element be docs root element if there is one, null otherwise.
// 14. Perform a scroll of the viewports scrolling box to its current scroll position + (layout dx, layout dy) with element as the associated element, and behavior as the scroll behavior.
// 14. Perform a scroll of the viewports scrolling box to its current scroll position + (layout dx, layout dy)
// with element as the associated element, and behavior as the scroll behavior. Let scrollPromise1 be the
// Promise returned from this step.
// FIXME: Get a Promise from this.
// AD-HOC: Skip scrolling unscrollable boxes.
if (!doc->paintable_box()->could_be_scrolled_by_wheel_event())
return;
return WebIDL::create_resolved_promise(doc->realm(), JS::js_undefined());
auto scrolling_area = doc->paintable_box()->scrollable_overflow_rect()->to_type<float>();
auto new_viewport_scroll_offset = m_viewport_scroll_offset.to_type<double>() + Gfx::Point(layout_dx, layout_dy);
// NOTE: Clamp to the scrolling area.
@ -2838,9 +2842,21 @@ void Navigable::scroll_viewport_by_delta(CSSPixelPoint delta)
new_viewport_scroll_offset.set_y(max(0.0, min(new_viewport_scroll_offset.y(), scrolling_area.height() - viewport_size().height().to_double())));
perform_scroll_of_viewport(new_viewport_scroll_offset.to_type<CSSPixels>());
// 15. Perform a scroll of vvs scrolling box to its current scroll position + (visual dx, visual dy) with element as the associated element, and behavior as the scroll behavior.
// 15. Perform a scroll of vvs scrolling box to its current scroll position + (visual dx, visual dy) with element
// as the associated element, and behavior as the scroll behavior. Let scrollPromise2 be the Promise returned
// from this step.
// FIXME: Get a Promise from this.
vv->scroll_by({ visual_dx, visual_dy });
doc->set_needs_display(InvalidateDisplayList::No);
// 16. Let scrollPromise be a new Promise.
auto scroll_promise = WebIDL::create_promise(doc->realm());
// 17. Return scrollPromise, and run the remaining steps in parallel.
// 18. Resolve scrollPromise when both scrollPromise1 and scrollPromise2 have settled.
// FIXME: Actually wait for scroll to occur. For now, all our scrolls are instant.
WebIDL::resolve_promise(doc->realm(), scroll_promise);
return scroll_promise;
}
void Navigable::reset_zoom()

View file

@ -219,7 +219,7 @@ public:
template<typename T>
bool fast_is() const = delete;
void scroll_viewport_by_delta(CSSPixelPoint delta);
GC::Ref<WebIDL::Promise> scroll_viewport_by_delta(CSSPixelPoint delta);
void reset_zoom();
protected:

View file

@ -1472,31 +1472,38 @@ double Window::scroll_y() const
return 0;
}
// https://w3c.github.io/csswg-drafts/cssom-view/#dom-window-scroll
void Window::scroll(ScrollToOptions const& options)
// https://drafts.csswg.org/cssom-view/#dom-window-scroll
GC::Ref<WebIDL::Promise> Window::scroll(ScrollToOptions const& options)
{
// 4. If there is no viewport, abort these steps.
// 4. If there is no viewport, return a resolved Promise and abort the remaining steps.
// AD-HOC: Done here as step 1 requires the viewport.
auto navigable = associated_document().navigable();
if (!navigable)
return;
return WebIDL::create_resolved_promise(realm(), JS::js_undefined());
// 1. If invoked with one argument, follow these substeps:
// NB: This Window::scroll() overload always has one argument.
// 1. Let options be the argument.
// 1. Let options be the argument.
auto viewport_rect = navigable->viewport_rect().to_type<float>();
// 2. Let x be the value of the left dictionary member of options, if present, or the viewports current scroll
// 2. Let x be the value of the left dictionary member of options, if present, or the viewports 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 viewports current scroll
// position on the y axis otherwise.
// 3. Let y be the value of the top dictionary member of options, if present, or the viewports current scroll
// position on the y axis otherwise.
auto y = options.top.value_or(viewport_rect.y());
// 2. If invoked with two arguments, follow these substeps:
// NB: Implemented by other Window::scroll() overload.
// 3. Normalize non-finite values for x and y.
x = HTML::normalize_non_finite_values(x);
y = HTML::normalize_non_finite_values(y);
// AD-HOC: Step 4 is done at the start.
// 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();
@ -1506,7 +1513,7 @@ void Window::scroll(ScrollToOptions const& options)
auto const document = navigable->active_document();
VERIFY(document);
// Make sure layout is up-to-date before looking at scrollable overflow metrics.
// NB: Make sure layout is up-to-date before looking at scrollable overflow metrics.
document->update_layout(DOM::UpdateLayoutReason::WindowScroll);
VERIFY(document->paintable_box());
@ -1531,38 +1538,49 @@ void Window::scroll(ScrollToOptions const& options)
// with the top of the viewport.
auto position = Gfx::FloatPoint { x, y };
// FIXME: We need an execution context to resolve promises, are we missing one in a caller?
TemporaryExecutionContext temporary_execution_context { realm() };
// 10. If position is the same as the viewports current scroll position, and the viewport does not have an ongoing
// smooth scroll, abort these steps.
// smooth scroll, return a resolved Promise and abort the remaining steps.
if (position == viewport_rect.location())
return;
return WebIDL::create_resolved_promise(realm(), JS::js_undefined());
// 11. Let document be the viewports associated Document.
// NOTE: document is already defined above.
// NB: document is already defined above.
// 12. Perform a scroll of the viewport to position, documents 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.
navigable->perform_scroll_of_viewport({ x, y });
// Let scrollPromise be the Promise returned from this step.
auto scroll_promise = navigable->scroll_viewport_by_delta({ x, y });
// 13. Return scrollPromise.
return scroll_promise;
}
// https://w3c.github.io/csswg-drafts/cssom-view/#dom-window-scroll
void Window::scroll(double x, double y)
// https://drafts.csswg.org/cssom-view/#dom-window-scroll
GC::Ref<WebIDL::Promise> Window::scroll(double x, double y)
{
// NB: This just implements step 2, and then forwards to the other Window::scroll() overload.
// 2. If invoked with two arguments, follow these substeps:
// 1. Let options be null converted to a ScrollToOptions dictionary. [WEBIDL]
// 1. Let options be null converted to a ScrollToOptions dictionary. [WEBIDL]
auto options = ScrollToOptions {};
// 2. Let x and y be the arguments, respectively.
// 2. Let x and y be the arguments, respectively.
options.left = x;
options.top = y;
scroll(options);
return scroll(options);
}
// https://w3c.github.io/csswg-drafts/cssom-view/#dom-window-scrollby
void Window::scroll_by(ScrollToOptions options)
// https://drafts.csswg.org/cssom-view/#dom-window-scrollby
GC::Ref<WebIDL::Promise> Window::scroll_by(ScrollToOptions options)
{
// 1. If invoked with two arguments, follow these substeps:
// NB: Implemented by the other overload, which then calls this.
// 2. Normalize non-finite values for the left and top dictionary members of options.
auto left = HTML::normalize_non_finite_values(options.left);
auto top = HTML::normalize_non_finite_values(options.top);
@ -1573,27 +1591,28 @@ void Window::scroll_by(ScrollToOptions options)
// 4. Add the value of scrollY to the top dictionary member.
options.top = top + scroll_y();
// 5. Act as if the scroll() method was invoked with options as the only argument.
scroll(options);
// 5. Return the Promise returned from scroll() after the method is invoked with options as the only argument.
return scroll(options);
}
// https://w3c.github.io/csswg-drafts/cssom-view/#dom-window-scrollby
void Window::scroll_by(double x, double y)
// https://drafts.csswg.org/cssom-view/#dom-window-scrollby
GC::Ref<WebIDL::Promise> Window::scroll_by(double x, double y)
{
// 1. If invoked with two arguments, follow these substeps:
// 1. Let options be null converted to a ScrollToOptions dictionary. [WEBIDL]
// 1. Let options be null converted to a ScrollToOptions dictionary. [WEBIDL]
auto options = ScrollToOptions {};
// 2. Let x and y be the arguments, respectively.
// 2. Let x and y be the arguments, respectively.
// 3. Let the left dictionary member of options have the value x.
// 3. Let the left dictionary member of options have the value x.
options.left = x;
// 4. Let the top dictionary member of options have the value y.
// 4. Let the top dictionary member of options have the value y.
options.top = y;
scroll_by(options);
// NB: Complete the algorithm using the other overload.
return scroll_by(options);
}
// https://w3c.github.io/csswg-drafts/cssom-view/#dom-window-screenx

View file

@ -220,10 +220,10 @@ public:
double scroll_x() const;
double scroll_y() const;
void scroll(ScrollToOptions const&);
void scroll(double x, double y);
void scroll_by(ScrollToOptions);
void scroll_by(double x, double y);
GC::Ref<WebIDL::Promise> scroll(ScrollToOptions const&);
GC::Ref<WebIDL::Promise> scroll(double x, double y);
GC::Ref<WebIDL::Promise> scroll_by(ScrollToOptions);
GC::Ref<WebIDL::Promise> scroll_by(double x, double y);
i32 screen_x() const;
i32 screen_y() const;

View file

@ -94,12 +94,12 @@ 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);
undefined scrollBy(optional ScrollToOptions options = {});
undefined scrollBy(unrestricted double x, unrestricted double y);
Promise<undefined> scroll(optional ScrollToOptions options = {});
Promise<undefined> scroll(unrestricted double x, unrestricted double y);
[ImplementedAs=scroll] Promise<undefined> scrollTo(optional ScrollToOptions options = {});
[ImplementedAs=scroll] Promise<undefined> scrollTo(unrestricted double x, unrestricted double y);
Promise<undefined> scrollBy(optional ScrollToOptions options = {});
Promise<undefined> scrollBy(unrestricted double x, unrestricted double y);
// client
[Replaceable] readonly attribute long screenX;

View file

@ -1430,10 +1430,14 @@ EventResult EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u
case UIEvents::KeyCode::Key_Down:
if (modifiers && modifiers != UIEvents::KeyModifier::Mod_PlatformCtrl)
break;
if (modifiers)
key == UIEvents::KeyCode::Key_Up ? document->scroll_to_the_beginning_of_the_document() : document->window()->scroll_by(0, INT64_MAX);
else
if (modifiers) {
if (key == UIEvents::KeyCode::Key_Up)
document->scroll_to_the_beginning_of_the_document();
else
document->window()->scroll_by(0, INT64_MAX);
} else {
document->window()->scroll_by(0, key == UIEvents::KeyCode::Key_Up ? -arrow_key_scroll_distance : arrow_key_scroll_distance);
}
return EventResult::Handled;
case UIEvents::KeyCode::Key_Left:
case UIEvents::KeyCode::Key_Right:

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 415 tests
325 Pass
90 Fail
331 Pass
84 Fail
Pass idl_test setup
Pass idl_test validation
Pass Partial interface Window: original interface defined
@ -272,12 +272,12 @@ Pass Window interface: attribute scrollX
Pass Window interface: attribute pageXOffset
Pass Window interface: attribute scrollY
Pass Window interface: attribute pageYOffset
Fail Window interface: operation scroll(optional ScrollToOptions)
Fail Window interface: operation scroll(unrestricted double, unrestricted double)
Fail Window interface: operation scrollTo(optional ScrollToOptions)
Fail Window interface: operation scrollTo(unrestricted double, unrestricted double)
Fail Window interface: operation scrollBy(optional ScrollToOptions)
Fail Window interface: operation scrollBy(unrestricted double, unrestricted double)
Pass Window interface: operation scroll(optional ScrollToOptions)
Pass Window interface: operation scroll(unrestricted double, unrestricted double)
Pass Window interface: operation scrollTo(optional ScrollToOptions)
Pass Window interface: operation scrollTo(unrestricted double, unrestricted double)
Pass Window interface: operation scrollBy(optional ScrollToOptions)
Pass Window interface: operation scrollBy(unrestricted double, unrestricted double)
Pass Window interface: attribute screenX
Pass Window interface: attribute screenLeft
Pass Window interface: attribute screenY