LibJS+LibWeb: Set [[CanBlock]] false to Agent for window agent

similar-origin window agents have the [[CanBlock]] flag set to false.
Achieve this by hooking up JS's concept with an agent to HTML::Agent.
For now, this is only hooked up to the similar-origin window agent
case but should be extended to the other agent types in the future.
This commit is contained in:
Shannon Booth 2025-04-20 16:49:34 +12:00 committed by Tim Flynn
commit e124ef52ee
Notes: github-actions[bot] 2025-04-22 15:52:53 +00:00
16 changed files with 106 additions and 44 deletions

View file

@ -154,6 +154,7 @@ namespace JS {
class ASTNode;
class Accessor;
class Agent;
struct AsyncGeneratorRequest;
class BigInt;
class BoundFunction;

View file

@ -1,19 +1,28 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2025, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Agent.h>
#include <LibJS/Runtime/VM.h>
namespace JS {
Agent::~Agent() = default;
// 9.7.2 AgentCanSuspend ( ), https://tc39.es/ecma262/#sec-agentcansuspend
bool agent_can_suspend()
bool agent_can_suspend(VM const& vm)
{
// FIXME: 1. Let AR be the Agent Record of the surrounding agent.
// FIXME: 2. Return AR.[[CanBlock]].
return true;
// 1. Let AR be the Agent Record of the surrounding agent.
auto const* agent = vm.agent();
// 2. Return AR.[[CanBlock]].
// NOTE: We default to true if no agent has been provided (standalone LibJS with no embedder).
if (!agent)
return true;
return agent->can_block();
}
}

View file

@ -1,13 +1,29 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2025, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGC/Function.h>
#include <LibGC/Root.h>
#include <LibJS/Forward.h>
namespace JS {
bool agent_can_suspend();
// https://tc39.es/ecma262/#sec-agents
class Agent {
public:
virtual ~Agent();
// [[CanBlock]]
virtual bool can_block() const = 0;
virtual void spin_event_loop_until(GC::Root<GC::Function<bool()>> goal_condition) = 0;
};
bool agent_can_suspend(VM const&);
}

View file

@ -186,7 +186,7 @@ static ThrowCompletionOr<Value> do_wait(VM& vm, WaitMode mode, TypedArrayBase& t
timeout = max(timeout_number.as_double(), 0.0);
// 10. If mode is sync and AgentCanSuspend() is false, throw a TypeError exception.
if (mode == WaitMode::Sync && !agent_can_suspend())
if (mode == WaitMode::Sync && !agent_can_suspend(vm))
return vm.throw_completion<TypeError>(ErrorType::AgentCannotSuspend);
// FIXME: Implement the remaining steps when we support SharedArrayBuffer.

View file

@ -6,6 +6,7 @@
*/
#include <AK/TypeCasts.h>
#include <LibJS/Runtime/Agent.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/Promise.h>
@ -98,9 +99,9 @@ ThrowCompletionOr<Value> await(VM& vm, Value value)
// FIXME: Since we don't support context suspension, we attempt to "wait" for the promise to resolve
// by syncronously running all queued promise jobs.
if (auto* custom_data = vm.custom_data()) {
if (auto* agent = vm.agent()) {
// Embedder case (i.e. LibWeb). Runs all promise jobs by performing a microtask checkpoint.
custom_data->spin_event_loop_until(GC::create_function(vm.heap(), [success] {
agent->spin_event_loop_until(GC::create_function(vm.heap(), [success] {
return success.has_value();
}));
} else {

View file

@ -37,12 +37,12 @@
namespace JS {
ErrorOr<NonnullRefPtr<VM>> VM::create(OwnPtr<CustomData> custom_data)
ErrorOr<NonnullRefPtr<VM>> VM::create(OwnPtr<Agent> agent)
{
ErrorMessages error_messages {};
error_messages[to_underlying(ErrorMessage::OutOfMemory)] = ErrorType::OutOfMemory.message();
auto vm = adopt_ref(*new VM(move(custom_data), move(error_messages)));
auto vm = adopt_ref(*new VM(move(agent), move(error_messages)));
WellKnownSymbols well_known_symbols {
#define __JS_ENUMERATE(SymbolName, snake_name) \
@ -63,12 +63,12 @@ static constexpr auto make_single_ascii_character_strings(IndexSequence<code_poi
static constexpr auto single_ascii_character_strings = make_single_ascii_character_strings(MakeIndexSequence<128>());
VM::VM(OwnPtr<CustomData> custom_data, ErrorMessages error_messages)
VM::VM(OwnPtr<Agent> agent, ErrorMessages error_messages)
: m_heap(this, [this](HashMap<GC::Cell*, GC::HeapRoot>& roots) {
gather_roots(roots);
})
, m_error_messages(move(error_messages))
, m_custom_data(move(custom_data))
, m_agent(move(agent))
{
m_bytecode_interpreter = make<Bytecode::Interpreter>(*this);

View file

@ -21,6 +21,7 @@
#include <LibGC/RootVector.h>
#include <LibJS/CyclicModule.h>
#include <LibJS/ModuleLoading.h>
#include <LibJS/Runtime/Agent.h>
#include <LibJS/Runtime/CommonPropertyNames.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Error.h>
@ -46,13 +47,7 @@ enum class EvalMode {
class VM : public RefCounted<VM> {
public:
struct CustomData {
virtual ~CustomData() = default;
virtual void spin_event_loop_until(GC::Root<GC::Function<bool()>> goal_condition) = 0;
};
static ErrorOr<NonnullRefPtr<VM>> create(OwnPtr<CustomData> = {});
static ErrorOr<NonnullRefPtr<VM>> create(OwnPtr<Agent> = {});
~VM();
GC::Heap& heap() { return m_heap; }
@ -245,7 +240,8 @@ public:
Function<void(Promise&)> on_promise_rejection_handled;
Function<void(Object const&, PropertyKey const&)> on_unimplemented_property_access;
CustomData* custom_data() { return m_custom_data; }
Agent* agent() { return m_agent; }
Agent const* agent() const { return m_agent; }
void save_execution_context_stack();
void clear_execution_context_stack();
@ -294,7 +290,7 @@ private:
#undef __JS_ENUMERATE
};
VM(OwnPtr<CustomData>, ErrorMessages);
VM(OwnPtr<Agent>, ErrorMessages);
void load_imported_module(ImportedModuleReferrer, ModuleRequest const&, GC::Ptr<GraphLoadingState::HostDefined>, ImportedModulePayload);
ThrowCompletionOr<void> link_and_eval_module(CyclicModule&);
@ -339,7 +335,7 @@ private:
u32 m_execution_generation { 0 };
OwnPtr<CustomData> m_custom_data;
OwnPtr<Agent> m_agent;
OwnPtr<Bytecode::Interpreter> m_bytecode_interpreter;