From 222cc29c5c0269cdf1881eef25f49d75acb8b413 Mon Sep 17 00:00:00 2001 From: Bastiaan van der Plaat Date: Thu, 14 Sep 2023 21:07:53 +0200 Subject: [PATCH] LibWeb: Add XMLHttpRequest Document response type --- .../Libraries/LibWeb/DOM/DocumentLoading.cpp | 2 +- .../Libraries/LibWeb/DOM/DocumentLoading.h | 1 + .../Libraries/LibWeb/MimeSniff/MimeType.cpp | 12 ++ .../Libraries/LibWeb/MimeSniff/MimeType.h | 2 + .../Libraries/LibWeb/XHR/XMLHttpRequest.cpp | 114 ++++++++++++++++-- .../Libraries/LibWeb/XHR/XMLHttpRequest.h | 2 + .../Libraries/LibWeb/XHR/XMLHttpRequest.idl | 1 + 7 files changed, 122 insertions(+), 12 deletions(-) diff --git a/Userland/Libraries/LibWeb/DOM/DocumentLoading.cpp b/Userland/Libraries/LibWeb/DOM/DocumentLoading.cpp index 5f7132e33f0..054e3d99368 100644 --- a/Userland/Libraries/LibWeb/DOM/DocumentLoading.cpp +++ b/Userland/Libraries/LibWeb/DOM/DocumentLoading.cpp @@ -146,7 +146,7 @@ static bool build_gemini_document(DOM::Document& document, ByteBuffer const& dat return true; } -static bool build_xml_document(DOM::Document& document, ByteBuffer const& data) +bool build_xml_document(DOM::Document& document, ByteBuffer const& data) { auto encoding = HTML::run_encoding_sniffing_algorithm(document, data); auto decoder = TextCodec::decoder_for(encoding); diff --git a/Userland/Libraries/LibWeb/DOM/DocumentLoading.h b/Userland/Libraries/LibWeb/DOM/DocumentLoading.h index aa87db2a21b..b8baf8d8d95 100644 --- a/Userland/Libraries/LibWeb/DOM/DocumentLoading.h +++ b/Userland/Libraries/LibWeb/DOM/DocumentLoading.h @@ -11,6 +11,7 @@ namespace Web { +bool build_xml_document(DOM::Document& document, ByteBuffer const& data); bool parse_document(DOM::Document& document, ByteBuffer const& data); JS::GCPtr load_document(Optional navigation_params); JS::GCPtr create_document_for_inline_content(JS::GCPtr navigable, Optional navigation_id, StringView content_html); diff --git a/Userland/Libraries/LibWeb/MimeSniff/MimeType.cpp b/Userland/Libraries/LibWeb/MimeSniff/MimeType.cpp index ff2dd711f56..c4333cd27cb 100644 --- a/Userland/Libraries/LibWeb/MimeSniff/MimeType.cpp +++ b/Userland/Libraries/LibWeb/MimeSniff/MimeType.cpp @@ -238,6 +238,18 @@ ErrorOr MimeType::set_parameter(String name, String value) return {}; } +// https://mimesniff.spec.whatwg.org/#xml-mime-type +bool MimeType::is_xml() const +{ + return m_subtype.ends_with_bytes("+xml"sv) || essence().is_one_of("text/xml"sv, "application/xml"sv); +} + +// https://mimesniff.spec.whatwg.org/#html-mime-type +bool MimeType::is_html() const +{ + return essence().is_one_of("text/html"sv); +} + // https://mimesniff.spec.whatwg.org/#javascript-mime-type bool MimeType::is_javascript() const { diff --git a/Userland/Libraries/LibWeb/MimeSniff/MimeType.h b/Userland/Libraries/LibWeb/MimeSniff/MimeType.h index 84805f21764..0030d6fde59 100644 --- a/Userland/Libraries/LibWeb/MimeSniff/MimeType.h +++ b/Userland/Libraries/LibWeb/MimeSniff/MimeType.h @@ -25,6 +25,8 @@ public: String const& subtype() const { return m_subtype; } OrderedHashMap const& parameters() const { return m_parameters; } + bool is_xml() const; + bool is_html() const; bool is_javascript() const; ErrorOr set_parameter(String name, String value); diff --git a/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.cpp b/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.cpp index fff7c13bab4..257fa199005 100644 --- a/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.cpp +++ b/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,8 @@ #include #include #include +#include +#include #include #include #include @@ -115,9 +118,35 @@ WebIDL::ExceptionOr XMLHttpRequest::response_text() const if (m_state != State::Loading && m_state != State::Done) return String {}; + // 3. Return the result of getting a text response for this. return get_text_response(); } +// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-responsexml +WebIDL::ExceptionOr> XMLHttpRequest::response_xml() +{ + // 1. If this’s response type is not the empty string or "document", then throw an "InvalidStateError" DOMException. + if (m_response_type != Bindings::XMLHttpRequestResponseType::Empty && m_response_type != Bindings::XMLHttpRequestResponseType::Document) + return WebIDL::InvalidStateError::create(realm(), "XHR responseXML can only be used for responseXML \"\" or \"document\""_fly_string); + + // 2. If this’s state is not done, then return null. + if (m_state != State::Done) + return nullptr; + + // 3. Assert: this’s response object is not failure. + VERIFY(!m_response_object.has()); + + // 4. If this’s response object is non-null, then return it. + if (!m_response_object.has()) + return &verify_cast(m_response_object.get().as_object()); + + // 5. Set a document response for this. + set_document_response(); + + // 6. Return this’s response object. + return &verify_cast(m_response_object.get().as_object()); +} + // https://xhr.spec.whatwg.org/#dom-xmlhttprequest-responsetype WebIDL::ExceptionOr XMLHttpRequest::set_response_type(Bindings::XMLHttpRequestResponseType response_type) { @@ -186,8 +215,7 @@ WebIDL::ExceptionOr XMLHttpRequest::response() } // 7. Otherwise, if this’s response type is "document", set a document response for this. else if (m_response_type == Bindings::XMLHttpRequestResponseType::Document) { - // FIXME: Implement this. - return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "XHR Document type not implemented"sv }; + set_document_response(); } // 8. Otherwise: else { @@ -221,16 +249,8 @@ String XMLHttpRequest::get_text_response() const // 2. Let charset be the result of get a final encoding for xhr. auto charset = get_final_encoding().release_value_but_fixme_should_propagate_errors(); - auto is_xml_mime_type = [](MimeSniff::MimeType const& mime_type) { - // An XML MIME type is any MIME type whose subtype ends in "+xml" or whose essence is "text/xml" or "application/xml". [RFC7303] - if (mime_type.essence().is_one_of("text/xml"sv, "application/xml"sv)) - return true; - - return mime_type.subtype().ends_with_bytes("+xml"sv); - }; - // 3. If xhr’s response type is the empty string, charset is null, and the result of get a final MIME type for xhr is an XML MIME type, - if (m_response_type == Bindings::XMLHttpRequestResponseType::Empty && !charset.has_value() && is_xml_mime_type(get_final_mime_type().release_value_but_fixme_should_propagate_errors())) { + if (m_response_type == Bindings::XMLHttpRequestResponseType::Empty && !charset.has_value() && get_final_mime_type().release_value_but_fixme_should_propagate_errors().is_xml()) { // FIXME: then use the rules set forth in the XML specifications to determine the encoding. Let charset be the determined encoding. [XML] [XML-NAMES] } @@ -247,6 +267,78 @@ String XMLHttpRequest::get_text_response() const return TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*decoder, m_received_bytes).release_value_but_fixme_should_propagate_errors(); } +// https://xhr.spec.whatwg.org/#document-response +void XMLHttpRequest::set_document_response() +{ + // 1. If xhr’s response’s body is null, then return. + if (!m_response->body()) + return; + + // 2. Let finalMIME be the result of get a final MIME type for xhr. + auto final_mine = MUST(get_final_mime_type()); + + // 3. If finalMIME is not an HTML MIME type or an XML MIME type, then return. + if (!final_mine.is_html() && !final_mine.is_xml()) + return; + + // 4. If xhr’s response type is the empty string and finalMIME is an HTML MIME type, then return. + if (m_response_type == Bindings::XMLHttpRequestResponseType::Empty && final_mine.is_html()) + return; + + // 5. If finalMIME is an HTML MIME type, then: + Optional charset; + JS::GCPtr document; + if (final_mine.is_html()) { + // 5.1. Let charset be the result of get a final encoding for xhr. + charset = MUST(get_final_encoding()); + + // 5.2. If charset is null, prescan the first 1024 bytes of xhr’s received bytes and if that does not terminate unsuccessfully then let charset be the return value. + document = DOM::Document::create(realm()); + if (!charset.has_value()) + if (auto found_charset = HTML::run_prescan_byte_stream_algorithm(*document, m_received_bytes); found_charset.has_value()) + charset = MUST(String::from_deprecated_string(found_charset.value())).bytes_as_string_view(); + + // 5.3. If charset is null, then set charset to UTF-8. + if (!charset.has_value()) + charset = "UTF-8"sv; + + // 5.4. Let document be a document that represents the result parsing xhr’s received bytes following the rules set forth in the HTML Standard for an HTML parser with scripting disabled and a known definite encoding charset. + auto parser = HTML::HTMLParser::create(*document, m_received_bytes, charset.value()); + parser->run(document->url()); + + // 5.5. Flag document as an HTML document. + document->set_document_type(DOM::Document::Type::HTML); + } + + // 6. Otherwise, let document be a document that represents the result of running the XML parser with XML scripting support disabled on xhr’s received bytes. If that fails (unsupported character encoding, namespace well-formedness error, etc.), then return null. + else { + document = DOM::Document::create(realm()); + if (!Web::build_xml_document(*document, m_received_bytes)) { + m_response_object = JS::js_null(); + return; + } + } + + // 7. If charset is null, then set charset to UTF-8. + if (!charset.has_value()) + charset = "UTF-8"sv; + + // 8. Set document’s encoding to charset. + document->set_encoding(charset->to_deprecated_string()); + + // 9. Set document’s content type to finalMIME. + document->set_content_type(MUST(final_mine.serialized()).to_deprecated_string()); + + // 10. Set document’s URL to xhr’s response’s URL. + document->set_url(m_response->url().value_or({})); + + // 11. Set document’s origin to xhr’s relevant settings object’s origin. + document->set_origin(HTML::relevant_settings_object(*this).origin()); + + // 12. Set xhr’s response object to document. + m_response_object = JS::Value(document); +} + // https://xhr.spec.whatwg.org/#final-mime-type ErrorOr XMLHttpRequest::get_final_mime_type() const { diff --git a/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.h b/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.h index acb1a72ff04..3f7a4817525 100644 --- a/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.h +++ b/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.h @@ -48,6 +48,7 @@ public: Fetch::Infrastructure::Status status() const; WebIDL::ExceptionOr status_text() const; WebIDL::ExceptionOr response_text() const; + WebIDL::ExceptionOr> response_xml(); WebIDL::ExceptionOr response(); Bindings::XMLHttpRequestResponseType response_type() const { return m_response_type; } @@ -86,6 +87,7 @@ private: ErrorOr get_final_mime_type() const; String get_text_response() const; + void set_document_response(); WebIDL::ExceptionOr handle_response_end_of_body(); WebIDL::ExceptionOr handle_errors(); diff --git a/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.idl b/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.idl index 4b697b85063..3d91301e995 100644 --- a/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.idl +++ b/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.idl @@ -29,6 +29,7 @@ interface XMLHttpRequest : XMLHttpRequestEventTarget { readonly attribute unsigned short status; readonly attribute ByteString statusText; readonly attribute DOMString responseText; + readonly attribute Document? responseXML; readonly attribute any response; attribute XMLHttpRequestResponseType responseType; attribute unsigned long timeout;