diff --git a/Tests/LibWeb/Text/expected/HTML/Window-close.txt b/Tests/LibWeb/Text/expected/HTML/Window-close.txt
new file mode 100644
index 000000000000..cc65910f3ac5
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/HTML/Window-close.txt
@@ -0,0 +1,3 @@
+window.closed = false
+window.closed = true
+window.closed = true
diff --git a/Tests/LibWeb/Text/input/HTML/Window-close.html b/Tests/LibWeb/Text/input/HTML/Window-close.html
new file mode 100644
index 000000000000..d391adcd6d48
--- /dev/null
+++ b/Tests/LibWeb/Text/input/HTML/Window-close.html
@@ -0,0 +1,15 @@
+
+
diff --git a/Userland/Libraries/LibWeb/HTML/BrowsingContext.h b/Userland/Libraries/LibWeb/HTML/BrowsingContext.h
index 1e75d9d3552d..6900a4201ace 100644
--- a/Userland/Libraries/LibWeb/HTML/BrowsingContext.h
+++ b/Userland/Libraries/LibWeb/HTML/BrowsingContext.h
@@ -96,6 +96,7 @@ class BrowsingContext final : public JS::Cell {
}
bool is_top_level() const;
+ bool is_auxiliary() const { return m_is_auxiliary; }
DOM::Document const* active_document() const;
DOM::Document* active_document();
diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.cpp b/Userland/Libraries/LibWeb/HTML/Navigable.cpp
index a69bfc4212ba..9856661610f6 100644
--- a/Userland/Libraries/LibWeb/HTML/Navigable.cpp
+++ b/Userland/Libraries/LibWeb/HTML/Navigable.cpp
@@ -128,6 +128,21 @@ void Navigable::visit_edges(Cell::Visitor& visitor)
m_event_handler.visit_edges(visitor);
}
+// https://html.spec.whatwg.org/multipage/nav-history-apis.html#script-closable
+bool Navigable::is_script_closable()
+{
+ // A navigable is script-closable if its active browsing context is an auxiliary browsing context that was created
+ // by a script (as opposed to by an action of the user), or if it is a top-level traversable whose session history
+ // entries's size is 1.
+ if (auto browsing_context = active_browsing_context(); browsing_context && browsing_context->is_auxiliary())
+ return true;
+
+ if (is_top_level_traversable())
+ return get_session_history_entries().size() == 1;
+
+ return false;
+}
+
void Navigable::set_delaying_load_events(bool value)
{
if (value) {
diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.h b/Userland/Libraries/LibWeb/HTML/Navigable.h
index 4b2122f65b43..8283dff578df 100644
--- a/Userland/Libraries/LibWeb/HTML/Navigable.h
+++ b/Userland/Libraries/LibWeb/HTML/Navigable.h
@@ -69,6 +69,7 @@ class Navigable : public JS::Cell {
bool is_closing() const { return m_closing; }
void set_closing(bool value) { m_closing = value; }
+ bool is_script_closable();
void set_delaying_load_events(bool value);
bool is_delaying_load_events() const { return m_delaying_the_load_event.has_value(); }
diff --git a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp
index 3570b5c193c5..75c54595ad2c 100644
--- a/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp
+++ b/Userland/Libraries/LibWeb/HTML/TraversableNavigable.cpp
@@ -1178,8 +1178,9 @@ void TraversableNavigable::close_top_level_traversable()
VERIFY(is_top_level_traversable());
// 1. If traversable's is closing is true, then return.
- if (is_closing())
- return;
+ // FIXME: Spec-issue: The only place in the spec that sets the `is closing` flag to true is `window.close`, and it
+ // does so immediately before invoking this method. So it does not make sense to return early here.
+ // https://github.com/whatwg/html/issues/10678
// 2. Let toUnload be traversable's active document's inclusive descendant navigables.
auto to_unload = active_document()->inclusive_descendant_navigables();
diff --git a/Userland/Libraries/LibWeb/HTML/Window.cpp b/Userland/Libraries/LibWeb/HTML/Window.cpp
index ea154e1b8de5..1e5a32727d8d 100644
--- a/Userland/Libraries/LibWeb/HTML/Window.cpp
+++ b/Userland/Libraries/LibWeb/HTML/Window.cpp
@@ -787,15 +787,59 @@ String Window::status() const
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-window-close
void Window::close()
{
- // FIXME: Implement this properly
- dbgln("(STUBBED) Window::close()");
+ // 1. Let thisTraversable be this's navigable.
+ auto traversable = navigable();
+
+ // 2. If thisTraversable is not a top-level traversable, then return.
+ if (!traversable || !traversable->is_top_level_traversable())
+ return;
+
+ // 3. If thisTraversable's is closing is true, then return.
+ if (traversable->is_closing())
+ return;
+
+ // 4. Let browsingContext be thisTraversable's active browsing context.
+ auto browsing_context = traversable->active_browsing_context();
+
+ // 5. Let sourceSnapshotParams be the result of snapshotting source snapshot params given thisTraversable's active document.
+ auto source_snapshot_params = traversable->active_document()->snapshot_source_snapshot_params();
+
+ auto& incumbent_global_object = verify_cast(HTML::incumbent_global_object());
+
+ // 6. If all the following are true:
+ if (
+ // thisTraversable is script-closable;
+ traversable->is_script_closable()
+
+ // the incumbent global object's browsing context is familiar with browsingContext; and
+ && incumbent_global_object.browsing_context()->is_familiar_with(*browsing_context)
+
+ // the incumbent global object's navigable is allowed by sandboxing to navigate thisTraversable, given sourceSnapshotParams,
+ && incumbent_global_object.navigable()->allowed_by_sandboxing_to_navigate(*traversable, source_snapshot_params))
+ // then:
+ {
+ // 1. Set thisTraversable's is closing to true.
+ traversable->set_closing(true);
+
+ // 2. Queue a task on the DOM manipulation task source to close thisTraversable.
+ HTML::queue_global_task(HTML::Task::Source::DOMManipulation, incumbent_global_object, JS::create_heap_function(heap(), [traversable] {
+ verify_cast(*traversable).close_top_level_traversable();
+ }));
+ }
}
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-window-closed
bool Window::closed() const
{
- // FIXME: Implement this properly
- dbgln("(STUBBED) Window::closed");
+ // The closed getter steps are to return true if this's browsing context is null or its is closing is true;
+ // otherwise false.
+ if (!browsing_context())
+ return true;
+
+ // FIXME: The spec seems a bit out of date. The `is closing` flag is on the navigable, not the browsing context.
+ if (auto navigable = this->navigable(); !navigable || navigable->is_closing())
+ return true;
+
return false;
}