2024-11-08 16:35:16 +13:00
/*
* Copyright ( c ) 2022 , Andrew Kaster < akaster @ serenityos . org >
* Copyright ( c ) 2023 , Linus Groh < linusg @ serenityos . org >
* Copyright ( c ) 2023 , Luke Wilde < lukew @ serenityos . org >
* Copyright ( c ) 2024 , Shannon Booth < shannon @ serenityos . org >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <AK/Base64.h>
# include <AK/String.h>
# include <AK/Utf8View.h>
# include <AK/Vector.h>
2024-11-15 04:01:23 +13:00
# include <LibGC/Function.h>
2024-11-18 22:49:00 +13:00
# include <LibJS/Runtime/NativeFunction.h>
2024-11-28 04:06:02 +13:00
# include <LibWeb/HTML/PromiseRejectionEvent.h>
2024-11-08 16:35:16 +13:00
# include <LibWeb/HTML/Scripting/ExceptionReporter.h>
# include <LibWeb/HTML/StructuredSerialize.h>
# include <LibWeb/HTML/StructuredSerializeOptions.h>
# include <LibWeb/HTML/UniversalGlobalScope.h>
# include <LibWeb/HTML/Window.h>
# include <LibWeb/Infra/Strings.h>
# include <LibWeb/WebIDL/AbstractOperations.h>
# include <LibWeb/WebIDL/DOMException.h>
# include <LibWeb/WebIDL/ExceptionOr.h>
# include <LibWeb/WebIDL/Types.h>
namespace Web : : HTML {
UniversalGlobalScopeMixin : : ~ UniversalGlobalScopeMixin ( ) = default ;
2024-11-18 22:49:00 +13:00
void UniversalGlobalScopeMixin : : visit_edges ( GC : : Cell : : Visitor & visitor )
{
visitor . visit ( m_count_queuing_strategy_size_function ) ;
visitor . visit ( m_byte_length_queuing_strategy_size_function ) ;
2024-11-28 04:06:02 +13:00
visitor . ignore ( m_outstanding_rejected_promises_weak_set ) ;
2024-11-18 22:49:00 +13:00
}
2024-11-08 16:35:16 +13:00
// https://html.spec.whatwg.org/multipage/webappapis.html#dom-btoa
WebIDL : : ExceptionOr < String > UniversalGlobalScopeMixin : : btoa ( String const & data ) const
{
auto & vm = this_impl ( ) . vm ( ) ;
auto & realm = * vm . current_realm ( ) ;
// The btoa(data) method must throw an "InvalidCharacterError" DOMException if data contains any character whose code point is greater than U+00FF.
Vector < u8 > byte_string ;
byte_string . ensure_capacity ( data . bytes ( ) . size ( ) ) ;
for ( u32 code_point : Utf8View ( data ) ) {
if ( code_point > 0xff )
2025-08-07 19:31:52 -04:00
return WebIDL : : InvalidCharacterError : : create ( realm , " Data contains characters outside the range U+0000 and U+00FF " _utf16 ) ;
2024-11-08 16:35:16 +13:00
byte_string . append ( code_point ) ;
}
// Otherwise, the user agent must convert data to a byte sequence whose nth byte is the eight-bit representation of the nth code point of data,
// and then must apply forgiving-base64 encode to that byte sequence and return the result.
return TRY_OR_THROW_OOM ( vm , encode_base64 ( byte_string . span ( ) ) ) ;
}
// https://html.spec.whatwg.org/multipage/webappapis.html#dom-atob
WebIDL : : ExceptionOr < String > UniversalGlobalScopeMixin : : atob ( String const & data ) const
{
auto & vm = this_impl ( ) . vm ( ) ;
auto & realm = * vm . current_realm ( ) ;
// 1. Let decodedData be the result of running forgiving-base64 decode on data.
auto decoded_data = decode_base64 ( data ) ;
// 2. If decodedData is failure, then throw an "InvalidCharacterError" DOMException.
if ( decoded_data . is_error ( ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : InvalidCharacterError : : create ( realm , " Input string is not valid base64 data " _utf16 ) ;
2024-11-08 16:35:16 +13:00
// 3. Return decodedData.
// decode_base64() returns a byte buffer. LibJS uses UTF-8 for strings. Use isomorphic decoding to convert bytes to UTF-8.
return Infra : : isomorphic_decode ( decoded_data . value ( ) ) ;
}
// https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-queuemicrotask
void UniversalGlobalScopeMixin : : queue_microtask ( WebIDL : : CallbackType & callback )
{
auto & vm = this_impl ( ) . vm ( ) ;
auto & realm = * vm . current_realm ( ) ;
2024-11-15 04:01:23 +13:00
GC : : Ptr < DOM : : Document > document ;
2024-11-08 16:35:16 +13:00
if ( is < Window > ( this_impl ( ) ) )
document = & static_cast < Window & > ( this_impl ( ) ) . associated_document ( ) ;
2024-12-05 10:05:10 +00:00
// The queueMicrotask(callback) method must queue a microtask to invoke callback with « » and "report".
HTML : : queue_a_microtask ( document , GC : : create_function ( realm . heap ( ) , [ & callback ] {
2025-04-15 20:56:03 -04:00
( void ) WebIDL : : invoke_callback ( callback , { } , WebIDL : : ExceptionBehavior : : Report , { } ) ;
2024-11-08 16:35:16 +13:00
} ) ) ;
}
// https://html.spec.whatwg.org/multipage/structured-data.html#dom-structuredclone
WebIDL : : ExceptionOr < JS : : Value > UniversalGlobalScopeMixin : : structured_clone ( JS : : Value value , StructuredSerializeOptions const & options ) const
{
2025-04-17 09:00:36 -04:00
auto & realm = HTML : : relevant_realm ( this_impl ( ) ) ;
2024-11-08 16:35:16 +13:00
// 1. Let serialized be ? StructuredSerializeWithTransfer(value, options["transfer"]).
2025-04-17 09:00:36 -04:00
auto serialized = TRY ( structured_serialize_with_transfer ( realm . vm ( ) , value , options . transfer ) ) ;
2024-11-08 16:35:16 +13:00
// 2. Let deserializeRecord be ? StructuredDeserializeWithTransfer(serialized, this's relevant realm).
2025-04-17 09:00:36 -04:00
auto deserialized = TRY ( structured_deserialize_with_transfer ( serialized , realm ) ) ;
2024-11-08 16:35:16 +13:00
// 3. Return deserializeRecord.[[Deserialized]].
2025-04-17 09:00:36 -04:00
return deserialized . deserialized ;
2024-11-08 16:35:16 +13:00
}
2024-11-18 22:49:00 +13:00
// https://streams.spec.whatwg.org/#count-queuing-strategy-size-function
GC : : Ref < WebIDL : : CallbackType > UniversalGlobalScopeMixin : : count_queuing_strategy_size_function ( )
{
auto & realm = HTML : : relevant_realm ( this_impl ( ) ) ;
if ( ! m_count_queuing_strategy_size_function ) {
// 1. Let steps be the following steps:
auto steps = [ ] ( auto const & ) {
// 1. Return 1.
return 1.0 ;
} ;
// 2. Let F be ! CreateBuiltinFunction(steps, 0, "size", « », globalObject’ s relevant Realm).
2025-08-02 19:27:29 -04:00
auto function = JS : : NativeFunction : : create ( realm , move ( steps ) , 0 , " size " _utf16_fly_string , & realm ) ;
2024-11-18 22:49:00 +13:00
// 3. Set globalObject’ s count queuing strategy size function to a Function that represents a reference to F, with callback context equal to globalObject’ s relevant settings object.
2024-11-19 00:38:06 +13:00
// FIXME: Update spec comment to pass globalObject's relevant realm once Streams spec is updated for ShadowRealm spec
m_count_queuing_strategy_size_function = realm . create < WebIDL : : CallbackType > ( * function , realm ) ;
2024-11-18 22:49:00 +13:00
}
return GC : : Ref { * m_count_queuing_strategy_size_function } ;
}
// https://streams.spec.whatwg.org/#byte-length-queuing-strategy-size-function
GC : : Ref < WebIDL : : CallbackType > UniversalGlobalScopeMixin : : byte_length_queuing_strategy_size_function ( )
{
auto & realm = HTML : : relevant_realm ( this_impl ( ) ) ;
if ( ! m_byte_length_queuing_strategy_size_function ) {
// 1. Let steps be the following steps, given chunk:
auto steps = [ ] ( JS : : VM & vm ) {
auto chunk = vm . argument ( 0 ) ;
// 1. Return ? GetV(chunk, "byteLength").
return chunk . get ( vm , vm . names . byteLength ) ;
} ;
// 2. Let F be ! CreateBuiltinFunction(steps, 1, "size", « », globalObject’ s relevant Realm).
2025-08-02 19:27:29 -04:00
auto function = JS : : NativeFunction : : create ( realm , move ( steps ) , 1 , " size " _utf16_fly_string , & realm ) ;
2024-11-18 22:49:00 +13:00
// 3. Set globalObject’ s byte length queuing strategy size function to a Function that represents a reference to F, with callback context equal to globalObject’ s relevant settings object.
2024-11-19 00:38:06 +13:00
// FIXME: Update spec comment to pass globalObject's relevant realm once Streams spec is updated for ShadowRealm spec
m_byte_length_queuing_strategy_size_function = realm . create < WebIDL : : CallbackType > ( * function , realm ) ;
2024-11-18 22:49:00 +13:00
}
return GC : : Ref { * m_byte_length_queuing_strategy_size_function } ;
}
2024-11-28 04:06:02 +13:00
void UniversalGlobalScopeMixin : : push_onto_outstanding_rejected_promises_weak_set ( JS : : Promise * promise )
{
m_outstanding_rejected_promises_weak_set . append ( promise ) ;
}
bool UniversalGlobalScopeMixin : : remove_from_outstanding_rejected_promises_weak_set ( JS : : Promise * promise )
{
return m_outstanding_rejected_promises_weak_set . remove_first_matching ( [ & ] ( JS : : Promise * promise_in_set ) {
return promise = = promise_in_set ;
} ) ;
}
void UniversalGlobalScopeMixin : : push_onto_about_to_be_notified_rejected_promises_list ( GC : : Ref < JS : : Promise > promise )
{
m_about_to_be_notified_rejected_promises_list . append ( GC : : make_root ( promise ) ) ;
}
bool UniversalGlobalScopeMixin : : remove_from_about_to_be_notified_rejected_promises_list ( GC : : Ref < JS : : Promise > promise )
{
return m_about_to_be_notified_rejected_promises_list . remove_first_matching ( [ & ] ( auto & promise_in_list ) {
return promise = = promise_in_list ;
} ) ;
}
// https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises
void UniversalGlobalScopeMixin : : notify_about_rejected_promises ( Badge < EventLoop > )
{
auto & realm = this_impl ( ) . realm ( ) ;
// 1. Let list be a copy of settings object's about-to-be-notified rejected promises list.
auto list = m_about_to_be_notified_rejected_promises_list ;
// 2. If list is empty, return.
if ( list . is_empty ( ) )
return ;
// 3. Clear settings object's about-to-be-notified rejected promises list.
m_about_to_be_notified_rejected_promises_list . clear ( ) ;
// 4. Let global be settings object's global object.
2024-12-03 02:37:33 +13:00
auto & global = this_impl ( ) ;
2024-11-28 04:06:02 +13:00
// 5. Queue a global task on the DOM manipulation task source given global to run the following substep:
queue_global_task ( Task : : Source : : DOMManipulation , global , GC : : create_function ( realm . heap ( ) , [ this , & global , list = move ( list ) ] {
auto & realm = global . realm ( ) ;
// 1. For each promise p in list:
for ( auto const & promise : list ) {
// 1. If p's [[PromiseIsHandled]] internal slot is true, continue to the next iteration of the loop.
if ( promise - > is_handled ( ) )
continue ;
// 2. Let notHandled be the result of firing an event named unhandledrejection at global, using PromiseRejectionEvent, with the cancelable attribute initialized to true,
// the promise attribute initialized to p, and the reason attribute initialized to the value of p's [[PromiseResult]] internal slot.
PromiseRejectionEventInit event_init {
{
. bubbles = false ,
. cancelable = true ,
. composed = false ,
} ,
// Sadly we can't use .promise and .reason here, as we can't use the designator on the initialization of DOM::EventInit above.
/* .promise = */ * promise ,
/* .reason = */ promise - > result ( ) ,
} ;
auto promise_rejection_event = PromiseRejectionEvent : : create ( realm , HTML : : EventNames : : unhandledrejection , event_init ) ;
bool not_handled = global . dispatch_event ( * promise_rejection_event ) ;
// 3. If notHandled is false, then the promise rejection is handled. Otherwise, the promise rejection is not handled.
// 4. If p's [[PromiseIsHandled]] internal slot is false, add p to settings object's outstanding rejected promises weak set.
if ( ! promise - > is_handled ( ) )
m_outstanding_rejected_promises_weak_set . append ( * promise ) ;
// This algorithm results in promise rejections being marked as handled or not handled. These concepts parallel handled and not handled script errors.
// If a rejection is still not handled after this, then the rejection may be reported to a developer console.
if ( not_handled )
HTML : : report_exception_to_console ( promise - > result ( ) , realm , ErrorInPromise : : Yes ) ;
}
} ) ) ;
}
2024-11-08 16:35:16 +13:00
}