From 46e00a8f5eeca4c7ed7d67453d9394247f59b8f4 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Tue, 28 May 2024 09:12:18 -0600 Subject: [PATCH] LibWeb: Parse TokenizedFeatures from window.open --- Userland/Libraries/LibWeb/HTML/Navigable.cpp | 10 +- Userland/Libraries/LibWeb/HTML/Navigable.h | 2 +- .../Libraries/LibWeb/HTML/WebViewHints.cpp | 121 ++++++++++++++++++ Userland/Libraries/LibWeb/HTML/WebViewHints.h | 12 +- Userland/Libraries/LibWeb/HTML/Window.cpp | 6 +- 5 files changed, 136 insertions(+), 15 deletions(-) diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.cpp b/Userland/Libraries/LibWeb/HTML/Navigable.cpp index be8097a5946..827d706b90e 100644 --- a/Userland/Libraries/LibWeb/HTML/Navigable.cpp +++ b/Userland/Libraries/LibWeb/HTML/Navigable.cpp @@ -335,7 +335,7 @@ void Navigable::set_ongoing_navigation(Variant ongoing } // https://html.spec.whatwg.org/multipage/document-sequences.html#the-rules-for-choosing-a-navigable -Navigable::ChosenNavigable Navigable::choose_a_navigable(StringView name, TokenizedFeature::NoOpener no_opener, ActivateTab activate_tab) +Navigable::ChosenNavigable Navigable::choose_a_navigable(StringView name, TokenizedFeature::NoOpener no_opener, ActivateTab activate_tab, Optional window_features) { // NOTE: Implementation for step 7 here. JS::GCPtr same_name_navigable = nullptr; @@ -442,12 +442,8 @@ Navigable::ChosenNavigable Navigable::choose_a_navigable(StringView name, Tokeni if (!Infra::is_ascii_case_insensitive_match(name, "_blank"sv)) target_name = MUST(String::from_utf8(name)); - auto create_new_traversable_closure = [this, window_type, no_opener, target_name, activate_tab](JS::GCPtr opener) -> JS::NonnullGCPtr { - // FIXME: The popup state for window.open is calculated after this call (somehow?) - // Probably want to deviate from the spec and pass the popup state in here - auto hints = WebViewHints { - .popup = window_type != WindowType::ExistingOrNone, - }; + auto create_new_traversable_closure = [this, no_opener, target_name, activate_tab, window_features](JS::GCPtr opener) -> JS::NonnullGCPtr { + auto hints = WebViewHints::from_tokenised_features(window_features.value_or({}), traversable_navigable()->page()); auto [page, window_handle] = traversable_navigable()->page().client().page_did_request_new_web_view(activate_tab, hints, no_opener); auto traversable = TraversableNavigable::create_a_new_top_level_traversable(*page, opener, target_name).release_value_but_fixme_should_propagate_errors(); page->set_top_level_traversable(traversable); diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.h b/Userland/Libraries/LibWeb/HTML/Navigable.h index 44a3b5d1ede..c5a5488290c 100644 --- a/Userland/Libraries/LibWeb/HTML/Navigable.h +++ b/Userland/Libraries/LibWeb/HTML/Navigable.h @@ -107,7 +107,7 @@ public: WindowType window_type; }; - ChosenNavigable choose_a_navigable(StringView name, TokenizedFeature::NoOpener no_opener, ActivateTab = ActivateTab::Yes); + ChosenNavigable choose_a_navigable(StringView name, TokenizedFeature::NoOpener no_opener, ActivateTab = ActivateTab::Yes, Optional window_features = {}); static JS::GCPtr navigable_with_active_document(JS::NonnullGCPtr); diff --git a/Userland/Libraries/LibWeb/HTML/WebViewHints.cpp b/Userland/Libraries/LibWeb/HTML/WebViewHints.cpp index 9d699bda0c1..96387c9dd49 100644 --- a/Userland/Libraries/LibWeb/HTML/WebViewHints.cpp +++ b/Userland/Libraries/LibWeb/HTML/WebViewHints.cpp @@ -6,7 +6,128 @@ #include #include +#include #include +#include +#include + +namespace Web::HTML { + +static void set_up_browsing_context_features(WebViewHints& target, TokenizedFeature::Map const& tokenized_features, Page const& page); + +WebViewHints WebViewHints::from_tokenised_features(TokenizedFeature::Map const& tokenized_features, Page const& page) +{ + WebViewHints hints; + hints.popup = check_if_a_popup_window_is_requested(tokenized_features) == TokenizedFeature::Popup::Yes; + set_up_browsing_context_features(hints, tokenized_features, page); + return hints; +} + +// https://drafts.csswg.org/cssom-view/#set-up-browsing-context-features +static void set_up_browsing_context_features(WebViewHints& target, TokenizedFeature::Map const& tokenized_features, Page const& page) +{ + // 1. Let x be null. + Optional x; + + // 2. Let y be null. + Optional y; + + // 3. Let width be null. + Optional width; + + // 4. Let height be null. + Optional height; + + auto screen_rect = page.web_exposed_screen_area(); + + // 5. If tokenizedFeatures["left"] exists: + if (auto left = tokenized_features.get("left"sv); left.has_value()) { + // 1. Set x to the result of invoking the rules for parsing integers on tokenizedFeatures["left"]. + // 2. If x is an error, set x to 0. + x = parse_integer(*left).value_or(0); + + // 3. Optionally, clamp x in a user-agent-defined manner so that the window does not move outside the Web-exposed available screen area. + x = min(*x, screen_rect.x()); + + // 4. Optionally, move target’s window such that the window’s left edge is at the horizontal coordinate x relative + // to the left edge of the Web-exposed screen area, measured in CSS pixels of target. The positive axis is rightward. + // Note: Handled in the UI process when creating the traversable navigable. + } + + // 6. If tokenizedFeatures["top"] exists: + if (auto top = tokenized_features.get("top"sv); top.has_value()) { + // 1. Set y to the result of invoking the rules for parsing integers on tokenizedFeatures["top"]. + // 2. If y is an error, set y to 0. + y = parse_integer(*top).value_or(0); + + // 3. Optionally, clamp y in a user-agent-defined manner so that the window does not move outside the Web-exposed available screen area. + y = min(*y, screen_rect.y()); + + // 4. Optionally, move target’s window such that the window’s top edge is at the vertical coordinate y relative + // to the top edge of the Web-exposed screen area, measured in CSS pixels of target. The positive axis is downward. + // Note: Handled in the UI process when creating the traversable navigable. + } + + // 7. If tokenizedFeatures["width"] exists: + if (auto width_token = tokenized_features.get("width"sv); width_token.has_value()) { + // 1. Set width to the result of invoking the rules for parsing integers on tokenizedFeatures["width"]. + // 2. If width is an error, set width to 0. + width = parse_integer(*width_token).value_or(0); + + // 3. If width is not 0: + if (width != 0) { + // 1. Optionally, clamp width in a user-agent-defined manner so that the window does not get too small or bigger than the Web-exposed available screen area. + width = clamp(*width, 100, screen_rect.width()); + + // 2. Optionally, size target’s window by moving its right edge such that the distance between the left and right edges of the viewport are width CSS pixels of target. + // 3. Optionally, move target’s window in a user-agent-defined manner so that it does not grow outside the Web-exposed available screen area. + // Note: Handled in the UI process when creating the traversable navigable. + } + } + + // 8. If tokenizedFeatures["height"] exists: + if (auto height_token = tokenized_features.get("height"sv); height_token.has_value()) { + // 1. Set height to the result of invoking the rules for parsing integers on tokenizedFeatures["height"]. + // 2. If height is an error, set height to 0. + height = parse_integer(*height_token).value_or(0); + + // 3. If height is not 0: + if (height != 0) { + // 1. Optionally, clamp height in a user-agent-defined manner so that the window does not get too small or bigger than the Web-exposed available screen area. + height = clamp(*height, 100, screen_rect.height()); + + // 2. Optionally, size target’s window by moving its bottom edge such that the distance between the top and bottom edges of the viewport are height CSS pixels of target. + // 3. Optionally, move target’s window in a user-agent-defined manner so that it does not grow outside the Web-exposed available screen area. + // Note: Handled in the UI process when creating the traversable navigable. + } + } + + auto scale = page.client().device_pixels_per_css_pixel(); + + if (x.has_value()) { + // Make sure we don't fly off the screen to the right + if (x.value() + width.value_or(0) > screen_rect.width()) + x = screen_rect.width() - width.value_or(0); + target.screen_x = x.value() / scale; + } + + if (y.has_value()) { + // Make sure we don't fly off the screen to the bottom + if (y.value() + height.value_or(0) > screen_rect.height()) + y = screen_rect.height() - height.value_or(0); + target.screen_y = y.value() / scale; + } + + if (width.has_value()) { + target.width = width.value() / scale; + } + + if (height.has_value()) { + target.height = height.value() / scale; + } +} + +} namespace IPC { diff --git a/Userland/Libraries/LibWeb/HTML/WebViewHints.h b/Userland/Libraries/LibWeb/HTML/WebViewHints.h index e9aa702c3ec..d8daea24d36 100644 --- a/Userland/Libraries/LibWeb/HTML/WebViewHints.h +++ b/Userland/Libraries/LibWeb/HTML/WebViewHints.h @@ -9,16 +9,20 @@ #include #include #include +#include +#include #include namespace Web::HTML { struct WebViewHints { bool popup = false; - Optional width = {}; - Optional height = {}; - Optional screen_x = {}; - Optional screen_y = {}; + Optional width; + Optional height; + Optional screen_x; + Optional screen_y; + + static WebViewHints from_tokenised_features(TokenizedFeature::Map const&, Page const&); }; } diff --git a/Userland/Libraries/LibWeb/HTML/Window.cpp b/Userland/Libraries/LibWeb/HTML/Window.cpp index d63a846cc66..bb769ff8174 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.cpp +++ b/Userland/Libraries/LibWeb/HTML/Window.cpp @@ -189,7 +189,7 @@ WebIDL::ExceptionOr> Window::open_impl(StringView url, St // 10. Let targetNavigable and windowType be the result of applying the rules for choosing a navigable given target, sourceDocument's node navigable, and noopener. VERIFY(source_document.navigable()); - auto [target_navigable, window_type] = source_document.navigable()->choose_a_navigable(target, no_opener); + auto [target_navigable, window_type] = source_document.navigable()->choose_a_navigable(target, no_opener, ActivateTab::Yes, tokenized_features); // 11. If targetNavigable is null, then return null. if (target_navigable == nullptr) @@ -200,8 +200,8 @@ WebIDL::ExceptionOr> Window::open_impl(StringView url, St // 1. Set the target browsing context's is popup to the result of checking if a popup window is requested, given tokenizedFeatures. target_navigable->set_is_popup(check_if_a_popup_window_is_requested(tokenized_features)); - // FIXME: 2. Set up browsing context features for target browsing context given tokenizedFeatures. [CSSOMVIEW] - // NOTE: While this is not implemented yet, all of observable actions taken by this operation are optional (implementation-defined). + // 2. Set up browsing context features for target browsing context given tokenizedFeatures. [CSSOMVIEW] + // NOTE: This is implemented in choose_a_navigable when creating the top level traversable. // 3. Let urlRecord be the URL record about:blank. auto url_record = URL::URL("about:blank"sv);