diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 61ba9f5b503..1fa66a1ac4c 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -522,4 +523,10 @@ void Document::set_focused_element(Element* element) m_layout_root->set_needs_display(); } +void Document::set_ready_state(const String& ready_state) +{ + m_ready_state = ready_state; + dispatch_event(Event::create("readystatechange")); +} + } diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index 8f58a97353a..f438ff3c4fc 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -174,6 +174,9 @@ public: const Document* associated_inert_template_document() const { return m_associated_inert_template_document; } void set_associated_inert_template_document(Document& document) { m_associated_inert_template_document = document; } + const String& ready_state() const { return m_ready_state; } + void set_ready_state(const String&); + private: virtual RefPtr create_layout_node(const CSS::StyleProperties* parent_style) override; @@ -209,6 +212,8 @@ private: bool m_created_for_appropriate_template_contents { false }; RefPtr m_associated_inert_template_document; + + String m_ready_state { "loading" }; }; } diff --git a/Libraries/LibWeb/DOM/Document.idl b/Libraries/LibWeb/DOM/Document.idl index 48c2fd67659..c03204c53cf 100644 --- a/Libraries/LibWeb/DOM/Document.idl +++ b/Libraries/LibWeb/DOM/Document.idl @@ -17,4 +17,6 @@ interface Document : Node { attribute HTMLElement? body; readonly attribute HTMLHeadElement? head; + readonly attribute DOMString readyState; + } diff --git a/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp b/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp index 2ef4fd3b214..2f204692b61 100644 --- a/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp +++ b/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp @@ -156,6 +156,8 @@ void HTMLDocumentParser::run(const URL& url) // "The end" + m_document->set_ready_state("interactive"); + auto scripts_to_execute_when_parsing_has_finished = m_document->take_scripts_to_execute_when_parsing_has_finished({}); for (auto& script : scripts_to_execute_when_parsing_has_finished) { script.execute_script(); @@ -167,6 +169,8 @@ void HTMLDocumentParser::run(const URL& url) for (auto& script : scripts_to_execute_as_soon_as_possible) { script.execute_script(); } + + m_document->set_ready_state("complete"); } void HTMLDocumentParser::process_using_the_rules_for(InsertionMode mode, HTMLToken& token) diff --git a/Libraries/LibWeb/Tests/HTML/document.readyState.js b/Libraries/LibWeb/Tests/HTML/document.readyState.js new file mode 100644 index 00000000000..12b6c1d8dfb --- /dev/null +++ b/Libraries/LibWeb/Tests/HTML/document.readyState.js @@ -0,0 +1,31 @@ +loadPage("file:///res/html/misc/blank.html"); + +beforeInitialPageLoad(() => { + window.events = []; + + document.addEventListener("readystatechange", () => { + window.events.push(document.readyState); + }); + + document.addEventListener("DOMContentLoaded", () => { + test("Ready state should be 'interactive' when 'DOMContentLoaded' fires", () => { + expect(document.readyState).toBe("interactive"); + }); + }); + + test("Ready state should be 'loading' initially", () => { + expect(document.readyState).toBe("loading"); + }); +}); + +afterInitialPageLoad(() => { + test("'interactive' should come before 'complete' and both should have happened", () => { + expect(window.events).toHaveLength(2); + expect(window.events[0]).toBe("interactive"); + expect(window.events[1]).toBe("complete"); + }); + + test("Ready state should be 'complete' after loading", () => { + expect(document.readyState).toBe("complete"); + }); +});