diff --git a/Userland/Libraries/LibWeb/HTML/Window.cpp b/Userland/Libraries/LibWeb/HTML/Window.cpp
index 8a911c0684..4890025fa4 100644
--- a/Userland/Libraries/LibWeb/HTML/Window.cpp
+++ b/Userland/Libraries/LibWeb/HTML/Window.cpp
@@ -31,6 +31,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -297,6 +298,131 @@ static bool check_if_a_popup_window_is_requested(OrderedHashMap
return false;
}
+// FIXME: This is based on the old 'browsing context' concept, which was replaced with 'navigable'
+// https://html.spec.whatwg.org/multipage/window-object.html#window-open-steps
+WebIDL::ExceptionOr> Window::open_impl(StringView url, StringView target, StringView features)
+{
+ auto& vm = this->vm();
+
+ // 1. If the event loop's termination nesting level is nonzero, return null.
+ if (HTML::main_thread_event_loop().termination_nesting_level() != 0)
+ return nullptr;
+
+ // 2. Let source browsing context be the entry global object's browsing context.
+ auto* source_browsing_context = verify_cast(entry_global_object()).browsing_context();
+
+ // 3. If target is the empty string, then set target to "_blank".
+ if (target.is_empty())
+ target = "_blank"sv;
+
+ // 4. Let tokenizedFeatures be the result of tokenizing features.
+ auto tokenized_features = tokenize_open_features(features);
+
+ // 5. Let noopener and noreferrer be false.
+ auto no_opener = false;
+ auto no_referrer = false;
+
+ // 6. If tokenizedFeatures["noopener"] exists, then:
+ if (auto no_opener_feature = tokenized_features.get("noopener"sv); no_opener_feature.has_value()) {
+ // 1. Set noopener to the result of parsing tokenizedFeatures["noopener"] as a boolean feature.
+ no_opener = parse_boolean_feature(*no_opener_feature);
+
+ // 2. Remove tokenizedFeatures["noopener"].
+ tokenized_features.remove("noopener"sv);
+ }
+
+ // 7. If tokenizedFeatures["noreferrer"] exists, then:
+ if (auto no_referrer_feature = tokenized_features.get("noreferrer"sv); no_referrer_feature.has_value()) {
+ // 1. Set noreferrer to the result of parsing tokenizedFeatures["noreferrer"] as a boolean feature.
+ no_referrer = parse_boolean_feature(*no_referrer_feature);
+
+ // 2. Remove tokenizedFeatures["noreferrer"].
+ tokenized_features.remove("noreferrer"sv);
+ }
+
+ // 8. If noreferrer is true, then set noopener to true.
+ if (no_referrer)
+ no_opener = true;
+
+ // 9. Let target browsing context and windowType be the result of applying the rules for choosing a browsing context given target, source browsing context, and noopener.
+ auto [target_browsing_context, window_type] = source_browsing_context->choose_a_browsing_context(target, no_opener);
+
+ // 10. If target browsing context is null, then return null.
+ if (target_browsing_context == nullptr)
+ return nullptr;
+
+ // 11. If windowType is either "new and unrestricted" or "new with no opener", then:
+ if (window_type == BrowsingContext::WindowType::NewAndUnrestricted || window_type == BrowsingContext::WindowType::NewWithNoOpener) {
+ // 1. Set the target browsing context's is popup to the result of checking if a popup window is requested, given tokenizedFeatures.
+ target_browsing_context->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).
+
+ // 3. Let urlRecord be the URL record about:blank.
+ auto url_record = AK::URL("about:blank"sv);
+
+ // 4. If url is not the empty string, then parse url relative to the entry settings object, and set urlRecord to the resulting URL record, if any. If the parse a URL algorithm failed, then throw a "SyntaxError" DOMException.
+ if (!url.is_empty()) {
+ url_record = entry_settings_object().parse_url(url);
+ if (!url_record.is_valid())
+ return WebIDL::SyntaxError::create(realm(), "URL is not valid");
+ }
+
+ // FIXME: 5. If urlRecord matches about:blank, then perform the URL and history update steps given target browsing context's active document and urlRecord.
+
+ // 6. Otherwise:
+ else {
+ // 1. Let request be a new request whose URL is urlRecord.
+ auto request = Fetch::Infrastructure::Request::create(vm);
+ request->set_url(url_record);
+
+ // 2. If noreferrer is true, then set request's referrer to "no-referrer".
+ if (no_referrer)
+ request->set_referrer(Fetch::Infrastructure::Request::Referrer::NoReferrer);
+
+ // 3. Navigate target browsing context to request, with exceptionsEnabled set to true and the source browsing context set to source browsing context.
+ TRY(target_browsing_context->navigate(request, *source_browsing_context, true));
+ }
+ }
+
+ // 12. Otherwise:
+ else {
+ // 1. If url is not the empty string, then:
+ if (!url.is_empty()) {
+ // 1. Let urlRecord be the URL record about:blank.
+ auto url_record = AK::URL("about:blank"sv);
+
+ // 2. Parse url relative to the entry settings object, and set urlRecord to the resulting URL record, if any. If the parse a URL algorithm failed, then throw a "SyntaxError" DOMException.
+ url_record = entry_settings_object().parse_url(url);
+ if (!url_record.is_valid())
+ return WebIDL::SyntaxError::create(realm(), "URL is not valid");
+
+ // 3. Let request be a new request whose URL is urlRecord.
+ auto request = Fetch::Infrastructure::Request::create(vm);
+ request->set_url(url_record);
+
+ // 4. If noreferrer is true, then set request's referrer to "noreferrer".
+ if (no_referrer)
+ request->set_referrer(Fetch::Infrastructure::Request::Referrer::NoReferrer);
+
+ // 5. Navigate target browsing context to request, with exceptionsEnabled set to true and the source browsing context set to source browsing context.
+ TRY(target_browsing_context->navigate(request, *source_browsing_context, true));
+ }
+
+ // 2. If noopener is false, then set target browsing context's opener browsing context to source browsing context.
+ if (!no_opener)
+ target_browsing_context->set_opener_browsing_context(source_browsing_context);
+ }
+
+ // 13. If noopener is true or windowType is "new with no opener", then return null.
+ if (no_opener || window_type == BrowsingContext::WindowType::NewWithNoOpener)
+ return nullptr;
+
+ // 14. Return target browsing context's WindowProxy object.
+ return target_browsing_context->window_proxy();
+}
+
void Window::alert_impl(String const& message)
{
if (auto* page = this->page())
@@ -950,6 +1076,7 @@ void Window::initialize_web_interfaces(Badge)
define_native_accessor(realm, "innerHeight", inner_height_getter, {}, JS::Attribute::Enumerable);
define_native_accessor(realm, "devicePixelRatio", device_pixel_ratio_getter, {}, JS::Attribute::Enumerable | JS::Attribute::Configurable);
u8 attr = JS::Attribute::Writable | JS::Attribute::Enumerable | JS::Attribute::Configurable;
+ define_native_function(realm, "open", open, 0, attr);
define_native_function(realm, "alert", alert, 0, attr);
define_native_function(realm, "confirm", confirm, 0, attr);
define_native_function(realm, "prompt", prompt, 0, attr);
@@ -1059,6 +1186,28 @@ static JS::ThrowCompletionOr impl_from(JS::VM& vm)
return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "Window");
}
+JS_DEFINE_NATIVE_FUNCTION(Window::open)
+{
+ auto* impl = TRY(impl_from(vm));
+
+ // optional USVString url = ""
+ String url = "";
+ if (!vm.argument(0).is_undefined())
+ url = TRY(vm.argument(0).to_string(vm));
+
+ // optional DOMString target = "_blank"
+ String target = "_blank";
+ if (!vm.argument(1).is_undefined())
+ target = TRY(vm.argument(1).to_string(vm));
+
+ // optional [LegacyNullToEmptyString] DOMString features = "")
+ String features = "";
+ if (!vm.argument(2).is_nullish())
+ features = TRY(vm.argument(2).to_string(vm));
+
+ return TRY(Bindings::throw_dom_exception_if_needed(vm, [&] { return impl->open_impl(url, target, features); }));
+}
+
JS_DEFINE_NATIVE_FUNCTION(Window::alert)
{
// https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#simple-dialogs
diff --git a/Userland/Libraries/LibWeb/HTML/Window.h b/Userland/Libraries/LibWeb/HTML/Window.h
index 9b5b405ff0..d9e7e6715e 100644
--- a/Userland/Libraries/LibWeb/HTML/Window.h
+++ b/Userland/Libraries/LibWeb/HTML/Window.h
@@ -61,6 +61,7 @@ public:
bool import_maps_allowed() const { return m_import_maps_allowed; }
void set_import_maps_allowed(bool import_maps_allowed) { m_import_maps_allowed = import_maps_allowed; }
+ WebIDL::ExceptionOr> open_impl(StringView url, StringView target, StringView features);
void alert_impl(String const&);
bool confirm_impl(String const&);
String prompt_impl(String const&, String const&);
@@ -245,6 +246,7 @@ private:
JS_DECLARE_NATIVE_FUNCTION(session_storage_getter);
JS_DECLARE_NATIVE_FUNCTION(origin_getter);
+ JS_DECLARE_NATIVE_FUNCTION(open);
JS_DECLARE_NATIVE_FUNCTION(alert);
JS_DECLARE_NATIVE_FUNCTION(confirm);
JS_DECLARE_NATIVE_FUNCTION(prompt);