diff --git a/Libraries/LibWeb/HTML/Navigable.cpp b/Libraries/LibWeb/HTML/Navigable.cpp
index a407425f14f..a4a9594e8ca 100644
--- a/Libraries/LibWeb/HTML/Navigable.cpp
+++ b/Libraries/LibWeb/HTML/Navigable.cpp
@@ -2234,4 +2234,18 @@ void Navigable::unregister_navigation_observer(Badge, Naviga
VERIFY(was_removed);
}
+// https://html.spec.whatwg.org/multipage/document-lifecycle.html#nav-stop
+void Navigable::stop_loading()
+{
+ // 1. Let document be navigable's active document.
+ auto document = active_document();
+
+ // 2. If document's unload counter is 0, and navigable's ongoing navigation is a navigation ID, then set the ongoing navigation for navigable to null.
+ if (document->unload_counter() == 0 && ongoing_navigation().has())
+ set_ongoing_navigation(Empty {});
+
+ // 3. Abort a document and its descendants given document.
+ document->abort_a_document_and_its_descendants();
+}
+
}
diff --git a/Libraries/LibWeb/HTML/Navigable.h b/Libraries/LibWeb/HTML/Navigable.h
index 4ee694c4a9a..3d81aa48b52 100644
--- a/Libraries/LibWeb/HTML/Navigable.h
+++ b/Libraries/LibWeb/HTML/Navigable.h
@@ -74,6 +74,8 @@ public:
void set_closing(bool value) { m_closing = value; }
bool is_script_closable();
+ void stop_loading();
+
void set_delaying_load_events(bool value);
bool is_delaying_load_events() const { return m_delaying_the_load_event.has_value(); }
diff --git a/Libraries/LibWeb/HTML/Window.cpp b/Libraries/LibWeb/HTML/Window.cpp
index db340cbe288..4168c3f4625 100644
--- a/Libraries/LibWeb/HTML/Window.cpp
+++ b/Libraries/LibWeb/HTML/Window.cpp
@@ -856,6 +856,18 @@ GC::Ref Window::history() const
return associated_document().history();
}
+// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-window-stop
+void Window::stop()
+{
+ // 1. If this's navigable is null, then return.
+ auto navigable = this->navigable();
+ if (!navigable)
+ return;
+
+ // 2. Stop loading this's navigable.
+ navigable->stop_loading();
+}
+
// https://html.spec.whatwg.org/multipage/interaction.html#dom-window-focus
void Window::focus()
{
diff --git a/Libraries/LibWeb/HTML/Window.h b/Libraries/LibWeb/HTML/Window.h
index 2ea6f22ce78..0e21066b602 100644
--- a/Libraries/LibWeb/HTML/Window.h
+++ b/Libraries/LibWeb/HTML/Window.h
@@ -154,6 +154,7 @@ public:
[[nodiscard]] GC::Ref location();
GC::Ref history() const;
GC::Ref navigation();
+ void stop();
void focus();
void blur();
diff --git a/Libraries/LibWeb/HTML/Window.idl b/Libraries/LibWeb/HTML/Window.idl
index d78fbc20a2e..26ba6f977fb 100644
--- a/Libraries/LibWeb/HTML/Window.idl
+++ b/Libraries/LibWeb/HTML/Window.idl
@@ -30,6 +30,7 @@ interface Window : EventTarget {
readonly attribute History history;
readonly attribute Navigation navigation;
readonly attribute CustomElementRegistry customElements;
+ undefined stop();
undefined focus();
undefined blur();
diff --git a/Tests/LibWeb/TestConfig.ini b/Tests/LibWeb/TestConfig.ini
index 610b69ec9ca..9f756a0a5f5 100644
--- a/Tests/LibWeb/TestConfig.ini
+++ b/Tests/LibWeb/TestConfig.ini
@@ -47,6 +47,8 @@ Text/input/wpt-import/html/syntax/parsing/support/no-doctype-name-line.html
Text/input/wpt-import/html/syntax/parsing/support/no-doctype-name-space.html
Text/input/wpt-import/html/semantics/embedded-content/the-iframe-element/support/sandbox_allow_script.html
Text/input/wpt-import/html/semantics/embedded-content/the-iframe-element/support/blank.htm
+Text/input/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-frame.html
+Text/input/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-async-frame.html
; Unknown, imported as skipped in #2148
Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.html
diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/aborted-parser.window.txt b/Tests/LibWeb/Text/expected/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/aborted-parser.window.txt
new file mode 100644
index 00000000000..fe57db893f3
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/aborted-parser.window.txt
@@ -0,0 +1,7 @@
+Harness status: OK
+
+Found 2 tests
+
+2 Pass
+Pass document.open() after parser is aborted
+Pass async document.open() after parser is aborted
\ No newline at end of file
diff --git a/Tests/LibWeb/Text/input/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/aborted-parser.window.html b/Tests/LibWeb/Text/input/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/aborted-parser.window.html
new file mode 100644
index 00000000000..96a547e1a1a
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/aborted-parser.window.html
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/Tests/LibWeb/Text/input/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/aborted-parser.window.js b/Tests/LibWeb/Text/input/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/aborted-parser.window.js
new file mode 100644
index 00000000000..ba7278ef18a
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/aborted-parser.window.js
@@ -0,0 +1,31 @@
+// document.open() bails out early if there is an active parser with non-zero
+// script nesting level or if a load was aborted while there was an active
+// parser. window.stop() aborts the current parser, so once it has been called
+// while a parser is active, document.open() will no longer do anything to that
+// document,
+
+window.handlers = {};
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.src = "resources/aborted-parser-frame.html";
+ window.handlers.afterOpen = t.step_func_done(() => {
+ const openCalled = frame.contentDocument.childNodes.length === 0;
+ assert_false(openCalled, "child document should not be empty");
+ assert_equals(frame.contentDocument.querySelector("p").textContent,
+ "Text", "Should still have our paragraph");
+ });
+}, "document.open() after parser is aborted");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.src = "resources/aborted-parser-async-frame.html";
+ window.handlers.afterOpenAsync = t.step_func_done(() => {
+ const openCalled = frame.contentDocument.childNodes.length === 0;
+ assert_false(openCalled, "child document should not be empty");
+ assert_equals(frame.contentDocument.querySelector("p").textContent,
+ "Text", "Should still have our paragraph");
+ });
+}, "async document.open() after parser is aborted");
diff --git a/Tests/LibWeb/Text/input/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-async-frame.html b/Tests/LibWeb/Text/input/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-async-frame.html
new file mode 100644
index 00000000000..d5535630be0
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-async-frame.html
@@ -0,0 +1,9 @@
+
+Text
+
diff --git a/Tests/LibWeb/Text/input/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-frame.html b/Tests/LibWeb/Text/input/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-frame.html
new file mode 100644
index 00000000000..d9ec23590bf
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-frame.html
@@ -0,0 +1,7 @@
+
+Text
+