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 >
2024-11-02 14:29:37 -04:00
* Copyright ( c ) 2024 , Tim Flynn < trflynn89 @ ladybird . org >
2022-11-02 18:02:19 +00:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2022-11-23 13:28:01 +01:00
# include <LibJS/Parser.h>
2022-11-02 18:02:19 +00:00
# include <LibJS/Runtime/ECMAScriptFunctionObject.h>
# include <LibJS/Runtime/GlobalEnvironment.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/PromiseConstructor.h>
# include <LibWeb/DOM/Document.h>
# include <LibWeb/HTML/BrowsingContext.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-08-30 22:36:46 +01:00
# include <LibWeb/Platform/EventLoopPlugin.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 {
// 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 )
{
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
2024-11-01 10:17:42 -04:00
auto source_text = ByteString : : formatted (
R " ~~~(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-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 ] ( ) {
2024-11-02 14:29:37 -04:00
on_complete - > function ( ) ( { . state = JS : : Promise : : State : : Pending } ) ;
2024-10-25 11:02:31 -04:00
} ) ) ;
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-11-02 14:29:37 -04:00
auto reaction_steps = JS : : create_heap_function ( vm . heap ( ) , [ 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 ( ) ) } ;
2024-11-02 14:29:37 -04:00
on_complete - > function ( ) ( { promise_promise - > state ( ) , promise_promise - > result ( ) } ) ;
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 ] ( ) {
2024-11-02 14:29:37 -04:00
on_complete - > function ( ) ( { . state = JS : : Promise : : State : : Pending } ) ;
2024-10-25 11:02:31 -04:00
} ) ) ;
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-11-02 14:29:37 -04:00
auto reaction_steps = JS : : create_heap_function ( vm . heap ( ) , [ 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-11-02 14:29:37 -04:00
on_complete - > function ( ) ( { promise - > state ( ) , promise - > result ( ) } ) ;
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
}
}