1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 10:28:10 +00:00

LibWeb/WebDriver: Support resolve with callback in execute_async_script

Before execute_async_script supported returning value from script to
/execute/async caller only if script returns promise that resolves into
returned value. This change fixes execute_async_script to also support
returning value to caller using callback that is always passed to
script as a last argument.
This commit is contained in:
Aliaksandr Kalenik 2023-05-30 15:23:46 +03:00 committed by Andreas Kling
parent 33815d550b
commit 857ceb215c

View file

@ -331,19 +331,21 @@ ExecuteScriptResultSerialized execute_async_script(Web::Page& page, DeprecatedSt
auto& vm = window->vm();
auto start = MonotonicTime::now();
// 4. Let promise be a new Promise.
auto promise = JS::Promise::create(realm);
// FIXME: 5 Run the following substeps in parallel:
auto result = [&] {
// NOTE: We need to push an execution context in order to make create_resolving_functions() succeed.
vm.push_execution_context(settings_object.realm_execution_context());
// 1. Let resolvingFunctions be CreateResolvingFunctions(promise).
auto resolving_functions = promise->create_resolving_functions();
// NOTE: We need to push an execution context in order to make create_resolving_functions() succeed.
vm.push_execution_context(settings_object.realm_execution_context());
ScopeGuard pop_guard = [&] {
VERIFY(&settings_object.realm_execution_context() == &vm.running_execution_context());
vm.pop_execution_context();
};
// 4. Let promise be a new Promise.
auto promise_capability = WebIDL::create_promise(realm);
JS::NonnullGCPtr promise { verify_cast<JS::Promise>(*promise_capability->promise()) };
// FIXME: 5 Run the following substeps in parallel:
[&] {
// 1. Let resolvingFunctions be CreateResolvingFunctions(promise).
auto resolving_functions = promise->create_resolving_functions();
// 2. Append resolvingFunctions.[[Resolve]] to arguments.
arguments.append(resolving_functions.resolve);
@ -357,27 +359,27 @@ ExecuteScriptResultSerialized execute_async_script(Web::Page& page, DeprecatedSt
// 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.
if (script_result.is_throw_completion())
return ExecuteScriptResult { ExecuteScriptResultType::PromiseRejected, *script_result.throw_completion().value() };
return;
// 5. If Type(scriptResult.[[Value]]) is not Object, then abort these steps.
if (!script_result.value().is_object())
return ExecuteScriptResult { ExecuteScriptResultType::PromiseResolved, JS::js_null() };
return;
// 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.
if (then.is_throw_completion())
return ExecuteScriptResult { ExecuteScriptResultType::PromiseRejected, *then.throw_completion().value() };
return;
// 8. If IsCallable(then.[[Type]]) is false, then abort these steps.
if (!then.value().is_function())
return ExecuteScriptResult { ExecuteScriptResultType::PromiseResolved, JS::js_null() };
return;
// 9. Let scriptPromise be PromiseResolve(Promise, scriptResult.[[Value]]).
auto script_promise_or_error = JS::promise_resolve(vm, realm.intrinsics().promise_constructor(), script_result.value());
if (script_promise_or_error.is_throw_completion())
return ExecuteScriptResult { ExecuteScriptResultType::PromiseRejected, *script_promise_or_error.throw_completion().value() };
return;
auto& script_promise = static_cast<JS::Promise&>(*script_promise_or_error.value());
vm.custom_data()->spin_event_loop_until([&] {
@ -390,26 +392,37 @@ ExecuteScriptResultSerialized execute_async_script(Web::Page& page, DeprecatedSt
// 10. Upon fulfillment of scriptPromise with value v, resolve promise with value v.
if (script_promise.state() == JS::Promise::State::Fulfilled)
return ExecuteScriptResult { ExecuteScriptResultType::PromiseResolved, script_promise.result() };
WebIDL::resolve_promise(realm, promise_capability, script_promise.result());
// 11. Upon rejection of scriptPromise with value r, reject promise with value r.
if (script_promise.state() == JS::Promise::State::Rejected)
return ExecuteScriptResult { ExecuteScriptResultType::PromiseRejected, script_promise.result() };
return ExecuteScriptResult { ExecuteScriptResultType::Timeout, script_promise.result() };
WebIDL::reject_promise(realm, promise_capability, script_promise.result());
}();
// 6. If promise is still pending and session script timeout milliseconds is reached, return error with error code script timeout.
// 7. Upon fulfillment of promise with value v, let result be a JSON clone of v, and return success with data result.
// 8. Upon rejection of promise with reason r, let result be a JSON clone of r, and return error with error code javascript error and data result.
auto json_value_or_error = json_clone(realm, result.value);
// FIXME: 6. If promise is still pending and session script timeout milliseconds is reached, return error with error code script timeout.
vm.custom_data()->spin_event_loop_until([&] {
return promise->state() != JS::Promise::State::Pending;
});
auto json_value_or_error = json_clone(realm, promise->result());
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");
return { ExecuteScriptResultType::JavaScriptError, move(error_object) };
}
return { result.type, json_value_or_error.release_value() };
// 7. Upon fulfillment of promise with value v, let result be a JSON clone of v, and return success with data result.
if (promise->state() == JS::Promise::State::Fulfilled) {
return { ExecuteScriptResultType::PromiseResolved, json_value_or_error.release_value() };
}
// 8. Upon rejection of promise with reason r, let result be a JSON clone of r, and return error with error code javascript error and data result.
if (promise->state() == JS::Promise::State::Rejected) {
return { ExecuteScriptResultType::PromiseRejected, json_value_or_error.release_value() };
}
VERIFY_NOT_REACHED();
}
}