2020-04-08 21:11:51 +02:00
/*
* Copyright ( c ) 2020 , Andreas Kling < kling @ serenityos . org >
2022-07-11 21:50:45 +01:00
* Copyright ( c ) 2021 - 2022 , Linus Groh < linusg @ serenityos . org >
2022-02-11 21:31:18 +00:00
* Copyright ( c ) 2022 , Luke Wilde < lukew @ serenityos . org >
2022-02-15 14:35:29 +03:30
* Copyright ( c ) 2022 , Ali Mohammad Pur < mpfard @ serenityos . org >
2022-07-07 23:15:41 +02:00
* Copyright ( c ) 2022 , Kenneth Myhra < kennethmyhra @ serenityos . org >
2020-04-08 21:11:51 +02:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-04-08 21:11:51 +02:00
*/
2022-07-18 08:08:21 +01:00
# include <AK/ByteBuffer.h>
2022-02-11 21:31:18 +00:00
# include <AK/GenericLexer.h>
2021-09-19 22:32:33 +02:00
# include <AK/QuickSort.h>
2022-02-15 14:35:29 +03:30
# include <LibJS/Runtime/AbstractOperations.h>
# include <LibJS/Runtime/ArrayBuffer.h>
2021-06-27 21:48:34 +02:00
# include <LibJS/Runtime/FunctionObject.h>
2022-02-15 14:35:29 +03:30
# include <LibJS/Runtime/GlobalObject.h>
2022-02-11 21:31:18 +00:00
# include <LibTextCodec/Decoder.h>
2022-08-28 13:42:07 +02:00
# include <LibWeb/Bindings/XMLHttpRequestPrototype.h>
2020-04-08 21:18:41 +02:00
# include <LibWeb/DOM/Document.h>
2020-04-08 21:11:51 +02:00
# include <LibWeb/DOM/Event.h>
2020-09-06 14:28:41 +02:00
# include <LibWeb/DOM/EventDispatcher.h>
2022-02-16 20:43:24 +01:00
# include <LibWeb/DOM/IDLEventListener.h>
2022-09-22 00:04:06 +01:00
# include <LibWeb/Fetch/BodyInit.h>
2022-07-11 22:11:03 +01:00
# include <LibWeb/Fetch/Infrastructure/HTTP.h>
2022-07-15 21:54:02 +02:00
# include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h>
2022-07-11 21:50:45 +01:00
# include <LibWeb/Fetch/Infrastructure/HTTP/Methods.h>
2022-07-14 14:32:04 +02:00
# include <LibWeb/FileAPI/Blob.h>
2021-09-19 01:40:13 +02:00
# include <LibWeb/HTML/EventHandler.h>
2020-11-21 19:15:57 +00:00
# include <LibWeb/HTML/EventNames.h>
2022-07-12 20:37:43 +01:00
# include <LibWeb/HTML/Origin.h>
2022-03-07 23:08:26 +01:00
# include <LibWeb/HTML/Window.h>
2020-06-01 20:42:50 +02:00
# include <LibWeb/Loader/ResourceLoader.h>
2021-09-12 02:10:43 +02:00
# include <LibWeb/Page/Page.h>
2022-09-25 17:28:46 +01:00
# include <LibWeb/WebIDL/DOMException.h>
2022-09-25 17:03:42 +01:00
# include <LibWeb/WebIDL/ExceptionOr.h>
2021-01-23 17:50:22 +00:00
# include <LibWeb/XHR/EventNames.h>
# include <LibWeb/XHR/ProgressEvent.h>
2021-01-23 11:51:36 +01:00
# include <LibWeb/XHR/XMLHttpRequest.h>
2020-04-08 21:11:51 +02:00
2021-01-23 11:51:36 +01:00
namespace Web : : XHR {
2020-04-08 21:11:51 +02:00
2022-08-28 13:42:07 +02:00
JS : : NonnullGCPtr < XMLHttpRequest > XMLHttpRequest : : create_with_global_object ( HTML : : Window & window )
{
return * window . heap ( ) . allocate < XMLHttpRequest > ( window . realm ( ) , window ) ;
}
2022-03-07 23:08:26 +01:00
XMLHttpRequest : : XMLHttpRequest ( HTML : : Window & window )
2022-08-28 13:42:07 +02:00
: XMLHttpRequestEventTarget ( window . realm ( ) )
2020-09-20 19:22:44 +02:00
, m_window ( window )
2022-02-15 14:35:29 +03:30
, m_response_type ( Bindings : : XMLHttpRequestResponseType : : Empty )
2020-04-08 21:11:51 +02:00
{
2022-09-03 18:43:24 +02:00
set_prototype ( & window . cached_web_prototype ( " XMLHttpRequest " ) ) ;
2020-04-08 21:11:51 +02:00
}
2022-03-14 13:21:51 -06:00
XMLHttpRequest : : ~ XMLHttpRequest ( ) = default ;
2020-04-08 21:11:51 +02:00
2022-08-28 13:42:07 +02:00
void XMLHttpRequest : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( m_window . ptr ( ) ) ;
2022-09-01 12:49:39 +02:00
if ( auto * value = m_response_object . get_pointer < JS : : Value > ( ) )
visitor . visit ( * value ) ;
2022-08-28 13:42:07 +02:00
}
2020-04-22 19:47:26 +01:00
void XMLHttpRequest : : set_ready_state ( ReadyState ready_state )
{
m_ready_state = ready_state ;
2022-08-28 13:42:07 +02:00
dispatch_event ( * DOM : : Event : : create ( global_object ( ) , EventNames : : readystatechange ) ) ;
2021-01-23 17:50:22 +00:00
}
2022-04-01 20:58:27 +03:00
void XMLHttpRequest : : fire_progress_event ( String const & event_name , u64 transmitted , u64 length )
2021-01-23 17:50:22 +00:00
{
2021-10-01 18:55:24 +03:00
ProgressEventInit event_init { } ;
event_init . length_computable = true ;
event_init . loaded = transmitted ;
event_init . total = length ;
2022-08-28 13:42:07 +02:00
dispatch_event ( * ProgressEvent : : create ( global_object ( ) , event_name , event_init ) ) ;
2020-04-22 19:47:26 +01:00
}
2022-02-11 21:31:18 +00:00
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-responsetext
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < String > XMLHttpRequest : : response_text ( ) const
2020-04-08 21:11:51 +02:00
{
2022-02-15 14:35:29 +03:30
// 1. If this’ s response type is not the empty string or "text", then throw an "InvalidStateError" DOMException.
if ( m_response_type ! = Bindings : : XMLHttpRequestResponseType : : Empty & & m_response_type ! = Bindings : : XMLHttpRequestResponseType : : Text )
2022-09-25 17:28:46 +01:00
return WebIDL : : InvalidStateError : : create ( global_object ( ) , " XHR responseText can only be used for responseType \" \" or \" text \" " ) ;
2022-02-11 21:31:18 +00:00
// 2. If this’ s state is not loading or done, then return the empty string.
if ( m_ready_state ! = ReadyState : : Loading & & m_ready_state ! = ReadyState : : Done )
return String : : empty ( ) ;
return get_text_response ( ) ;
}
2022-02-15 14:35:29 +03:30
// https://xhr.spec.whatwg.org/#response
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < JS : : Value > XMLHttpRequest : : response ( )
2022-02-15 14:35:29 +03:30
{
// 1. If this’ s response type is the empty string or "text", then:
if ( m_response_type = = Bindings : : XMLHttpRequestResponseType : : Empty | | m_response_type = = Bindings : : XMLHttpRequestResponseType : : Text ) {
// 1. If this’ s state is not loading or done, then return the empty string.
if ( m_ready_state ! = ReadyState : : Loading & & m_ready_state ! = ReadyState : : Done )
2022-08-28 13:42:07 +02:00
return JS : : Value ( JS : : js_string ( vm ( ) , " " ) ) ;
2022-02-15 14:35:29 +03:30
// 2. Return the result of getting a text response for this.
2022-08-28 13:42:07 +02:00
return JS : : Value ( JS : : js_string ( vm ( ) , get_text_response ( ) ) ) ;
2022-02-15 14:35:29 +03:30
}
// 2. If this’ s state is not done, then return null.
if ( m_ready_state ! = ReadyState : : Done )
return JS : : js_null ( ) ;
// 3. If this’ s response object is failure, then return null.
if ( m_response_object . has < Failure > ( ) )
return JS : : js_null ( ) ;
// 4. If this’ s response object is non-null, then return it.
if ( ! m_response_object . has < Empty > ( ) )
2022-09-01 12:49:39 +02:00
return m_response_object . get < JS : : Value > ( ) ;
2022-02-15 14:35:29 +03:30
// 5. If this’ s response type is "arraybuffer",
if ( m_response_type = = Bindings : : XMLHttpRequestResponseType : : Arraybuffer ) {
// then set this’ s response object to a new ArrayBuffer object representing this’ s received bytes. If this throws an exception, then set this’ s response object to failure and return null.
2022-08-28 13:42:07 +02:00
auto buffer_result = JS : : ArrayBuffer : : create ( realm ( ) , m_received_bytes . size ( ) ) ;
2022-02-15 14:35:29 +03:30
if ( buffer_result . is_error ( ) ) {
m_response_object = Failure ( ) ;
return JS : : js_null ( ) ;
}
auto buffer = buffer_result . release_value ( ) ;
buffer - > buffer ( ) . overwrite ( 0 , m_received_bytes . data ( ) , m_received_bytes . size ( ) ) ;
2022-09-01 12:49:39 +02:00
m_response_object = JS : : Value ( buffer ) ;
2022-02-15 14:35:29 +03:30
}
// 6. Otherwise, if this’ s response type is "blob", set this’ s response object to a new Blob object representing this’ s received bytes with type set to the result of get a final MIME type for this.
else if ( m_response_type = = Bindings : : XMLHttpRequestResponseType : : Blob ) {
2022-09-22 18:39:33 +01:00
auto blob_part = FileAPI : : Blob : : create ( global_object ( ) , m_received_bytes , get_final_mime_type ( ) . type ( ) ) ;
2022-09-04 11:44:38 +02:00
auto blob = TRY ( FileAPI : : Blob : : create ( global_object ( ) , Vector < FileAPI : : BlobPart > { JS : : make_handle ( * blob_part ) } ) ) ;
m_response_object = JS : : Value ( blob . ptr ( ) ) ;
2022-02-15 14:35:29 +03:30
}
// 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.
2022-09-25 17:03:42 +01:00
return WebIDL : : SimpleException { WebIDL : : SimpleExceptionType : : TypeError , " XHR Document type not implemented " } ;
2022-02-15 14:35:29 +03:30
}
// 8. Otherwise:
else {
// 1. Assert: this’ s response type is "json".
// Note: Automatically done by the layers above us.
// 2. If this’ s response’ s body is null, then return null.
// FIXME: Implement this once we have 'Response'.
if ( m_received_bytes . is_empty ( ) )
return JS : : Value ( JS : : js_null ( ) ) ;
// 3. Let jsonObject be the result of running parse JSON from bytes on this’ s received bytes. If that threw an exception, then return null.
TextCodec : : UTF8Decoder decoder ;
2022-08-28 13:42:07 +02:00
auto json_object_result = JS : : call ( vm ( ) , realm ( ) . intrinsics ( ) . json_parse_function ( ) , JS : : js_undefined ( ) , JS : : js_string ( vm ( ) , decoder . to_utf8 ( { m_received_bytes . data ( ) , m_received_bytes . size ( ) } ) ) ) ;
2022-02-15 14:35:29 +03:30
if ( json_object_result . is_error ( ) )
return JS : : Value ( JS : : js_null ( ) ) ;
// 4. Set this’ s response object to jsonObject.
2022-09-01 12:49:39 +02:00
m_response_object = json_object_result . release_value ( ) ;
2022-02-15 14:35:29 +03:30
}
// 9. Return this’ s response object.
2022-09-01 12:49:39 +02:00
return m_response_object . get < JS : : Value > ( ) ;
2022-02-15 14:35:29 +03:30
}
2022-02-11 21:31:18 +00:00
// https://xhr.spec.whatwg.org/#text-response
String XMLHttpRequest : : get_text_response ( ) const
{
// FIXME: 1. If xhr’ s response’ s body is null, then return the empty string.
// 2. Let charset be the result of get a final encoding for xhr.
auto charset = get_final_encoding ( ) ;
2022-02-15 14:35:29 +03:30
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 ;
2022-07-11 17:32:29 +00:00
return mime_type . subtype ( ) . ends_with ( " +xml " sv ) ;
2022-02-15 14:35:29 +03:30
} ;
// 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 ( ) ) ) {
// FIXME: then use the rules set forth in the XML specifications to determine the encoding. Let charset be the determined encoding. [XML] [XML-NAMES]
}
2022-02-11 21:31:18 +00:00
// 4. If charset is null, then set charset to UTF-8.
if ( ! charset . has_value ( ) )
2022-07-11 17:32:29 +00:00
charset = " UTF-8 " sv ;
2022-02-11 21:31:18 +00:00
// 5. Return the result of running decode on xhr’ s received bytes using fallback encoding charset.
auto * decoder = TextCodec : : decoder_for ( charset . value ( ) ) ;
// If we don't support the decoder yet, let's crash instead of attempting to return something, as the result would be incorrect and create obscure bugs.
VERIFY ( decoder ) ;
2022-02-15 14:35:29 +03:30
return TextCodec : : convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark ( * decoder , m_received_bytes ) ;
}
// https://xhr.spec.whatwg.org/#final-mime-type
MimeSniff : : MimeType XMLHttpRequest : : get_final_mime_type ( ) const
{
// 1. If xhr’ s override MIME type is null, return the result of get a response MIME type for xhr.
if ( ! m_override_mime_type . has_value ( ) )
return get_response_mime_type ( ) ;
// 2. Return xhr’ s override MIME type.
return * m_override_mime_type ;
2022-02-11 21:31:18 +00:00
}
// https://xhr.spec.whatwg.org/#response-mime-type
MimeSniff : : MimeType XMLHttpRequest : : get_response_mime_type ( ) const
{
2022-07-18 08:08:21 +01:00
// FIXME: Use an actual HeaderList for XHR headers.
2022-07-17 23:52:02 +01:00
Fetch : : Infrastructure : : HeaderList header_list ;
2022-07-18 08:08:21 +01:00
for ( auto const & entry : m_response_headers ) {
2022-07-17 23:52:02 +01:00
auto header = Fetch : : Infrastructure : : Header {
2022-07-18 08:08:21 +01:00
. name = MUST ( ByteBuffer : : copy ( entry . key . bytes ( ) ) ) ,
. value = MUST ( ByteBuffer : : copy ( entry . value . bytes ( ) ) ) ,
} ;
MUST ( header_list . append ( move ( header ) ) ) ;
}
2022-07-20 17:47:29 +01:00
// 1. Let mimeType be the result of extracting a MIME type from xhr’ s response’ s header list.
auto mime_type = header_list . extract_mime_type ( ) ;
2022-02-11 21:31:18 +00:00
// 2. If mimeType is failure, then set mimeType to text/xml.
if ( ! mime_type . has_value ( ) )
return MimeSniff : : MimeType ( " text " sv , " xml " sv ) ;
// 3. Return mimeType.
return mime_type . release_value ( ) ;
}
// https://xhr.spec.whatwg.org/#final-charset
2022-03-21 00:09:28 +01:00
Optional < StringView > XMLHttpRequest : : get_final_encoding ( ) const
2022-02-11 21:31:18 +00:00
{
// 1. Let label be null.
Optional < String > label ;
// 2. Let responseMIME be the result of get a response MIME type for xhr.
auto response_mime = get_response_mime_type ( ) ;
// 3. If responseMIME’ s parameters["charset"] exists, then set label to it.
auto response_mime_charset_it = response_mime . parameters ( ) . find ( " charset " sv ) ;
if ( response_mime_charset_it ! = response_mime . parameters ( ) . end ( ) )
label = response_mime_charset_it - > value ;
// 4. If xhr’ s override MIME type’ s parameters["charset"] exists, then set label to it.
if ( m_override_mime_type . has_value ( ) ) {
auto override_mime_charset_it = m_override_mime_type - > parameters ( ) . find ( " charset " sv ) ;
if ( override_mime_charset_it ! = m_override_mime_type - > parameters ( ) . end ( ) )
label = override_mime_charset_it - > value ;
}
// 5. If label is null, then return null.
if ( ! label . has_value ( ) )
return { } ;
// 6. Let encoding be the result of getting an encoding from label.
auto encoding = TextCodec : : get_standardized_encoding ( label . value ( ) ) ;
// 7. If encoding is failure, then return null.
// 8. Return encoding.
return encoding ;
}
2021-01-23 17:50:22 +00:00
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-setrequestheader
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > XMLHttpRequest : : set_request_header ( String const & name_string , String const & value_string )
2021-01-18 14:01:05 +01:00
{
2022-07-11 22:07:44 +01:00
auto name = name_string . to_byte_buffer ( ) ;
auto value = value_string . to_byte_buffer ( ) ;
2022-04-03 20:59:09 +02:00
// 1. If this’ s state is not opened, then throw an "InvalidStateError" DOMException.
2021-02-20 00:45:24 +01:00
if ( m_ready_state ! = ReadyState : : Opened )
2022-09-25 17:28:46 +01:00
return WebIDL : : InvalidStateError : : create ( global_object ( ) , " XHR readyState is not OPENED " ) ;
2021-01-23 17:50:22 +00:00
2022-04-03 20:59:09 +02:00
// 2. If this’ s send() flag is set, then throw an "InvalidStateError" DOMException.
2021-02-20 00:45:24 +01:00
if ( m_send )
2022-09-25 17:28:46 +01:00
return WebIDL : : InvalidStateError : : create ( global_object ( ) , " XHR send() flag is already set " ) ;
2021-01-23 17:50:22 +00:00
2022-04-03 20:59:09 +02:00
// 3. Normalize value.
2022-07-17 23:52:02 +01:00
value = MUST ( Fetch : : Infrastructure : : normalize_header_value ( value ) ) ;
2021-01-23 17:50:22 +00:00
2022-04-13 22:05:36 +02:00
// 4. If name is not a header name or value is not a header value, then throw a "SyntaxError" DOMException.
2022-07-17 23:52:02 +01:00
if ( ! Fetch : : Infrastructure : : is_header_name ( name ) )
2022-09-25 17:28:46 +01:00
return WebIDL : : SyntaxError : : create ( global_object ( ) , " Header name contains invalid characters. " ) ;
2022-07-17 23:52:02 +01:00
if ( ! Fetch : : Infrastructure : : is_header_value ( value ) )
2022-09-25 17:28:46 +01:00
return WebIDL : : SyntaxError : : create ( global_object ( ) , " Header value contains invalid characters. " ) ;
2022-04-03 20:59:09 +02:00
// 5. If name is a forbidden header name, then return.
2022-07-17 23:52:02 +01:00
if ( Fetch : : Infrastructure : : is_forbidden_header_name ( name ) )
2021-02-20 00:45:24 +01:00
return { } ;
2021-01-23 17:50:22 +00:00
2022-04-03 20:59:09 +02:00
// 6. Combine (name, value) in this’ s author request headers.
// FIXME: The header name look-up should be case-insensitive.
2022-07-11 22:07:44 +01:00
// FIXME: Headers should be stored as raw byte sequences, not Strings.
if ( m_request_headers . contains ( StringView { name } ) ) {
2022-04-03 20:59:09 +02:00
// 1. If list contains name, then set the value of the first such header to its value,
// followed by 0x2C 0x20, followed by value.
2022-07-11 22:07:44 +01:00
auto maybe_header_value = m_request_headers . get ( StringView { name } ) ;
m_request_headers . set ( StringView { name } , String : : formatted ( " {}, {} " , maybe_header_value . release_value ( ) , StringView { name } ) ) ;
2022-04-03 20:59:09 +02:00
} else {
// 2. Otherwise, append (name, value) to list.
2022-07-11 22:07:44 +01:00
m_request_headers . set ( StringView { name } , StringView { value } ) ;
2022-04-03 20:59:09 +02:00
}
2021-02-20 00:45:24 +01:00
return { } ;
2021-01-18 14:01:05 +01:00
}
2021-04-03 15:15:31 +02:00
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-open
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > XMLHttpRequest : : open ( String const & method_string , String const & url )
2020-04-08 21:11:51 +02:00
{
2022-04-18 20:31:29 +02:00
// 8. If the async argument is omitted, set async to true, and set username and password to null.
2022-07-11 21:50:45 +01:00
return open ( method_string , url , true , { } , { } ) ;
2022-04-03 18:43:02 +02:00
}
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > XMLHttpRequest : : open ( String const & method_string , String const & url , bool async , String const & username , String const & password )
2022-04-03 18:43:02 +02:00
{
2022-07-11 21:50:45 +01:00
auto method = method_string . to_byte_buffer ( ) ;
2022-04-18 20:31:29 +02:00
// 1. Let settingsObject be this’ s relevant settings object.
auto & settings_object = m_window - > associated_document ( ) . relevant_settings_object ( ) ;
2021-01-23 17:50:22 +00:00
2022-04-18 20:31:29 +02:00
// 2. If settingsObject has a responsible document and it is not fully active, then throw an "InvalidStateError" DOMException.
2022-08-28 13:42:07 +02:00
if ( settings_object . responsible_document ( ) & & ! settings_object . responsible_document ( ) - > is_active ( ) )
2022-09-25 17:28:46 +01:00
return WebIDL : : InvalidStateError : : create ( global_object ( ) , " Invalid state: Responsible document is not fully active. " ) ;
2021-01-23 17:50:22 +00:00
2022-04-03 18:43:02 +02:00
// 3. If method is not a method, then throw a "SyntaxError" DOMException.
2022-07-17 23:52:02 +01:00
if ( ! Fetch : : Infrastructure : : is_method ( method ) )
2022-09-25 17:28:46 +01:00
return WebIDL : : SyntaxError : : create ( global_object ( ) , " An invalid or illegal string was specified. " ) ;
2021-01-23 17:50:22 +00:00
2022-04-03 18:43:02 +02:00
// 4. If method is a forbidden method, then throw a "SecurityError" DOMException.
2022-07-17 23:52:02 +01:00
if ( Fetch : : Infrastructure : : is_forbidden_method ( method ) )
2022-09-25 17:28:46 +01:00
return WebIDL : : SecurityError : : create ( global_object ( ) , " Forbidden method, must not be 'CONNECT', 'TRACE', or 'TRACK' " ) ;
2021-01-23 17:50:22 +00:00
2022-04-03 18:43:02 +02:00
// 5. Normalize method.
2022-07-17 23:52:02 +01:00
method = MUST ( Fetch : : Infrastructure : : normalize_method ( method ) ) ;
2021-01-23 17:50:22 +00:00
2022-04-03 18:43:02 +02:00
// 6. Let parsedURL be the result of parsing url with settingsObject’ s API base URL and settingsObject’ s API URL character encoding.
2022-06-19 16:02:48 +01:00
auto parsed_url = settings_object . api_base_url ( ) . complete_url ( url ) ;
2022-04-18 20:31:29 +02:00
2022-04-03 18:43:02 +02:00
// 7. If parsedURL is failure, then throw a "SyntaxError" DOMException.
2021-02-20 00:46:19 +01:00
if ( ! parsed_url . is_valid ( ) )
2022-09-25 17:28:46 +01:00
return WebIDL : : SyntaxError : : create ( global_object ( ) , " Invalid URL " ) ;
2021-01-23 17:50:22 +00:00
2022-04-18 20:31:29 +02:00
// 8. If the async argument is omitted, set async to true, and set username and password to null.
// NOTE: This is handled in the overload lacking the async argument.
2022-04-03 18:43:02 +02:00
// 9. If parsedURL’ s host is non-null, then:
2021-01-23 17:50:22 +00:00
if ( ! parsed_url . host ( ) . is_null ( ) ) {
2022-04-03 18:43:02 +02:00
// 1. If the username argument is not null, set the username given parsedURL and username.
if ( ! username . is_null ( ) )
parsed_url . set_username ( username ) ;
// 2. If the password argument is not null, set the password given parsedURL and password.
if ( ! password . is_null ( ) )
parsed_url . set_password ( password ) ;
2021-01-23 17:50:22 +00:00
}
2022-04-03 18:43:02 +02:00
// FIXME: 10. If async is false, the current global object is a Window object, and either this’ s timeout is
2021-01-23 17:50:22 +00:00
// not 0 or this’ s response type is not the empty string, then throw an "InvalidAccessError" DOMException.
2022-04-03 18:43:02 +02:00
// FIXME: 11. Terminate the ongoing fetch operated by the XMLHttpRequest object.
2021-01-23 17:50:22 +00:00
2022-04-03 18:43:02 +02:00
// 12. Set variables associated with the object as follows:
// Unset this’ s send() flag.
2021-01-23 17:50:22 +00:00
m_send = false ;
2022-04-03 18:43:02 +02:00
// Unset this’ s upload listener flag.
2021-01-23 17:50:22 +00:00
m_upload_listener = false ;
2022-04-03 18:43:02 +02:00
// Set this’ s request method to method.
2022-07-11 21:50:45 +01:00
m_method = move ( method ) ;
2022-04-03 18:43:02 +02:00
// Set this’ s request URL to parsedURL.
2021-01-23 17:50:22 +00:00
m_url = parsed_url ;
2022-04-03 18:43:02 +02:00
// Set this’ s synchronous flag if async is false; otherwise unset this’ s synchronous flag.
m_synchronous = ! async ;
// Empty this’ s author request headers.
2021-01-18 14:01:05 +01:00
m_request_headers . clear ( ) ;
2021-01-23 17:50:22 +00:00
// FIXME: Set this’ s response to a network error.
2022-04-03 18:43:02 +02:00
// Set this’ s received bytes to the empty byte sequence.
m_received_bytes = { } ;
// Set this’ s response object to null.
2021-01-23 17:50:22 +00:00
m_response_object = { } ;
2022-04-03 18:43:02 +02:00
// 13. If this’ s state is not opened, then:
if ( m_ready_state ! = ReadyState : : Opened ) {
// 1. Set this’ s state to opened.
// 2. Fire an event named readystatechange at this.
2021-01-23 17:50:22 +00:00
set_ready_state ( ReadyState : : Opened ) ;
2022-04-03 18:43:02 +02:00
}
2022-04-18 20:31:29 +02:00
2021-02-20 00:46:19 +01:00
return { } ;
2020-04-08 21:11:51 +02:00
}
2021-01-23 17:50:22 +00:00
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-send
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > XMLHttpRequest : : send ( Optional < Fetch : : XMLHttpRequestBodyInit > body )
2020-04-08 21:11:51 +02:00
{
2022-09-21 23:54:04 +01:00
auto & vm = this - > vm ( ) ;
auto & realm = * vm . current_realm ( ) ;
2021-02-20 00:46:52 +01:00
if ( m_ready_state ! = ReadyState : : Opened )
2022-09-25 17:28:46 +01:00
return WebIDL : : InvalidStateError : : create ( global_object ( ) , " XHR readyState is not OPENED " ) ;
2021-01-23 17:50:22 +00:00
2021-02-20 00:46:52 +01:00
if ( m_send )
2022-09-25 17:28:46 +01:00
return WebIDL : : InvalidStateError : : create ( global_object ( ) , " XHR send() flag is already set " ) ;
2021-01-23 17:50:22 +00:00
2021-10-04 00:06:48 +02:00
// If this’ s request method is `GET` or `HEAD`, then set body to null.
if ( m_method . is_one_of ( " GET " sv , " HEAD " sv ) )
body = { } ;
2021-01-23 17:50:22 +00:00
2022-09-22 17:30:54 +01:00
auto body_with_type = body . has_value ( ) ? TRY ( Fetch : : extract_body ( realm , body . value ( ) ) ) : Optional < Fetch : : Infrastructure : : BodyWithType > { } ;
2022-07-07 23:15:41 +02:00
2021-09-13 00:33:23 +03:00
AK : : URL request_url = m_window - > associated_document ( ) . parse_url ( m_url . to_string ( ) ) ;
2021-09-09 13:55:31 +02:00
dbgln ( " XHR send from {} to {} " , m_window - > associated_document ( ) . url ( ) , request_url ) ;
2020-10-31 20:36:50 +00:00
// TODO: Add support for preflight requests to support CORS requests
2022-09-29 01:30:58 +02:00
auto request_url_origin = HTML : : Origin ( request_url . scheme ( ) , request_url . host ( ) , request_url . port_or_default ( ) ) ;
2020-10-31 20:36:50 +00:00
2021-09-12 02:10:43 +02:00
bool should_enforce_same_origin_policy = true ;
if ( auto * page = m_window - > page ( ) )
should_enforce_same_origin_policy = page - > is_same_origin_policy_enabled ( ) ;
2022-02-14 21:54:20 +00:00
if ( should_enforce_same_origin_policy & & ! m_window - > associated_document ( ) . origin ( ) . is_same_origin ( request_url_origin ) ) {
2021-09-09 13:55:31 +02:00
dbgln ( " XHR failed to load: Same-Origin Policy violation: {} may not load {} " , m_window - > associated_document ( ) . url ( ) , request_url ) ;
2021-09-19 14:59:02 +02:00
set_ready_state ( ReadyState : : Done ) ;
2022-08-28 13:42:07 +02:00
dispatch_event ( * DOM : : Event : : create ( global_object ( ) , HTML : : EventNames : : error ) ) ;
2021-02-20 00:46:52 +01:00
return { } ;
2020-10-31 20:36:50 +00:00
}
2021-09-09 13:45:03 +02:00
auto request = LoadRequest : : create_for_url_on_page ( request_url , m_window - > page ( ) ) ;
2021-01-23 17:50:22 +00:00
request . set_method ( m_method ) ;
2022-07-15 21:54:02 +02:00
if ( body_with_type . has_value ( ) ) {
2022-09-04 16:56:15 +02:00
TRY_OR_RETURN_OOM ( global_object ( ) , body_with_type - > body . source ( ) . visit ( [ & ] ( ByteBuffer const & buffer ) - > ErrorOr < void > {
2022-07-24 15:43:33 +02:00
request . set_body ( buffer ) ;
2022-09-04 16:56:15 +02:00
return { } ; } , [ & ] ( JS : : Handle < FileAPI : : Blob > const & blob ) - > ErrorOr < void > {
2022-07-24 15:43:33 +02:00
auto byte_buffer = TRY ( ByteBuffer : : copy ( blob - > bytes ( ) ) ) ;
request . set_body ( byte_buffer ) ;
2022-09-04 16:56:15 +02:00
return { } ; } , [ ] ( auto & ) - > ErrorOr < void > { return { } ; } ) ) ;
2022-09-18 12:23:02 +02:00
if ( body_with_type - > type . has_value ( ) ) {
// If type is non-null and this’ s headers’ s header list does not contain `Content-Type`, then append (`Content-Type`, type) to this’ s headers.
if ( ! m_request_headers . contains ( " Content-Type " sv ) )
request . set_header ( " Content-Type " , String { body_with_type - > type - > span ( ) } ) ;
}
2022-07-07 23:15:41 +02:00
}
2021-01-18 14:01:05 +01:00
for ( auto & it : m_request_headers )
request . set_header ( it . key , it . value ) ;
2021-01-23 17:50:22 +00:00
m_upload_complete = false ;
m_timed_out = false ;
// FIXME: If req’ s body is null (which it always is currently)
m_upload_complete = true ;
m_send = true ;
if ( ! m_synchronous ) {
fire_progress_event ( EventNames : : loadstart , 0 , 0 ) ;
// FIXME: If this’ s upload complete flag is unset and this’ s upload listener flag is set,
// then fire a progress event named loadstart at this’ s upload object with 0 and req’ s body’ s total bytes.
if ( m_ready_state ! = ReadyState : : Opened | | ! m_send )
2021-02-20 00:46:52 +01:00
return { } ;
2021-01-23 17:50:22 +00:00
// FIXME: in order to properly set ReadyState::HeadersReceived and ReadyState::Loading,
// we need to make ResourceLoader give us more detailed updates than just "done" and "error".
2022-07-12 18:17:50 +01:00
// FIXME: In the Fetch spec, which XHR gets its definition of `status` from, the status code is 0-999.
// We could clamp, wrap around (current browser behavior!), or error out.
// See: https://github.com/whatwg/fetch/issues/1142
2021-01-23 17:50:22 +00:00
ResourceLoader : : the ( ) . load (
request ,
2022-08-28 13:42:07 +02:00
[ weak_this = make_weak_ptr < XMLHttpRequest > ( ) ] ( auto data , auto & response_headers , auto status_code ) {
JS : : GCPtr < XMLHttpRequest > strong_this = weak_this . ptr ( ) ;
2021-09-19 15:02:04 +02:00
if ( ! strong_this )
2021-01-23 17:50:22 +00:00
return ;
auto & xhr = const_cast < XMLHttpRequest & > ( * weak_this ) ;
2021-09-06 03:29:52 +04:30
// FIXME: Handle OOM failure.
2022-01-20 17:47:39 +00:00
auto response_data = ByteBuffer : : copy ( data ) . release_value_but_fixme_should_propagate_errors ( ) ;
2021-01-23 17:50:22 +00:00
// FIXME: There's currently no difference between transmitted and length.
u64 transmitted = response_data . size ( ) ;
u64 length = response_data . size ( ) ;
if ( ! xhr . m_synchronous ) {
2022-02-15 14:35:29 +03:30
xhr . m_received_bytes = response_data ;
2021-01-23 17:50:22 +00:00
xhr . fire_progress_event ( EventNames : : progress , transmitted , length ) ;
}
xhr . m_ready_state = ReadyState : : Done ;
2021-04-03 15:17:29 +02:00
xhr . m_status = status_code . value_or ( 0 ) ;
2021-04-03 15:51:15 +02:00
xhr . m_response_headers = move ( response_headers ) ;
2021-01-23 17:50:22 +00:00
xhr . m_send = false ;
2022-08-28 13:42:07 +02:00
xhr . dispatch_event ( * DOM : : Event : : create ( xhr . global_object ( ) , EventNames : : readystatechange ) ) ;
2021-01-23 17:50:22 +00:00
xhr . fire_progress_event ( EventNames : : load , transmitted , length ) ;
xhr . fire_progress_event ( EventNames : : loadend , transmitted , length ) ;
} ,
2022-08-28 13:42:07 +02:00
[ weak_this = make_weak_ptr < XMLHttpRequest > ( ) ] ( auto & error , auto status_code ) {
2021-01-23 17:50:22 +00:00
dbgln ( " XHR failed to load: {} " , error ) ;
2022-08-28 13:42:07 +02:00
JS : : GCPtr < XMLHttpRequest > strong_this = weak_this . ptr ( ) ;
2021-09-19 15:02:04 +02:00
if ( ! strong_this )
return ;
auto & xhr = const_cast < XMLHttpRequest & > ( * strong_this ) ;
xhr . set_ready_state ( ReadyState : : Done ) ;
xhr . set_status ( status_code . value_or ( 0 ) ) ;
2022-08-28 13:42:07 +02:00
xhr . dispatch_event ( * DOM : : Event : : create ( xhr . global_object ( ) , HTML : : EventNames : : error ) ) ;
2022-04-27 21:28:59 +02:00
} ,
2022-07-02 22:33:17 +02:00
m_timeout ,
2022-08-28 13:42:07 +02:00
[ weak_this = make_weak_ptr < XMLHttpRequest > ( ) ] {
JS : : GCPtr < XMLHttpRequest > strong_this = weak_this . ptr ( ) ;
2022-07-02 22:33:17 +02:00
if ( ! strong_this )
return ;
auto & xhr = const_cast < XMLHttpRequest & > ( * strong_this ) ;
2022-08-28 13:42:07 +02:00
xhr . dispatch_event ( * DOM : : Event : : create ( xhr . global_object ( ) , EventNames : : timeout ) ) ;
2022-07-02 22:33:17 +02:00
} ) ;
2021-01-23 17:50:22 +00:00
} else {
TODO ( ) ;
}
2021-02-20 00:46:52 +01:00
return { } ;
2020-04-08 21:11:51 +02:00
}
2022-09-24 16:02:41 +01:00
WebIDL : : CallbackType * XMLHttpRequest : : onreadystatechange ( )
2021-09-19 01:40:13 +02:00
{
return event_handler_attribute ( Web : : XHR : : EventNames : : readystatechange ) ;
}
2022-09-24 16:02:41 +01:00
void XMLHttpRequest : : set_onreadystatechange ( WebIDL : : CallbackType * value )
2021-09-19 01:40:13 +02:00
{
2022-08-08 14:12:01 +02:00
set_event_handler_attribute ( Web : : XHR : : EventNames : : readystatechange , value ) ;
2021-09-19 01:40:13 +02:00
}
2021-09-19 22:32:33 +02:00
// https://xhr.spec.whatwg.org/#the-getallresponseheaders()-method
String XMLHttpRequest : : get_all_response_headers ( ) const
{
// FIXME: Implement the spec-compliant sort order.
StringBuilder builder ;
auto keys = m_response_headers . keys ( ) ;
quick_sort ( keys ) ;
for ( auto & key : keys ) {
builder . append ( key ) ;
2022-07-11 17:32:29 +00:00
builder . append ( " : " sv ) ;
2021-09-19 22:32:33 +02:00
builder . append ( m_response_headers . get ( key ) . value ( ) ) ;
2022-07-11 17:32:29 +00:00
builder . append ( " \r \n " sv ) ;
2021-09-19 22:32:33 +02:00
}
return builder . to_string ( ) ;
}
2022-02-11 21:04:42 +00:00
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-overridemimetype
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > XMLHttpRequest : : override_mime_type ( String const & mime )
2022-02-11 21:04:42 +00:00
{
// 1. If this’ s state is loading or done, then throw an "InvalidStateError" DOMException.
if ( m_ready_state = = ReadyState : : Loading | | m_ready_state = = ReadyState : : Done )
2022-09-25 17:28:46 +01:00
return WebIDL : : InvalidStateError : : create ( global_object ( ) , " Cannot override MIME type when state is Loading or Done. " ) ;
2022-02-11 21:04:42 +00:00
// 2. Set this’ s override MIME type to the result of parsing mime.
m_override_mime_type = MimeSniff : : MimeType : : from_string ( mime ) ;
// 3. If this’ s override MIME type is failure, then set this’ s override MIME type to application/octet-stream.
if ( ! m_override_mime_type . has_value ( ) )
m_override_mime_type = MimeSniff : : MimeType ( " application " sv , " octet-stream " sv ) ;
return { } ;
}
2022-04-23 21:42:15 +02:00
// https://xhr.spec.whatwg.org/#ref-for-dom-xmlhttprequest-timeout%E2%91%A2
2022-09-25 17:03:42 +01:00
WebIDL : : ExceptionOr < void > XMLHttpRequest : : set_timeout ( u32 timeout )
2022-04-23 21:42:15 +02:00
{
// 1. If the current global object is a Window object and this’ s synchronous flag is set,
// then throw an "InvalidAccessError" DOMException.
2022-08-28 13:42:07 +02:00
if ( is < HTML : : Window > ( HTML : : current_global_object ( ) ) & & m_synchronous )
2022-09-25 17:28:46 +01:00
return WebIDL : : InvalidAccessError : : create ( global_object ( ) , " Use of XMLHttpRequest's timeout attribute is not supported in the synchronous mode in window context. " ) ;
2022-04-23 21:42:15 +02:00
// 2. Set this’ s timeout to the given value.
m_timeout = timeout ;
return { } ;
}
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-timeout
u32 XMLHttpRequest : : timeout ( ) const { return m_timeout ; }
2020-04-08 21:11:51 +02:00
}