mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-24 21:45:20 +00:00
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:
parent
4cd186f3f5
commit
e124ef52ee
Notes:
github-actions[bot]
2025-04-22 15:52:53 +00:00
Author: https://github.com/shannonbooth Commit: https://github.com/LadybirdBrowser/ladybird/commit/e124ef52ee9 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4406
16 changed files with 106 additions and 44 deletions
|
@ -154,6 +154,7 @@ namespace JS {
|
|||
|
||||
class ASTNode;
|
||||
class Accessor;
|
||||
class Agent;
|
||||
struct AsyncGeneratorRequest;
|
||||
class BigInt;
|
||||
class BoundFunction;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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&);
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -76,10 +76,10 @@ ErrorOr<void> initialize_main_thread_vm(HTML::EventLoop::Type type)
|
|||
{
|
||||
VERIFY(!s_main_thread_vm);
|
||||
|
||||
s_main_thread_vm = TRY(JS::VM::create(make<WebEngineCustomData>()));
|
||||
s_main_thread_vm = TRY(JS::VM::create(make<HTML::Agent>()));
|
||||
|
||||
auto& custom_data = as<WebEngineCustomData>(*s_main_thread_vm->custom_data());
|
||||
custom_data.agent.event_loop = s_main_thread_vm->heap().allocate<HTML::EventLoop>(type);
|
||||
auto& agent = as<HTML::Agent>(*s_main_thread_vm->agent());
|
||||
agent.event_loop = s_main_thread_vm->heap().allocate<HTML::EventLoop>(type);
|
||||
|
||||
s_main_thread_vm->on_unimplemented_property_access = [](auto const& object, auto const& property_key) {
|
||||
dbgln("FIXME: Unimplemented IDL interface: '{}.{}'", object.class_name(), property_key.to_string());
|
||||
|
@ -658,7 +658,7 @@ JS::VM& main_thread_vm()
|
|||
void queue_mutation_observer_microtask(DOM::Document const& document)
|
||||
{
|
||||
auto& vm = main_thread_vm();
|
||||
auto& surrounding_agent = as<WebEngineCustomData>(*vm.custom_data()).agent;
|
||||
auto& surrounding_agent = as<HTML::Agent>(*vm.agent());
|
||||
|
||||
// 1. If the surrounding agent’s mutation observer microtask queued is true, then return.
|
||||
if (surrounding_agent.mutation_observer_microtask_queued)
|
||||
|
@ -750,11 +750,6 @@ NonnullOwnPtr<JS::ExecutionContext> create_a_new_javascript_realm(JS::VM& vm, Fu
|
|||
return realm_execution_context;
|
||||
}
|
||||
|
||||
void WebEngineCustomData::spin_event_loop_until(GC::Root<GC::Function<bool()>> goal_condition)
|
||||
{
|
||||
Platform::EventLoopPlugin::the().spin_until(move(goal_condition));
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#invoke-custom-element-reactions
|
||||
void invoke_custom_element_reactions(Vector<GC::Root<DOM::Element>>& element_queue)
|
||||
{
|
||||
|
|
|
@ -17,14 +17,6 @@
|
|||
|
||||
namespace Web::Bindings {
|
||||
|
||||
struct WebEngineCustomData final : public JS::VM::CustomData {
|
||||
virtual ~WebEngineCustomData() override = default;
|
||||
|
||||
virtual void spin_event_loop_until(GC::Root<GC::Function<bool()>> goal_condition) override;
|
||||
|
||||
HTML::Agent agent;
|
||||
};
|
||||
|
||||
struct WebEngineCustomJobCallbackData final : public JS::JobCallback::CustomData {
|
||||
WebEngineCustomJobCallbackData(JS::Realm& incumbent_realm, OwnPtr<JS::ExecutionContext> active_script_context)
|
||||
: incumbent_realm(incumbent_realm)
|
||||
|
|
|
@ -68,7 +68,7 @@ void EventLoop::schedule()
|
|||
|
||||
EventLoop& main_thread_event_loop()
|
||||
{
|
||||
return *static_cast<Bindings::WebEngineCustomData*>(Bindings::main_thread_vm().custom_data())->agent.event_loop;
|
||||
return *static_cast<HTML::Agent*>(Bindings::main_thread_vm().agent())->event_loop;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#spin-the-event-loop
|
||||
|
|
|
@ -7,15 +7,27 @@
|
|||
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||
#include <LibWeb/HTML/Scripting/Agent.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/Platform/EventLoopPlugin.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
bool Agent::can_block() const
|
||||
{
|
||||
// similar-origin window agents can not block, see: https://html.spec.whatwg.org/multipage/webappapis.html#obtain-similar-origin-window-agent
|
||||
return false;
|
||||
}
|
||||
|
||||
void Agent::spin_event_loop_until(GC::Root<GC::Function<bool()>> goal_condition)
|
||||
{
|
||||
Platform::EventLoopPlugin::the().spin_until(move(goal_condition));
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#relevant-agent
|
||||
Agent& relevant_agent(JS::Object const& object)
|
||||
{
|
||||
// The relevant agent for a platform object platformObject is platformObject's relevant Realm's agent.
|
||||
// Spec Note: This pointer is not yet defined in the JavaScript specification; see tc39/ecma262#1357.
|
||||
return static_cast<Bindings::WebEngineCustomData*>(relevant_realm(object).vm().custom_data())->agent;
|
||||
return *static_cast<Agent*>(relevant_realm(object).vm().agent());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <AK/Vector.h>
|
||||
#include <LibGC/Root.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Runtime/Agent.h>
|
||||
#include <LibWeb/DOM/MutationObserver.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/CustomElements/CustomElementReactionsStack.h>
|
||||
|
@ -17,7 +18,7 @@
|
|||
namespace Web::HTML {
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#similar-origin-window-agent
|
||||
struct Agent {
|
||||
struct Agent : public JS::Agent {
|
||||
GC::Root<HTML::EventLoop> event_loop;
|
||||
|
||||
// FIXME: These should only be on similar-origin window agents, but we don't currently differentiate agent types.
|
||||
|
@ -40,6 +41,11 @@ struct Agent {
|
|||
// A similar-origin window agent's current element queue is the element queue at the top of its custom element reactions stack.
|
||||
Vector<GC::Root<DOM::Element>>& current_element_queue() { return custom_element_reactions_stack.element_queue_stack.last(); }
|
||||
Vector<GC::Root<DOM::Element>> const& current_element_queue() const { return custom_element_reactions_stack.element_queue_stack.last(); }
|
||||
|
||||
// [[CanBlock]]
|
||||
virtual bool can_block() const override;
|
||||
|
||||
virtual void spin_event_loop_until(GC::Root<GC::Function<bool()>> goal_condition) override;
|
||||
};
|
||||
|
||||
Agent& relevant_agent(JS::Object const&);
|
||||
|
|
|
@ -373,8 +373,10 @@ static ErrorOr<TestMetadata, String> extract_metadata(StringView source)
|
|||
metadata.harness_files.append(async_include);
|
||||
metadata.is_async = true;
|
||||
} else if (flag == "CanBlockIsFalse"sv) {
|
||||
if (JS::agent_can_suspend())
|
||||
metadata.skip_test = SkipTest::Yes;
|
||||
// NOTE: This should only be skipped if AgentCanSuspend is set to true. This is currently always the case.
|
||||
// Ideally we would check that, but we don't have the VM by this stage. So for now, we rely on that
|
||||
// assumption.
|
||||
metadata.skip_test = SkipTest::Yes;
|
||||
}
|
||||
}
|
||||
} else if (line.starts_with("includes:"sv)) {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 1 tests
|
||||
|
||||
1 Pass
|
||||
Pass [[CanBlock]] in a Window
|
|
@ -0,0 +1,15 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
|
||||
<script>
|
||||
self.GLOBAL = {
|
||||
isWindow: function() { return true; },
|
||||
isWorker: function() { return false; },
|
||||
isShadowRealm: function() { return false; },
|
||||
};
|
||||
</script>
|
||||
<script src="../../../../../resources/testharness.js"></script>
|
||||
<script src="../../../../../resources/testharnessreport.js"></script>
|
||||
|
||||
<div id=log></div>
|
||||
<script src="../../../../../html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/requires-failure.https.any.js"></script>
|
|
@ -0,0 +1,11 @@
|
|||
// META: global=window,serviceworker
|
||||
|
||||
test(() => {
|
||||
// See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()`
|
||||
const sab = new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer;
|
||||
const ta = new Int32Array(sab);
|
||||
|
||||
assert_throws_js(TypeError, () => {
|
||||
Atomics.wait(ta, 0, 0, 10);
|
||||
});
|
||||
}, `[[CanBlock]] in a ${self.constructor.name}`);
|
Loading…
Add table
Reference in a new issue