2022-11-02 18:02:19 +00:00
/*
2023-04-13 00:47:15 +02:00
* Copyright ( c ) 2022 - 2023 , Linus Groh < linusg @ serenityos . org >
2022-11-02 18:02:19 +00:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <AK/JsonArray.h>
# include <AK/JsonObject.h>
# include <AK/JsonValue.h>
# include <AK/NumericLimits.h>
# include <AK/ScopeGuard.h>
# include <AK/Time.h>
# include <AK/Variant.h>
2022-11-23 13:28:01 +01:00
# include <LibJS/Parser.h>
2022-11-02 18:02:19 +00:00
# include <LibJS/Runtime/Array.h>
# include <LibJS/Runtime/ECMAScriptFunctionObject.h>
# include <LibJS/Runtime/GlobalEnvironment.h>
# include <LibJS/Runtime/JSONObject.h>
2024-10-29 14:12:13 -04:00
# include <LibJS/Runtime/ObjectEnvironment.h>
2022-11-02 18:02:19 +00:00
# include <LibJS/Runtime/Promise.h>
# include <LibJS/Runtime/PromiseConstructor.h>
# include <LibWeb/DOM/Document.h>
# include <LibWeb/DOM/HTMLCollection.h>
# include <LibWeb/DOM/NodeList.h>
# include <LibWeb/FileAPI/FileList.h>
# include <LibWeb/HTML/BrowsingContext.h>
# include <LibWeb/HTML/HTMLOptionsCollection.h>
# include <LibWeb/HTML/Scripting/Environments.h>
2023-07-06 07:43:23 -04:00
# include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
2022-11-02 18:02:19 +00:00
# include <LibWeb/HTML/Window.h>
2024-02-04 03:44:19 -07:00
# include <LibWeb/HTML/WindowProxy.h>
2022-11-02 18:02:19 +00:00
# include <LibWeb/Page/Page.h>
2024-08-30 22:36:46 +01:00
# include <LibWeb/Platform/EventLoopPlugin.h>
2023-04-20 17:41:32 +01:00
# include <LibWeb/WebDriver/Contexts.h>
2024-09-13 19:41:31 -04:00
# include <LibWeb/WebDriver/ElementReference.h>
2022-11-02 18:02:19 +00:00
# include <LibWeb/WebDriver/ExecuteScript.h>
2024-10-25 11:02:31 -04:00
# include <LibWeb/WebDriver/HeapTimer.h>
2022-11-02 18:02:19 +00:00
namespace Web : : WebDriver {
2023-01-14 16:34:44 -07:00
# define TRY_OR_JS_ERROR(expression) \
( { \
2023-02-09 13:27:43 -05:00
auto & & _temporary_result = ( expression ) ; \
2023-01-14 16:34:44 -07:00
if ( _temporary_result . is_error ( ) ) [[unlikely]] \
return ExecuteScriptResultType : : JavaScriptError ; \
static_assert ( ! : : AK : : Detail : : IsLvalueReference < decltype ( _temporary_result . release_value ( ) ) > , \
" Do not return a reference from a fallible expression " ) ; \
_temporary_result . release_value ( ) ; \
2022-11-02 18:02:19 +00:00
} )
2024-10-24 13:23:38 -04:00
static ErrorOr < JsonValue , ExecuteScriptResultType > internal_json_clone_algorithm ( JS : : Realm & , HTML : : BrowsingContext const & , JS : : Value , HashTable < JS : : Object * > & seen ) ;
static ErrorOr < JsonValue , ExecuteScriptResultType > clone_an_object ( JS : : Realm & , HTML : : BrowsingContext const & , JS : : Object & , HashTable < JS : : Object * > & seen , auto const & clone_algorithm ) ;
2022-11-02 18:02:19 +00:00
// https://w3c.github.io/webdriver/#dfn-collection
static bool is_collection ( JS : : Object const & value )
{
// A collection is an Object that implements the Iterable interface, and whose:
return (
// - initial value of the toString own property is "Arguments"
value . has_parameter_map ( )
// - instance of Array
| | is < JS : : Array > ( value )
// - instance of FileList
| | is < FileAPI : : FileList > ( value )
// - instance of HTMLAllCollection
| | false // FIXME
// - instance of HTMLCollection
| | is < DOM : : HTMLCollection > ( value )
// - instance of HTMLFormControlsCollection
| | false // FIXME
// - instance of HTMLOptionsCollection
| | is < HTML : : HTMLOptionsCollection > ( value )
// - instance of NodeList
| | is < DOM : : NodeList > ( value ) ) ;
}
// https://w3c.github.io/webdriver/#dfn-json-clone
2024-10-24 13:23:38 -04:00
static ErrorOr < JsonValue , ExecuteScriptResultType > json_clone ( JS : : Realm & realm , HTML : : BrowsingContext const & browsing_context , JS : : Value value )
2022-11-02 18:02:19 +00:00
{
// To perform a JSON clone return the result of calling the internal JSON clone algorithm with arguments value and an empty List.
auto seen = HashTable < JS : : Object * > { } ;
2024-10-24 13:23:38 -04:00
return internal_json_clone_algorithm ( realm , browsing_context , value , seen ) ;
2022-11-02 18:02:19 +00:00
}
// https://w3c.github.io/webdriver/#dfn-internal-json-clone-algorithm
2024-10-24 13:23:38 -04:00
static ErrorOr < JsonValue , ExecuteScriptResultType > internal_json_clone_algorithm ( JS : : Realm & realm , HTML : : BrowsingContext const & browsing_context , JS : : Value value , HashTable < JS : : Object * > & seen )
2022-11-02 18:02:19 +00:00
{
auto & vm = realm . vm ( ) ;
// When required to run the internal JSON clone algorithm with arguments value and seen, a remote end must return the value of the first matching statement, matching on value:
// -> undefined
// -> null
if ( value . is_nullish ( ) ) {
// Success with data null.
return JsonValue { } ;
}
// -> type Boolean
// -> type Number
// -> type String
// Success with data value.
if ( value . is_boolean ( ) )
return JsonValue { value . as_bool ( ) } ;
if ( value . is_number ( ) )
return JsonValue { value . as_double ( ) } ;
if ( value . is_string ( ) )
2023-12-16 17:49:34 +03:30
return JsonValue { value . as_string ( ) . byte_string ( ) } ;
2022-11-02 18:02:19 +00:00
// NOTE: BigInt and Symbol not mentioned anywhere in the WebDriver spec, as it references ES5.
// It assumes that all primitives are handled above, and the value is an object for the remaining steps.
if ( value . is_bigint ( ) | | value . is_symbol ( ) )
return ExecuteScriptResultType : : JavaScriptError ;
2023-04-20 17:41:32 +01:00
// FIXME: -> a collection
2024-09-13 19:41:31 -04:00
// -> instance of element
if ( value . is_object ( ) & & is < DOM : : Element > ( value . as_object ( ) ) ) {
auto const & element = static_cast < DOM : : Element const & > ( value . as_object ( ) ) ;
// If the element is stale, return error with error code stale element reference.
if ( is_element_stale ( element ) ) {
return ExecuteScriptResultType : : StaleElement ;
}
// Otherwise:
else {
// 1. Let reference be the web element reference object for session and value.
2024-10-24 13:23:38 -04:00
auto reference = web_element_reference_object ( browsing_context , element ) ;
2024-09-13 19:41:31 -04:00
// 2. Return success with data reference.
return reference ;
}
}
2023-04-20 17:41:32 +01:00
// FIXME: -> instance of shadow root
// -> a WindowProxy object
if ( is < HTML : : WindowProxy > ( value . as_object ( ) ) ) {
auto const & window_proxy = static_cast < HTML : : WindowProxy & > ( value . as_object ( ) ) ;
2024-02-05 12:34:29 -05:00
// If the associated browsing context of the WindowProxy object in value has been destroyed, return error with
2023-04-20 17:41:32 +01:00
// error code stale element reference.
2024-02-05 12:34:29 -05:00
if ( window_proxy . associated_browsing_context ( ) - > has_navigable_been_destroyed ( ) )
2023-04-20 17:41:32 +01:00
return ExecuteScriptResultType : : BrowsingContextDiscarded ;
// Otherwise return success with data set to WindowProxy reference object for value.
return window_proxy_reference_object ( window_proxy ) ;
}
2022-11-02 18:02:19 +00:00
// -> has an own property named "toJSON" that is a Function
auto to_json = value . as_object ( ) . get_without_side_effects ( vm . names . toJSON ) ;
if ( to_json . is_function ( ) ) {
// Return success with the value returned by Function.[[Call]](toJSON) with value as the this value.
auto to_json_result = TRY_OR_JS_ERROR ( to_json . as_function ( ) . internal_call ( value , JS : : MarkedVector < JS : : Value > { vm . heap ( ) } ) ) ;
if ( ! to_json_result . is_string ( ) )
return ExecuteScriptResultType : : JavaScriptError ;
2023-12-16 17:49:34 +03:30
return to_json_result . as_string ( ) . byte_string ( ) ;
2022-11-02 18:02:19 +00:00
}
// -> Otherwise
// 1. If value is in seen, return error with error code javascript error.
if ( seen . contains ( & value . as_object ( ) ) )
return ExecuteScriptResultType : : JavaScriptError ;
// 2. Append value to seen.
seen . set ( & value . as_object ( ) ) ;
ScopeGuard remove_seen { [ & ] {
// 4. Remove the last element of seen.
seen . remove ( & value . as_object ( ) ) ;
} } ;
// 3. Let result be the value of running the clone an object algorithm with arguments value and seen, and the internal JSON clone algorithm as the clone algorithm.
2024-10-24 13:23:38 -04:00
auto result = TRY ( clone_an_object ( realm , browsing_context , value . as_object ( ) , seen , internal_json_clone_algorithm ) ) ;
2022-11-02 18:02:19 +00:00
// 5. Return result.
return result ;
}
// https://w3c.github.io/webdriver/#dfn-clone-an-object
2024-10-24 13:23:38 -04:00
static ErrorOr < JsonValue , ExecuteScriptResultType > clone_an_object ( JS : : Realm & realm , HTML : : BrowsingContext const & browsing_context , JS : : Object & value , HashTable < JS : : Object * > & seen , auto const & clone_algorithm )
2022-11-02 18:02:19 +00:00
{
auto & vm = realm . vm ( ) ;
// 1. Let result be the value of the first matching statement, matching on value:
auto get_result = [ & ] ( ) - > ErrorOr < Variant < JsonArray , JsonObject > , ExecuteScriptResultType > {
// -> a collection
if ( is_collection ( value ) ) {
// A new Array which length property is equal to the result of getting the property length of value.
auto length_property = TRY_OR_JS_ERROR ( value . internal_get_own_property ( vm . names . length ) ) ;
if ( ! length_property - > value . has_value ( ) )
return ExecuteScriptResultType : : JavaScriptError ;
auto length = TRY_OR_JS_ERROR ( length_property - > value - > to_length ( vm ) ) ;
if ( length > NumericLimits < u32 > : : max ( ) )
return ExecuteScriptResultType : : JavaScriptError ;
auto array = JsonArray { } ;
for ( size_t i = 0 ; i < length ; + + i )
2023-04-17 15:38:27 +10:00
array . must_append ( JsonValue { } ) ;
2022-11-02 18:02:19 +00:00
return array ;
}
// -> Otherwise
else {
// A new Object.
return JsonObject { } ;
}
} ;
auto result = TRY ( get_result ( ) ) ;
// 2. For each enumerable own property in value, run the following substeps:
for ( auto & key : MUST ( value . Object : : internal_own_property_keys ( ) ) ) {
// 1. Let name be the name of the property.
auto name = MUST ( JS : : PropertyKey : : from_value ( vm , key ) ) ;
if ( ! value . storage_get ( name ) - > attributes . is_enumerable ( ) )
continue ;
// 2. Let source property value be the result of getting a property named name from value. If doing so causes script to be run and that script throws an error, return error with error code javascript error.
auto source_property_value = TRY_OR_JS_ERROR ( value . internal_get_own_property ( name ) ) ;
if ( ! source_property_value . has_value ( ) | | ! source_property_value - > value . has_value ( ) )
continue ;
// 3. Let cloned property result be the result of calling the clone algorithm with arguments source property value and seen.
2024-10-24 13:23:38 -04:00
auto cloned_property_result = clone_algorithm ( realm , browsing_context , * source_property_value - > value , seen ) ;
2022-11-02 18:02:19 +00:00
// 4. If cloned property result is a success, set a property of result with name name and value equal to cloned property result’ s data.
if ( ! cloned_property_result . is_error ( ) ) {
result . visit (
[ & ] ( JsonArray & array ) {
// NOTE: If this was a JS array, only indexed properties would be serialized anyway.
if ( name . is_number ( ) )
2023-05-08 13:53:29 -07:00
array . set ( name . as_number ( ) , cloned_property_result . value ( ) ) ;
2022-11-02 18:02:19 +00:00
} ,
[ & ] ( JsonObject & object ) {
object . set ( name . to_string ( ) , cloned_property_result . value ( ) ) ;
} ) ;
}
// 5. Otherwise, return cloned property result.
else {
return cloned_property_result ;
}
}
return result . visit ( [ & ] ( auto const & value ) - > JsonValue { return value ; } ) ;
}
// https://w3c.github.io/webdriver/#dfn-execute-a-function-body
2024-10-29 14:12:13 -04:00
JS : : ThrowCompletionOr < JS : : Value > execute_a_function_body ( HTML : : BrowsingContext const & browsing_context , ByteString const & body , ReadonlySpan < JS : : Value > parameters )
2022-11-02 18:02:19 +00:00
{
// 1. Let window be the associated window of the current browsing context’ s active document.
2024-09-14 08:04:15 -04:00
auto window = browsing_context . active_document ( ) - > window ( ) ;
2022-11-02 18:02:19 +00:00
2024-10-29 14:12:13 -04:00
return execute_a_function_body ( * window , body , move ( parameters ) ) ;
}
// https://w3c.github.io/webdriver/#dfn-execute-a-function-body
JS : : ThrowCompletionOr < JS : : Value > execute_a_function_body ( HTML : : Window const & window , ByteString const & body , ReadonlySpan < JS : : Value > parameters , JS : : GCPtr < JS : : Object > environment_override_object )
{
// FIXME: If at any point during the algorithm a user prompt appears, immediately return Completion { [[Type]]: normal, [[Value]]: null, [[Target]]: empty }, but continue to run the other steps of this algorithm in parallel.
auto & realm = window . realm ( ) ;
2022-11-02 18:02:19 +00:00
// 2. Let environment settings be the environment settings object for window.
2024-10-29 14:12:13 -04:00
auto & environment_settings = Web : : HTML : : relevant_settings_object ( window ) ;
2022-11-02 18:02:19 +00:00
// 3. Let global scope be environment settings realm’ s global environment.
auto & global_scope = environment_settings . realm ( ) . global_environment ( ) ;
2024-10-29 14:12:13 -04:00
JS : : NonnullGCPtr < JS : : Environment > scope = global_scope ;
2022-11-02 18:02:19 +00:00
2024-10-29 14:12:13 -04:00
if ( environment_override_object )
scope = JS : : new_object_environment ( * environment_override_object , true , & global_scope ) ;
2022-11-02 18:02:19 +00:00
2023-12-16 17:49:34 +03:30
auto source_text = ByteString : : formatted ( " function() {{ {} }} " , body ) ;
2022-11-02 18:02:19 +00:00
auto parser = JS : : Parser { JS : : Lexer { source_text } } ;
auto function_expression = parser . parse_function_node < JS : : FunctionExpression > ( ) ;
// 4. If body is not parsable as a FunctionBody or if parsing detects an early error, return Completion { [[Type]]: normal, [[Value]]: null, [[Target]]: empty }.
if ( parser . has_errors ( ) )
return JS : : js_null ( ) ;
// 5. If body begins with a directive prologue that contains a use strict directive then let strict be true, otherwise let strict be false.
// NOTE: Handled in step 8 below.
2024-10-21 20:09:02 +13:00
// 6. Prepare to run a script with realm.
HTML : : prepare_to_run_script ( realm ) ;
2022-11-02 18:02:19 +00:00
// 7. Prepare to run a callback with environment settings.
2024-10-21 20:54:39 +13:00
HTML : : prepare_to_run_callback ( realm ) ;
2022-11-02 18:02:19 +00:00
// 8. Let function be the result of calling FunctionCreate, with arguments:
// kind
// Normal.
// list
// An empty List.
// body
// The result of parsing body above.
// global scope
// The result of parsing global scope above.
// strict
// The result of parsing strict above.
2024-10-29 14:12:13 -04:00
auto function = JS : : ECMAScriptFunctionObject : : create ( realm , " " , move ( source_text ) , function_expression - > body ( ) , function_expression - > parameters ( ) , function_expression - > function_length ( ) , function_expression - > local_variables_names ( ) , scope , nullptr , function_expression - > kind ( ) , function_expression - > is_strict_mode ( ) , function_expression - > parsing_insights ( ) ) ;
2022-11-02 18:02:19 +00:00
// 9. Let completion be Function.[[Call]](window, parameters) with function as the this value.
// NOTE: This is not entirely clear, but I don't think they mean actually passing `function` as
// the this value argument, but using it as the object [[Call]] is executed on.
2024-10-29 14:12:13 -04:00
auto completion = function - > internal_call ( & window , parameters ) ;
2022-11-02 18:02:19 +00:00
// 10. Clean up after running a callback with environment settings.
2024-10-21 20:54:39 +13:00
HTML : : clean_up_after_running_callback ( realm ) ;
2022-11-02 18:02:19 +00:00
2024-10-21 16:18:25 +13:00
// 11. Clean up after running a script with realm.
HTML : : clean_up_after_running_script ( realm ) ;
2022-11-02 18:02:19 +00:00
// 12. Return completion.
return completion ;
}
2024-10-25 11:02:31 -04:00
static ExecuteScriptResultSerialized create_timeout_result ( )
{
JsonObject error_object ;
error_object . set ( " name " , " Error " ) ;
error_object . set ( " message " , " Script Timeout " ) ;
2024-09-13 07:42:24 -04:00
2024-10-25 11:02:31 -04:00
return { ExecuteScriptResultType : : Timeout , move ( error_object ) } ;
}
2024-09-13 07:42:24 -04:00
2024-09-14 08:04:15 -04:00
void execute_script ( HTML : : BrowsingContext const & browsing_context , ByteString body , JS : : MarkedVector < JS : : Value > arguments , Optional < u64 > const & timeout_ms , JS : : NonnullGCPtr < OnScriptComplete > on_complete )
2022-11-02 18:02:19 +00:00
{
2024-09-14 08:04:15 -04:00
auto const * document = browsing_context . active_document ( ) ;
auto & realm = document - > realm ( ) ;
auto & vm = document - > vm ( ) ;
2022-11-02 18:02:19 +00:00
2024-08-30 22:36:46 +01:00
// 5. Let timer be a new timer.
2024-09-13 07:42:24 -04:00
auto timer = vm . heap ( ) . allocate < HeapTimer > ( realm ) ;
2024-08-30 22:36:46 +01:00
// 6. If timeout is not null:
if ( timeout_ms . has_value ( ) ) {
// 1. Start the timer with timer and timeout.
2024-10-25 11:02:31 -04:00
timer - > start ( timeout_ms . value ( ) , JS : : create_heap_function ( vm . heap ( ) , [ on_complete ] ( ) {
on_complete - > function ( ) ( create_timeout_result ( ) ) ;
} ) ) ;
2024-08-30 22:36:46 +01:00
}
2022-11-02 18:02:19 +00:00
2024-08-30 22:36:46 +01:00
// AD-HOC: An execution context is required for Promise creation hooks.
2024-10-24 20:39:18 +13:00
HTML : : TemporaryExecutionContext execution_context { realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes } ;
2024-08-30 22:36:46 +01:00
// 7. Let promise be a new Promise.
2024-10-25 12:38:19 -06:00
auto promise = WebIDL : : create_promise ( realm ) ;
2024-08-30 22:36:46 +01:00
// 8. Run the following substeps in parallel:
2024-10-24 20:39:18 +13:00
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( JS : : create_heap_function ( realm . heap ( ) , [ & realm , & browsing_context , promise , body = move ( body ) , arguments = move ( arguments ) ] ( ) mutable {
HTML : : TemporaryExecutionContext execution_context { realm } ;
2024-09-13 07:42:24 -04:00
2022-11-02 18:02:19 +00:00
// 1. Let scriptPromise be the result of promise-calling execute a function body, with arguments body and arguments.
2024-09-14 08:04:15 -04:00
auto script_result = execute_a_function_body ( browsing_context , body , move ( arguments ) ) ;
2022-11-02 18:02:19 +00:00
2024-10-25 12:38:19 -06:00
// FIXME: This isn't right, we should be reacting to this using WebIDL::react_to_promise()
2022-11-02 18:02:19 +00:00
// 2. Upon fulfillment of scriptPromise with value v, resolve promise with value v.
2024-08-30 22:36:46 +01:00
if ( script_result . has_value ( ) ) {
2024-10-25 12:38:19 -06:00
WebIDL : : resolve_promise ( realm , promise , script_result . release_value ( ) ) ;
2024-08-30 22:36:46 +01:00
}
2022-11-02 18:02:19 +00:00
// 3. Upon rejection of scriptPromise with value r, reject promise with value r.
2024-08-30 22:36:46 +01:00
if ( script_result . is_throw_completion ( ) ) {
2024-10-25 12:38:19 -06:00
WebIDL : : reject_promise ( realm , promise , * script_result . throw_completion ( ) . value ( ) ) ;
2024-08-30 22:36:46 +01:00
}
2024-10-31 02:39:29 +13:00
} ) ) ;
2024-08-30 22:36:46 +01:00
// 9. Wait until promise is resolved, or timer's timeout fired flag is set, whichever occurs first.
2024-10-24 13:23:38 -04:00
auto reaction_steps = JS : : create_heap_function ( vm . heap ( ) , [ & realm , & browsing_context , promise , timer , on_complete ] ( JS : : Value ) - > WebIDL : : ExceptionOr < JS : : Value > {
2024-09-13 07:42:24 -04:00
if ( timer - > is_timed_out ( ) )
return JS : : js_undefined ( ) ;
timer - > stop ( ) ;
2024-10-25 12:38:19 -06:00
auto promise_promise = JS : : NonnullGCPtr { verify_cast < JS : : Promise > ( * promise - > promise ( ) ) } ;
auto json_value_or_error = json_clone ( realm , browsing_context , promise_promise - > result ( ) ) ;
2024-09-13 07:42:24 -04:00
if ( json_value_or_error . is_error ( ) ) {
auto error_object = JsonObject { } ;
error_object . set ( " name " , " Error " ) ;
error_object . set ( " message " , " Could not clone result value " ) ;
on_complete - > function ( ) ( { ExecuteScriptResultType : : JavaScriptError , move ( error_object ) } ) ;
}
2024-08-30 22:36:46 +01:00
2024-09-13 07:42:24 -04:00
// 10. If promise is still pending and timer's timeout fired flag is set, return error with error code script timeout.
// NOTE: This is handled by the HeapTimer.
2024-08-30 22:36:46 +01:00
2024-09-13 07:42:24 -04:00
// 11. If promise is fulfilled with value v, let result be JSON clone with session and v, and return success with data result.
2024-10-25 12:38:19 -06:00
else if ( promise_promise - > state ( ) = = JS : : Promise : : State : : Fulfilled ) {
2024-09-13 07:42:24 -04:00
on_complete - > function ( ) ( { ExecuteScriptResultType : : PromiseResolved , json_value_or_error . release_value ( ) } ) ;
}
2024-08-30 22:36:46 +01:00
2024-09-13 07:42:24 -04:00
// 12. If promise is rejected with reason r, let result be JSON clone with session and r, and return error with error code javascript error and data result.
2024-10-25 12:38:19 -06:00
else if ( promise_promise - > state ( ) = = JS : : Promise : : State : : Rejected ) {
2024-09-13 07:42:24 -04:00
on_complete - > function ( ) ( { ExecuteScriptResultType : : PromiseRejected , json_value_or_error . release_value ( ) } ) ;
}
2024-08-30 22:36:46 +01:00
2024-09-13 07:42:24 -04:00
return JS : : js_undefined ( ) ;
} ) ;
2024-08-30 22:36:46 +01:00
2024-10-25 12:38:19 -06:00
WebIDL : : react_to_promise ( promise , reaction_steps , reaction_steps ) ;
2022-11-02 18:02:19 +00:00
}
2024-09-14 08:04:15 -04:00
void execute_async_script ( HTML : : BrowsingContext const & browsing_context , ByteString body , JS : : MarkedVector < JS : : Value > arguments , Optional < u64 > const & timeout_ms , JS : : NonnullGCPtr < OnScriptComplete > on_complete )
2022-11-02 18:02:19 +00:00
{
2024-09-14 08:04:15 -04:00
auto const * document = browsing_context . active_document ( ) ;
auto & realm = document - > realm ( ) ;
auto & vm = document - > vm ( ) ;
2022-11-02 18:02:19 +00:00
2024-08-30 22:36:46 +01:00
// 5. Let timer be a new timer.
2024-09-13 07:42:24 -04:00
auto timer = vm . heap ( ) . allocate < HeapTimer > ( realm ) ;
2024-08-30 22:36:46 +01:00
// 6. If timeout is not null:
if ( timeout_ms . has_value ( ) ) {
// 1. Start the timer with timer and timeout.
2024-10-25 11:02:31 -04:00
timer - > start ( timeout_ms . value ( ) , JS : : create_heap_function ( vm . heap ( ) , [ on_complete ] ( ) {
on_complete - > function ( ) ( create_timeout_result ( ) ) ;
} ) ) ;
2024-08-30 22:36:46 +01:00
}
2023-06-17 18:07:27 +02:00
2023-07-06 07:43:23 -04:00
// AD-HOC: An execution context is required for Promise creation hooks.
2024-10-24 20:39:18 +13:00
HTML : : TemporaryExecutionContext execution_context { realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes } ;
2023-05-30 15:23:46 +03:00
2024-08-30 22:36:46 +01:00
// 7. Let promise be a new Promise.
2023-05-30 15:23:46 +03:00
auto promise_capability = WebIDL : : create_promise ( realm ) ;
JS : : NonnullGCPtr promise { verify_cast < JS : : Promise > ( * promise_capability - > promise ( ) ) } ;
2022-11-02 18:02:19 +00:00
2024-08-30 22:36:46 +01:00
// 8. Run the following substeps in parallel:
2024-10-24 20:39:18 +13:00
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( JS : : create_heap_function ( realm . heap ( ) , [ & vm , & realm , & browsing_context , timer , promise_capability , promise , body = move ( body ) , arguments = move ( arguments ) ] ( ) mutable {
HTML : : TemporaryExecutionContext execution_context { realm } ;
2024-09-13 07:42:24 -04:00
2022-11-02 18:02:19 +00:00
// 1. Let resolvingFunctions be CreateResolvingFunctions(promise).
auto resolving_functions = promise - > create_resolving_functions ( ) ;
// 2. Append resolvingFunctions.[[Resolve]] to arguments.
2023-02-26 16:09:02 -07:00
arguments . append ( resolving_functions . resolve ) ;
2022-11-02 18:02:19 +00:00
// 3. Let result be the result of calling execute a function body, with arguments body and arguments.
// FIXME: 'result' -> 'scriptResult' (spec issue)
2024-09-14 08:04:15 -04:00
auto script_result = execute_a_function_body ( browsing_context , body , move ( arguments ) ) ;
2022-11-02 18:02:19 +00:00
2024-08-30 22:36:46 +01:00
// 4. If scriptResult.[[Type]] is not normal, then reject promise with value scriptResult.[[Value]], and abort these steps.
2022-11-02 18:02:19 +00:00
// NOTE: Prior revisions of this specification did not recognize the return value of the provided script.
// In order to preserve legacy behavior, the return value only influences the command if it is a
// "thenable" object or if determining this produces an exception.
2023-06-17 18:17:05 +02:00
if ( script_result . is_throw_completion ( ) ) {
promise - > reject ( * script_result . throw_completion ( ) . value ( ) ) ;
2023-05-30 15:23:46 +03:00
return ;
2023-06-17 18:17:05 +02:00
}
2022-11-02 18:02:19 +00:00
// 5. If Type(scriptResult.[[Value]]) is not Object, then abort these steps.
if ( ! script_result . value ( ) . is_object ( ) )
2023-05-30 15:23:46 +03:00
return ;
2022-11-02 18:02:19 +00:00
// 6. Let then be Get(scriptResult.[[Value]], "then").
auto then = script_result . value ( ) . as_object ( ) . get ( vm . names . then ) ;
// 7. If then.[[Type]] is not normal, then reject promise with value then.[[Value]], and abort these steps.
2023-06-17 18:17:05 +02:00
if ( then . is_throw_completion ( ) ) {
promise - > reject ( * then . throw_completion ( ) . value ( ) ) ;
2023-05-30 15:23:46 +03:00
return ;
2023-06-17 18:17:05 +02:00
}
2022-11-02 18:02:19 +00:00
// 8. If IsCallable(then.[[Type]]) is false, then abort these steps.
if ( ! then . value ( ) . is_function ( ) )
2023-05-30 15:23:46 +03:00
return ;
2022-11-02 18:02:19 +00:00
// 9. Let scriptPromise be PromiseResolve(Promise, scriptResult.[[Value]]).
2023-04-13 00:47:15 +02:00
auto script_promise_or_error = JS : : promise_resolve ( vm , realm . intrinsics ( ) . promise_constructor ( ) , script_result . value ( ) ) ;
2022-11-02 18:02:19 +00:00
if ( script_promise_or_error . is_throw_completion ( ) )
2023-05-30 15:23:46 +03:00
return ;
2022-11-02 18:02:19 +00:00
auto & script_promise = static_cast < JS : : Promise & > ( * script_promise_or_error . value ( ) ) ;
2024-10-31 01:06:56 +13:00
vm . custom_data ( ) - > spin_event_loop_until ( JS : : create_heap_function ( vm . heap ( ) , [ timer , & script_promise ] ( ) {
2024-09-13 07:42:24 -04:00
return timer - > is_timed_out ( ) | | script_promise . state ( ) ! = JS : : Promise : : State : : Pending ;
2024-10-31 01:06:56 +13:00
} ) ) ;
2022-11-02 18:02:19 +00:00
// 10. Upon fulfillment of scriptPromise with value v, resolve promise with value v.
if ( script_promise . state ( ) = = JS : : Promise : : State : : Fulfilled )
2023-05-30 15:23:46 +03:00
WebIDL : : resolve_promise ( realm , promise_capability , script_promise . result ( ) ) ;
2022-11-02 18:02:19 +00:00
// 11. Upon rejection of scriptPromise with value r, reject promise with value r.
if ( script_promise . state ( ) = = JS : : Promise : : State : : Rejected )
2023-05-30 15:23:46 +03:00
WebIDL : : reject_promise ( realm , promise_capability , script_promise . result ( ) ) ;
2024-10-31 02:39:29 +13:00
} ) ) ;
2022-11-02 18:02:19 +00:00
2024-08-30 22:36:46 +01:00
// 9. Wait until promise is resolved, or timer's timeout fired flag is set, whichever occurs first.
2024-10-24 13:23:38 -04:00
auto reaction_steps = JS : : create_heap_function ( vm . heap ( ) , [ & realm , & browsing_context , promise , timer , on_complete ] ( JS : : Value ) - > WebIDL : : ExceptionOr < JS : : Value > {
2024-09-13 07:42:24 -04:00
if ( timer - > is_timed_out ( ) )
return JS : : js_undefined ( ) ;
timer - > stop ( ) ;
2024-10-24 13:23:38 -04:00
auto json_value_or_error = json_clone ( realm , browsing_context , promise - > result ( ) ) ;
2024-09-13 07:42:24 -04:00
if ( json_value_or_error . is_error ( ) ) {
auto error_object = JsonObject { } ;
error_object . set ( " name " , " Error " ) ;
error_object . set ( " message " , " Could not clone result value " ) ;
on_complete - > function ( ) ( { ExecuteScriptResultType : : JavaScriptError , move ( error_object ) } ) ;
}
2023-05-30 15:23:46 +03:00
2024-09-13 07:42:24 -04:00
// 10. If promise is still pending and timer's timeout fired flag is set, return error with error code script timeout.
// NOTE: This is handled by the HeapTimer.
2023-06-17 18:07:27 +02:00
2024-09-13 07:42:24 -04:00
// 11. If promise is fulfilled with value v, let result be JSON clone with session and v, and return success with data result.
else if ( promise - > state ( ) = = JS : : Promise : : State : : Fulfilled ) {
on_complete - > function ( ) ( { ExecuteScriptResultType : : PromiseResolved , json_value_or_error . release_value ( ) } ) ;
}
2023-05-30 15:23:46 +03:00
2024-09-13 07:42:24 -04:00
// 12. If promise is rejected with reason r, let result be JSON clone with session and r, and return error with error code javascript error and data result.
else if ( promise - > state ( ) = = JS : : Promise : : State : : Rejected ) {
on_complete - > function ( ) ( { ExecuteScriptResultType : : PromiseRejected , json_value_or_error . release_value ( ) } ) ;
}
2024-08-30 22:36:46 +01:00
2024-09-13 07:42:24 -04:00
return JS : : js_undefined ( ) ;
} ) ;
2023-05-30 15:23:46 +03:00
2024-09-13 07:42:24 -04:00
WebIDL : : react_to_promise ( promise_capability , reaction_steps , reaction_steps ) ;
2022-11-02 18:02:19 +00:00
}
}