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 {
2025-04-11 12:13:32 -04:00
// https://w3ctag.github.io/promises-guide/#should-promise-call
static GC : : Ref < WebIDL : : Promise > promise_call ( JS : : Realm & realm , JS : : ThrowCompletionOr < JS : : Value > result )
{
// If the developer supplies you with a function that you expect to return a promise, you should also allow it to
// return a thenable or non-promise value, or even throw an exception, and treat all these cases as if they had
// returned an analogous promise. This should be done by converting the returned value to a promise, as if by using
// Promise.resolve(), and catching thrown exceptions and converting those into a promise as if by using
// Promise.reject(). We call this "promise-calling" the function.
if ( result . is_error ( ) )
return WebIDL : : create_rejected_promise ( realm , result . error_value ( ) ) ;
return WebIDL : : create_resolved_promise ( realm , result . release_value ( ) ) ;
}
2022-11-02 18:02:19 +00:00
// https://w3c.github.io/webdriver/#dfn-execute-a-function-body
2025-02-17 13:58:21 -05:00
static JS : : ThrowCompletionOr < JS : : Value > execute_a_function_body ( HTML : : BrowsingContext const & browsing_context , StringView 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
// 2. Let environment settings be the environment settings object for window.
2024-10-31 08:03:47 -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.
2024-10-31 08:03:47 -04:00
auto & realm = environment_settings . realm ( ) ;
auto & global_scope = realm . global_environment ( ) ;
2022-11-02 18:02:19 +00:00
2025-04-11 12:57:49 -04:00
// FIXME: This does not handle scripts which contain `await` statements. It is not as as simple as declaring this
// function async, unfortunately. See: https://github.com/w3c/webdriver/issues/1436
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.
2025-04-07 21:08:55 +02:00
auto function = JS : : ECMAScriptFunctionObject : : create_from_function_node (
function_expression ,
2025-08-02 19:27:29 -04:00
Utf16FlyString { } ,
2025-04-07 21:08:55 +02:00
realm ,
& global_scope ,
nullptr ) ;
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.
2025-04-27 11:53:11 +02:00
auto completion = JS : : call ( realm . vm ( ) , * function , 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 ;
}
2025-04-11 12:55:24 -04:00
static void fire_completion_when_resolved ( GC : : Ref < WebIDL : : Promise > promise , GC : : Ref < HeapTimer > timer , GC : : Ref < OnScriptComplete > on_complete )
{
auto reaction_steps = GC : : create_function ( promise - > heap ( ) , [ promise , timer , on_complete ] ( JS : : Value ) - > WebIDL : : ExceptionOr < JS : : Value > {
if ( timer - > is_timed_out ( ) )
return JS : : js_undefined ( ) ;
timer - > stop ( ) ;
auto const & underlying_promise = as < JS : : Promise > ( * promise - > promise ( ) ) ;
on_complete - > function ( ) ( { underlying_promise . state ( ) , underlying_promise . result ( ) } ) ;
return JS : : js_undefined ( ) ;
} ) ;
WebIDL : : react_to_promise ( promise , reaction_steps , reaction_steps ) ;
}
2025-02-17 13:58:21 -05:00
void execute_script ( HTML : : BrowsingContext const & browsing_context , String body , GC : : RootVector < JS : : Value > arguments , Optional < u64 > const & timeout_ms , GC : : Ref < 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-11-14 05:50:17 +13:00
auto timer = realm . create < HeapTimer > ( ) ;
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-11-15 04:01:23 +13:00
timer - > start ( timeout_ms . value ( ) , GC : : create_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-11-15 04:01:23 +13:00
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( GC : : create_function ( realm . heap ( ) , [ & realm , & browsing_context , promise , body = move ( body ) , arguments = move ( arguments ) ] ( ) mutable {
2025-04-11 12:13:32 -04:00
HTML : : TemporaryExecutionContext execution_context { realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes } ;
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.
2025-04-11 12:13:32 -04:00
auto script_promise = promise_call ( realm , execute_a_function_body ( browsing_context , body , arguments ) ) ;
WebIDL : : react_to_promise ( script_promise ,
// 2. Upon fulfillment of scriptPromise with value v, resolve promise with value v.
GC : : create_function ( realm . heap ( ) , [ & realm , promise ] ( JS : : Value value ) - > WebIDL : : ExceptionOr < JS : : Value > {
HTML : : TemporaryExecutionContext execution_context { realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes } ;
WebIDL : : resolve_promise ( realm , promise , value ) ;
return JS : : js_undefined ( ) ;
} ) ,
// 3. Upon rejection of scriptPromise with value r, reject promise with value r.
GC : : create_function ( realm . heap ( ) , [ & realm , promise ] ( JS : : Value reason ) - > WebIDL : : ExceptionOr < JS : : Value > {
HTML : : TemporaryExecutionContext execution_context { realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes } ;
WebIDL : : reject_promise ( realm , promise , reason ) ;
return JS : : js_undefined ( ) ;
} ) ) ;
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.
2025-04-11 12:55:24 -04:00
fire_completion_when_resolved ( promise , timer , on_complete ) ;
2022-11-02 18:02:19 +00:00
}
2024-12-12 10:24:50 +00:00
// https://w3c.github.io/webdriver/#execute-async-script
2025-02-17 13:58:21 -05:00
void execute_async_script ( HTML : : BrowsingContext const & browsing_context , String body , GC : : RootVector < JS : : Value > arguments , Optional < u64 > const & timeout_ms , GC : : Ref < 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-11-14 05:50:17 +13:00
auto timer = realm . create < HeapTimer > ( ) ;
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-11-15 04:01:23 +13:00
timer - > start ( timeout_ms . value ( ) , GC : : create_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.
2025-04-11 12:55:24 -04:00
auto promise = WebIDL : : create_promise ( realm ) ;
2022-11-02 18:02:19 +00:00
2024-08-30 22:36:46 +01:00
// 8. Run the following substeps in parallel:
2025-04-11 12:55:24 -04:00
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( GC : : create_function ( realm . heap ( ) , [ & vm , & realm , & browsing_context , promise , body = move ( body ) , arguments = move ( arguments ) ] ( ) mutable {
HTML : : TemporaryExecutionContext execution_context { realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes } ;
2024-09-13 07:42:24 -04:00
2022-11-02 18:02:19 +00:00
// 1. Let resolvingFunctions be CreateResolvingFunctions(promise).
2025-04-11 12:55:24 -04:00
auto resolving_functions = as < JS : : Promise > ( * promise - > promise ( ) ) . create_resolving_functions ( ) ;
2022-11-02 18:02:19 +00:00
// 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
2024-12-12 10:24:50 +00:00
// 3. Let scriptResult be the result of 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-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 ( ) ) {
2025-04-11 12:55:24 -04:00
WebIDL : : reject_promise ( realm , promise , script_result . error_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 ( ) ) {
2025-04-11 12:55:24 -04:00
WebIDL : : reject_promise ( realm , promise , then . error_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]]).
2025-04-11 12:55:24 -04:00
auto script_promise = WebIDL : : create_resolved_promise ( realm , script_result . value ( ) ) ;
2022-11-02 18:02:19 +00:00
2025-04-11 12:55:24 -04:00
WebIDL : : react_to_promise ( script_promise ,
// 10. Upon fulfillment of scriptPromise with value v, resolve promise with value v.
GC : : create_function ( realm . heap ( ) , [ & realm , promise ] ( JS : : Value value ) - > WebIDL : : ExceptionOr < JS : : Value > {
HTML : : TemporaryExecutionContext execution_context { realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes } ;
WebIDL : : resolve_promise ( realm , promise , value ) ;
return JS : : js_undefined ( ) ;
} ) ,
2022-11-02 18:02:19 +00:00
2025-04-11 12:55:24 -04:00
// 11. Upon rejection of scriptPromise with value r, reject promise with value r.
GC : : create_function ( realm . heap ( ) , [ & realm , promise ] ( JS : : Value reason ) - > WebIDL : : ExceptionOr < JS : : Value > {
HTML : : TemporaryExecutionContext execution_context { realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes } ;
WebIDL : : reject_promise ( realm , promise , reason ) ;
return JS : : js_undefined ( ) ;
} ) ) ;
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.
2025-04-11 12:55:24 -04:00
fire_completion_when_resolved ( promise , timer , on_complete ) ;
2022-11-02 18:02:19 +00:00
}
}