LibWebView+WebContent+WebWorker: Move worker ownership into LibWebView
This lays the groundwork to allow shared workers, nested workers (i.e. workers owned by workers) and service workers to function independently of WebContent.
This commit is contained in:
parent
112ad32f6d
commit
c2dbd52982
35 changed files with 1212 additions and 263 deletions
|
|
@ -721,6 +721,7 @@ set(SOURCES
|
|||
HTML/WorkerDebugConsoleClient.cpp
|
||||
HTML/WorkerGlobalScope.cpp
|
||||
HTML/WorkerLocation.cpp
|
||||
HTML/WorkerAgentTypes.cpp
|
||||
HTML/WorkerNavigator.cpp
|
||||
HTML/WorkletGlobalScope.cpp
|
||||
HTML/XMLSerializer.cpp
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/MessageEvent.h>
|
||||
#include <LibWeb/Bindings/SharedWorker.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
|
|
@ -14,10 +13,8 @@
|
|||
#include <LibWeb/HTML/MessagePort.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/SharedWorker.h>
|
||||
#include <LibWeb/HTML/SharedWorkerGlobalScope.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/HTML/Worker.h>
|
||||
#include <LibWeb/Platform/EventLoopPlugin.h>
|
||||
#include <LibWeb/TrustedTypes/RequireTrustedTypesForDirective.h>
|
||||
#include <LibWeb/TrustedTypes/TrustedTypePolicy.h>
|
||||
|
||||
|
|
@ -67,104 +64,13 @@ WebIDL::ExceptionOr<GC::Ref<SharedWorker>> SharedWorker::construct_impl(JS::Real
|
|||
// 7. Set this's port to outsidePort.
|
||||
auto worker = realm.create<SharedWorker>(realm, url.release_value(), options, outside_port);
|
||||
|
||||
// 8. Let callerIsSecureContext be true if outside settings is a secure context; otherwise, false.
|
||||
auto caller_is_secure_context = HTML::is_secure_context(outside_settings);
|
||||
|
||||
// 9. Let outsideStorageKey be the result of running obtain a storage key for non-storage purposes given outsideSettings.
|
||||
auto outside_storage_key = StorageAPI::obtain_a_storage_key_for_non_storage_purposes(outside_settings);
|
||||
|
||||
// 10. Let worker be this.
|
||||
// NB: This is done earlier.
|
||||
|
||||
// 11. Enqueue the following steps to the shared worker manager:
|
||||
// FIXME: "A user agent has an associated shared worker manager which is the result of starting a new parallel queue."
|
||||
// We just use the singular global event loop for now.
|
||||
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [worker, outside_port, &outside_settings, caller_is_secure_context, outside_storage_key = move(outside_storage_key)]() mutable {
|
||||
// 1. Let workerGlobalScope be null.
|
||||
GC::Ptr<SharedWorkerGlobalScope> worker_global_scope;
|
||||
|
||||
// 2. For each scope in the list of all SharedWorkerGlobalScope objects:
|
||||
for (auto& scope : all_shared_worker_global_scopes()) {
|
||||
// 1. Let workerStorageKey be the result of running obtain a storage key for non-storage purposes given
|
||||
// scope's relevant settings object.
|
||||
auto worker_storage_key = StorageAPI::obtain_a_storage_key_for_non_storage_purposes(HTML::relevant_settings_object(scope));
|
||||
|
||||
// 2. If all of the following are true:
|
||||
if (
|
||||
// * workerStorageKey equals outsideStorageKey;
|
||||
worker_storage_key == outside_storage_key
|
||||
|
||||
// * scope's closing flag is false;
|
||||
&& !scope->is_closing()
|
||||
|
||||
// * scope's constructor URL equals urlRecord; and
|
||||
&& scope->url() == worker->m_script_url
|
||||
|
||||
// * scope's name equals options["name"],
|
||||
&& scope->name() == worker->m_options.name)
|
||||
// then:
|
||||
{
|
||||
// 1. Set workerGlobalScope to scope.
|
||||
worker_global_scope = scope;
|
||||
|
||||
// 2. Break.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: 3. If workerGlobalScope is not null, but the user agent has been configured to disallow communication between the worker represented by the workerGlobalScope and the scripts whose settings object is outsideSettings, then set workerGlobalScope to null.
|
||||
// FIXME: 4. If workerGlobalScope is not null, and any of the following are true: ...
|
||||
|
||||
// 5. If workerGlobalScope is not null:
|
||||
if (worker_global_scope) {
|
||||
// 1. Let insideSettings be workerGlobalScope's relevant settings object.
|
||||
auto& inside_settings = relevant_settings_object(*worker_global_scope);
|
||||
|
||||
// 2. Let workerIsSecureContext be true if insideSettings is a secure context; otherwise, false.
|
||||
auto worker_is_secure_context = is_secure_context(inside_settings);
|
||||
|
||||
// 3. If workerIsSecureContext is not callerIsSecureContext:
|
||||
if (worker_is_secure_context != caller_is_secure_context) {
|
||||
// 1. Queue a global task on the DOM manipulation task source given worker's relevant global object to fire an event named error at worker.
|
||||
queue_global_task(Task::Source::DOMManipulation, relevant_global_object(worker), GC::create_function(worker->heap(), [worker]() {
|
||||
worker->dispatch_event(DOM::Event::create(worker->realm(), EventNames::error));
|
||||
}));
|
||||
|
||||
// 2. Abort these steps.
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: 4. Associate worker with workerGlobalScope.
|
||||
|
||||
// 5. Let insidePort be a new MessagePort in insideSettings's realm.
|
||||
auto inside_port = HTML::MessagePort::create(inside_settings.realm());
|
||||
|
||||
// 6. Entangle outsidePort and insidePort.
|
||||
outside_port->entangle_with(inside_port);
|
||||
|
||||
// 7. Queue a global task on the DOM manipulation task source given workerGlobalScope to fire an event
|
||||
// named connect at workerGlobalScope, using MessageEvent, with the data attribute initialized to the
|
||||
// empty string, the ports attribute initialized to a new frozen array containing only insidePort, and
|
||||
// the source attribute initialized to insidePort.
|
||||
queue_global_task(Task::Source::DOMManipulation, *worker_global_scope, GC::create_function(worker->heap(), [worker_global_scope, inside_port]() {
|
||||
auto& realm = worker_global_scope->realm();
|
||||
|
||||
Bindings::MessageEventInit init;
|
||||
init.data = JS::PrimitiveString::create(realm.vm(), String {});
|
||||
init.ports.append(inside_port);
|
||||
init.source = inside_port;
|
||||
|
||||
worker_global_scope->dispatch_event(MessageEvent::create(realm, EventNames::connect, init));
|
||||
}));
|
||||
|
||||
// FIXME: 8. Append the relevant owner to add given outsideSettings to workerGlobalScope's owner set.
|
||||
|
||||
}
|
||||
// 6. Otherwise, in parallel, run a worker given worker, urlRecord, outsideSettings, outsidePort, and options.
|
||||
else {
|
||||
run_a_worker(worker, worker->m_script_url, outside_settings, outside_port, worker->m_options);
|
||||
}
|
||||
}));
|
||||
// The actual shared worker manager is owned by the UI process. It will either start a new
|
||||
// WebWorker agent or transfer this port to an existing same-key SharedWorkerGlobalScope.
|
||||
run_a_worker(worker, worker->m_script_url, outside_settings, outside_port, worker->m_options);
|
||||
|
||||
return worker;
|
||||
}
|
||||
|
|
|
|||
18
Libraries/LibWeb/HTML/WorkerAgentForward.h
Normal file
18
Libraries/LibWeb/HTML/WorkerAgentForward.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Ladybird contributors
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
using WorkerAgentId = u64;
|
||||
using WorkerAgentOwnerToken = u64;
|
||||
|
||||
struct WorkerAgentStartRequest;
|
||||
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <LibWeb/Bindings/PrincipalHostDefined.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
|
|
@ -18,16 +19,23 @@
|
|||
#include <LibWeb/HTML/Worker.h>
|
||||
#include <LibWeb/HTML/WorkerAgentParent.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
#include <LibWeb/Worker/WebWorkerClient.h>
|
||||
#include <LibWeb/StorageAPI/StorageKey.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(WorkerAgentParent);
|
||||
|
||||
static HashMap<WorkerAgentOwnerToken, GC::Ref<WorkerAgentParent>>& worker_agent_parents()
|
||||
{
|
||||
static HashMap<WorkerAgentOwnerToken, GC::Ref<WorkerAgentParent>> map;
|
||||
return map;
|
||||
}
|
||||
|
||||
WorkerAgentParent::WorkerAgentParent(URL::URL url, Bindings::WorkerOptions const& options, GC::Ptr<MessagePort> outside_port, GC::Ref<EnvironmentSettingsObject> outside_settings, GC::Ref<DOM::EventTarget> worker_event_target, Bindings::AgentType agent_type)
|
||||
: m_worker_options(options)
|
||||
, m_agent_type(agent_type)
|
||||
, m_url(move(url))
|
||||
, m_owner_token(next_owner_token())
|
||||
, m_outside_port(outside_port)
|
||||
, m_outside_settings(outside_settings)
|
||||
, m_worker_event_target(worker_event_target)
|
||||
|
|
@ -51,90 +59,61 @@ void WorkerAgentParent::initialize(JS::Realm& realm)
|
|||
// so let's do it here for now.
|
||||
m_outside_port->start();
|
||||
|
||||
// NOTE: This blocking IPC call may launch another process.
|
||||
// If spinning the event loop for this can cause other javascript to execute, we're in trouble.
|
||||
auto response = Bindings::principal_host_defined_page(realm).client().request_worker_agent(m_agent_type);
|
||||
|
||||
auto transport = MUST(response.worker_handle.create_transport());
|
||||
m_worker_ipc = make_ref_counted<WebWorkerClient>(move(transport));
|
||||
setup_worker_ipc_callbacks(realm);
|
||||
|
||||
m_worker_ipc->async_connect_to_request_server(move(response.request_server_handle));
|
||||
m_worker_ipc->async_connect_to_image_decoder(move(response.image_decoder_handle));
|
||||
|
||||
auto serialized_outside_settings = m_outside_settings->serialize();
|
||||
|
||||
m_worker_ipc->async_start_worker(m_url, m_worker_options.type, m_worker_options.credentials, m_worker_options.name, move(data_holder), serialized_outside_settings, m_agent_type);
|
||||
// 8. Let callerIsSecureContext be true if outside settings is a secure context; otherwise, false.
|
||||
// 9. Let outsideStorageKey be the result of running obtain a storage key for non-storage purposes
|
||||
// given outsideSettings.
|
||||
WorkerAgentStartRequest request {
|
||||
.url = m_url,
|
||||
.agent_type = m_agent_type,
|
||||
.type = m_worker_options.type,
|
||||
.credentials = m_worker_options.credentials,
|
||||
.name = m_worker_options.name,
|
||||
.outside_port = move(data_holder),
|
||||
.outside_settings = serialized_outside_settings,
|
||||
.storage_key = StorageAPI::obtain_a_storage_key_for_non_storage_purposes(*m_outside_settings),
|
||||
.caller_is_secure_context = is_secure_context(*m_outside_settings),
|
||||
.owner_token = m_owner_token,
|
||||
};
|
||||
|
||||
// NOTE: This blocking IPC call may launch another process.
|
||||
// If spinning the event loop for this can cause other javascript to execute, we're in trouble.
|
||||
worker_agent_parents().set(m_owner_token, *this);
|
||||
m_agent_id = Bindings::principal_host_defined_page(realm).client().start_worker_agent(move(request));
|
||||
}
|
||||
|
||||
void WorkerAgentParent::setup_worker_ipc_callbacks(JS::Realm& realm)
|
||||
void WorkerAgentParent::did_finish_loading_worker_script(WorkerAgentOwnerToken owner_token)
|
||||
{
|
||||
// NOTE: As long as WorkerAgentParent is alive, realm and m_worker_ipc will be alive.
|
||||
m_worker_ipc->on_request_cookie = [realm = GC::RawRef { realm }](URL::URL const& url, HTTP::Cookie::Source source) {
|
||||
auto& client = Bindings::principal_host_defined_page(realm).client();
|
||||
return client.page_did_request_cookie(url, source);
|
||||
};
|
||||
m_worker_ipc->on_post_broadcast_channel_message = [realm = GC::RawRef { realm }](Web::HTML::BroadcastChannelMessage message) {
|
||||
auto& client = Bindings::principal_host_defined_page(realm).client();
|
||||
client.page_did_post_broadcast_channel_message(message);
|
||||
};
|
||||
m_worker_ipc->on_request_worker_agent = [realm = GC::RawRef { realm }](Web::Bindings::AgentType worker_type) -> Messages::WebWorkerClient::RequestWorkerAgentResponse {
|
||||
auto& client = Bindings::principal_host_defined_page(realm).client();
|
||||
auto response = client.request_worker_agent(worker_type);
|
||||
return { move(response.worker_handle), move(response.request_server_handle), move(response.image_decoder_handle) };
|
||||
};
|
||||
m_worker_ipc->on_worker_exception = [self = GC::Weak { *this }](String message, String filename, u32 lineno, u32 colno) {
|
||||
if (!self)
|
||||
return;
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#report-an-exception
|
||||
// 7.2: If global implements DedicatedWorkerGlobalScope, queue a global task on the DOM manipulation task source with the global's associated Worker's relevant global object to run these steps:
|
||||
auto outside_settings = GC::Ref { *self->m_outside_settings };
|
||||
auto worker_event_target = GC::Ref { *self->m_worker_event_target };
|
||||
queue_global_task(Task::Source::DOMManipulation, outside_settings->global_object(), GC::create_function(outside_settings->heap(), [outside_settings, worker_event_target, message = move(message), filename = move(filename), lineno, colno]() {
|
||||
// 1. Let workerObject be the Worker object associated with global.
|
||||
auto& worker_object = as<Worker>(*worker_event_target);
|
||||
auto parent = worker_agent_parents().find(owner_token);
|
||||
if (parent == worker_agent_parents().end())
|
||||
return;
|
||||
parent->value->release_startup_keep_alive();
|
||||
}
|
||||
|
||||
auto& realm = outside_settings->realm();
|
||||
void WorkerAgentParent::did_fail_loading_worker_script(WorkerAgentOwnerToken owner_token)
|
||||
{
|
||||
auto parent = worker_agent_parents().find(owner_token);
|
||||
if (parent == worker_agent_parents().end())
|
||||
return;
|
||||
parent->value->dispatch_error_event();
|
||||
parent->value->release_startup_keep_alive();
|
||||
}
|
||||
|
||||
// 2. Set notHandled to the result of firing an event named error at workerObject, using ErrorEvent, with the
|
||||
// cancelable attribute initialized to true, and additional attributes initialized according to errorInfo.
|
||||
Bindings::ErrorEventInit event_init {};
|
||||
event_init.cancelable = true;
|
||||
event_init.message = message;
|
||||
event_init.filename = filename;
|
||||
event_init.lineno = lineno;
|
||||
event_init.colno = colno;
|
||||
event_init.error = JS::js_null();
|
||||
auto error = ErrorEvent::create(realm, EventNames::error, event_init);
|
||||
bool not_handled = worker_object.dispatch_event(error);
|
||||
void WorkerAgentParent::did_report_worker_exception(WorkerAgentOwnerToken owner_token, String message, String filename, u32 lineno, u32 colno)
|
||||
{
|
||||
auto parent = worker_agent_parents().find(owner_token);
|
||||
if (parent == worker_agent_parents().end())
|
||||
return;
|
||||
parent->value->dispatch_worker_exception(move(message), move(filename), lineno, colno);
|
||||
}
|
||||
|
||||
// 3. If notHandled is true, then report exception for workerObject's relevant global object with omitError set to true.
|
||||
if (not_handled)
|
||||
as<WindowOrWorkerGlobalScopeMixin>(outside_settings->global_object()).report_an_exception(error, WindowOrWorkerGlobalScopeMixin::OmitError::Yes);
|
||||
}));
|
||||
};
|
||||
m_worker_ipc->on_worker_close = [self = GC::Weak { *this }]() {
|
||||
if (!self)
|
||||
return;
|
||||
self->release_startup_keep_alive();
|
||||
};
|
||||
m_worker_ipc->on_worker_script_load_success = [self = GC::Weak { *this }]() {
|
||||
if (!self)
|
||||
return;
|
||||
self->release_startup_keep_alive();
|
||||
};
|
||||
m_worker_ipc->on_worker_script_load_failure = [self = GC::Weak { *this }]() {
|
||||
if (!self)
|
||||
return;
|
||||
auto outside_settings = GC::Ref { *self->m_outside_settings };
|
||||
auto event_target = GC::Ref { *self->m_worker_event_target };
|
||||
// See: https://html.spec.whatwg.org/multipage/workers.html#worker-processing-model, onComplete handler for fetching script.
|
||||
// 1. Queue a global task on the DOM manipulation task source given worker's relevant global object to fire an event named error at worker.
|
||||
queue_global_task(Task::Source::DOMManipulation, outside_settings->global_object(), GC::create_function(outside_settings->heap(), [event_target, outside_settings]() {
|
||||
event_target->dispatch_event(DOM::Event::create(outside_settings->realm(), EventNames::error));
|
||||
}));
|
||||
self->release_startup_keep_alive();
|
||||
};
|
||||
void WorkerAgentParent::did_close_worker(WorkerAgentOwnerToken owner_token)
|
||||
{
|
||||
auto parent = worker_agent_parents().find(owner_token);
|
||||
if (parent == worker_agent_parents().end())
|
||||
return;
|
||||
parent->value->release_startup_keep_alive();
|
||||
}
|
||||
|
||||
void WorkerAgentParent::release_startup_keep_alive()
|
||||
|
|
@ -142,6 +121,56 @@ void WorkerAgentParent::release_startup_keep_alive()
|
|||
m_outside_settings->release_worker_agent_from_startup_keep_alive(*this);
|
||||
}
|
||||
|
||||
void WorkerAgentParent::dispatch_error_event()
|
||||
{
|
||||
// See: https://html.spec.whatwg.org/multipage/workers.html#worker-processing-model, onComplete handler for fetching script.
|
||||
// 1. Queue a global task on the DOM manipulation task source given worker's relevant global object to fire an event named error at worker.
|
||||
queue_global_task(Task::Source::DOMManipulation, m_outside_settings->global_object(), GC::create_function(m_outside_settings->heap(), [this] {
|
||||
m_worker_event_target->dispatch_event(DOM::Event::create(m_outside_settings->realm(), EventNames::error));
|
||||
}));
|
||||
}
|
||||
|
||||
void WorkerAgentParent::dispatch_worker_exception(String message, String filename, u32 lineno, u32 colno)
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#report-an-exception
|
||||
// 7.2: If global implements DedicatedWorkerGlobalScope, queue a global task on the DOM manipulation task source with the global's associated Worker's relevant global object to run these steps:
|
||||
queue_global_task(Task::Source::DOMManipulation, m_outside_settings->global_object(), GC::create_function(m_outside_settings->heap(), [this, message = move(message), filename = move(filename), lineno, colno]() {
|
||||
auto& realm = m_outside_settings->realm();
|
||||
|
||||
// 2. Set notHandled to the result of firing an event named error at workerObject, using ErrorEvent, with the
|
||||
// cancelable attribute initialized to true, and additional attributes initialized according to errorInfo.
|
||||
Bindings::ErrorEventInit event_init {};
|
||||
event_init.cancelable = true;
|
||||
event_init.message = message;
|
||||
event_init.filename = filename;
|
||||
event_init.lineno = lineno;
|
||||
event_init.colno = colno;
|
||||
event_init.error = JS::js_null();
|
||||
auto error = ErrorEvent::create(realm, EventNames::error, event_init);
|
||||
bool not_handled = m_worker_event_target->dispatch_event(error);
|
||||
|
||||
// 3. If notHandled is true, then report exception for workerObject's relevant global object with omitError set to true.
|
||||
if (not_handled)
|
||||
as<WindowOrWorkerGlobalScopeMixin>(m_outside_settings->global_object()).report_an_exception(error, WindowOrWorkerGlobalScopeMixin::OmitError::Yes);
|
||||
}));
|
||||
}
|
||||
|
||||
WorkerAgentOwnerToken WorkerAgentParent::next_owner_token()
|
||||
{
|
||||
static WorkerAgentOwnerToken s_next_owner_token = 0;
|
||||
return ++s_next_owner_token;
|
||||
}
|
||||
|
||||
void WorkerAgentParent::finalize()
|
||||
{
|
||||
Base::finalize();
|
||||
|
||||
worker_agent_parents().remove(m_owner_token);
|
||||
|
||||
if (m_agent_id != 0)
|
||||
Bindings::principal_host_defined_page(m_outside_settings->realm()).client().close_worker_agent(m_agent_id, m_owner_token);
|
||||
}
|
||||
|
||||
void WorkerAgentParent::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <LibWeb/Bindings/Request.h>
|
||||
#include <LibWeb/Bindings/Worker.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/WorkerAgentTypes.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
|
|
@ -19,25 +20,37 @@ class WorkerAgentParent : public JS::Cell {
|
|||
GC_CELL(WorkerAgentParent, JS::Cell);
|
||||
GC_DECLARE_ALLOCATOR(WorkerAgentParent);
|
||||
|
||||
public:
|
||||
static constexpr bool OVERRIDES_FINALIZE = true;
|
||||
|
||||
static WEB_API void did_finish_loading_worker_script(WorkerAgentOwnerToken);
|
||||
static WEB_API void did_fail_loading_worker_script(WorkerAgentOwnerToken);
|
||||
static WEB_API void did_report_worker_exception(WorkerAgentOwnerToken, String message, String filename, u32 lineno, u32 colno);
|
||||
static WEB_API void did_close_worker(WorkerAgentOwnerToken);
|
||||
|
||||
protected:
|
||||
WorkerAgentParent(URL::URL url, Bindings::WorkerOptions const& options, GC::Ptr<MessagePort> outside_port, GC::Ref<EnvironmentSettingsObject> outside_settings, GC::Ref<DOM::EventTarget> worker_event_target, Bindings::AgentType);
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
virtual void finalize() override;
|
||||
|
||||
private:
|
||||
void setup_worker_ipc_callbacks(JS::Realm&);
|
||||
void release_startup_keep_alive();
|
||||
void dispatch_error_event();
|
||||
void dispatch_worker_exception(String message, String filename, u32 lineno, u32 colno);
|
||||
|
||||
static WorkerAgentOwnerToken next_owner_token();
|
||||
|
||||
Bindings::WorkerOptions m_worker_options;
|
||||
Bindings::AgentType m_agent_type { Bindings::AgentType::DedicatedWorker };
|
||||
URL::URL m_url;
|
||||
WorkerAgentId m_agent_id { 0 };
|
||||
WorkerAgentOwnerToken m_owner_token { 0 };
|
||||
|
||||
GC::Ptr<MessagePort> m_message_port;
|
||||
GC::Ptr<MessagePort> m_outside_port;
|
||||
GC::Ref<EnvironmentSettingsObject> m_outside_settings;
|
||||
GC::Ref<DOM::EventTarget> m_worker_event_target;
|
||||
|
||||
RefPtr<Web::HTML::WebWorkerClient> m_worker_ipc;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
48
Libraries/LibWeb/HTML/WorkerAgentTypes.cpp
Normal file
48
Libraries/LibWeb/HTML/WorkerAgentTypes.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Ladybird contributors
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibIPC/Decoder.h>
|
||||
#include <LibIPC/Encoder.h>
|
||||
#include <LibWeb/HTML/WorkerAgentTypes.h>
|
||||
|
||||
namespace IPC {
|
||||
|
||||
template<>
|
||||
ErrorOr<void> encode(Encoder& encoder, Web::HTML::WorkerAgentStartRequest const& request)
|
||||
{
|
||||
TRY(encoder.encode(request.url));
|
||||
TRY(encoder.encode(request.agent_type));
|
||||
TRY(encoder.encode(request.type));
|
||||
TRY(encoder.encode(request.credentials));
|
||||
TRY(encoder.encode(request.name));
|
||||
TRY(encoder.encode(request.extended_lifetime));
|
||||
TRY(encoder.encode(request.outside_port));
|
||||
TRY(encoder.encode(request.outside_settings));
|
||||
TRY(encoder.encode(request.storage_key));
|
||||
TRY(encoder.encode(request.caller_is_secure_context));
|
||||
TRY(encoder.encode(request.owner_token));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<Web::HTML::WorkerAgentStartRequest> decode(Decoder& decoder)
|
||||
{
|
||||
return Web::HTML::WorkerAgentStartRequest {
|
||||
.url = TRY(decoder.decode<URL::URL>()),
|
||||
.agent_type = TRY(decoder.decode<Web::Bindings::AgentType>()),
|
||||
.type = TRY(decoder.decode<Web::Bindings::WorkerType>()),
|
||||
.credentials = TRY(decoder.decode<Web::Bindings::RequestCredentials>()),
|
||||
.name = TRY(decoder.decode<String>()),
|
||||
.extended_lifetime = TRY(decoder.decode<bool>()),
|
||||
.outside_port = TRY(decoder.decode<Web::HTML::TransferDataEncoder>()),
|
||||
.outside_settings = TRY(decoder.decode<Web::HTML::SerializedEnvironmentSettingsObject>()),
|
||||
.storage_key = TRY(decoder.decode<Web::StorageAPI::StorageKey>()),
|
||||
.caller_is_secure_context = TRY(decoder.decode<bool>()),
|
||||
.owner_token = TRY(decoder.decode<Web::HTML::WorkerAgentOwnerToken>()),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
47
Libraries/LibWeb/HTML/WorkerAgentTypes.h
Normal file
47
Libraries/LibWeb/HTML/WorkerAgentTypes.h
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Ladybird contributors
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <LibIPC/Forward.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/Bindings/AgentType.h>
|
||||
#include <LibWeb/Bindings/Worker.h>
|
||||
#include <LibWeb/Export.h>
|
||||
#include <LibWeb/HTML/Scripting/SerializedEnvironmentSettingsObject.h>
|
||||
#include <LibWeb/HTML/StructuredSerialize.h>
|
||||
#include <LibWeb/HTML/WorkerAgentForward.h>
|
||||
#include <LibWeb/StorageAPI/StorageKey.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
struct WEB_API WorkerAgentStartRequest {
|
||||
URL::URL url;
|
||||
Bindings::AgentType agent_type { Bindings::AgentType::DedicatedWorker };
|
||||
Bindings::WorkerType type { Bindings::WorkerType::Classic };
|
||||
Bindings::RequestCredentials credentials { Bindings::RequestCredentials::SameOrigin };
|
||||
String name;
|
||||
// FIXME: We don't implement SharedWorkerOptions/extendedLifetime yet.
|
||||
bool extended_lifetime { false };
|
||||
TransferDataEncoder outside_port;
|
||||
SerializedEnvironmentSettingsObject outside_settings;
|
||||
StorageAPI::StorageKey storage_key;
|
||||
bool caller_is_secure_context { false };
|
||||
WorkerAgentOwnerToken owner_token { 0 };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace IPC {
|
||||
|
||||
template<>
|
||||
WEB_API ErrorOr<void> encode(Encoder&, Web::HTML::WorkerAgentStartRequest const&);
|
||||
|
||||
template<>
|
||||
WEB_API ErrorOr<Web::HTML::WorkerAgentStartRequest> decode(Decoder&);
|
||||
|
||||
}
|
||||
|
|
@ -44,6 +44,7 @@
|
|||
#include <LibWeb/HTML/SelectItem.h>
|
||||
#include <LibWeb/HTML/TokenizedFeatures.h>
|
||||
#include <LibWeb/HTML/WebViewHints.h>
|
||||
#include <LibWeb/HTML/WorkerAgentForward.h>
|
||||
#include <LibWeb/Loader/FileRequest.h>
|
||||
#include <LibWeb/Page/EventResult.h>
|
||||
#include <LibWeb/Page/InputEvent.h>
|
||||
|
|
@ -523,12 +524,8 @@ public:
|
|||
virtual void page_did_report_worker_exception([[maybe_unused]] String const& message, [[maybe_unused]] String const& filename, [[maybe_unused]] u32 lineno, [[maybe_unused]] u32 colno) { }
|
||||
virtual void page_did_post_broadcast_channel_message([[maybe_unused]] HTML::BroadcastChannelMessage const& message) { }
|
||||
|
||||
struct WorkerAgentResponse {
|
||||
IPC::TransportHandle worker_handle;
|
||||
IPC::TransportHandle request_server_handle;
|
||||
IPC::TransportHandle image_decoder_handle;
|
||||
};
|
||||
virtual WorkerAgentResponse request_worker_agent([[maybe_unused]] Web::Bindings::AgentType worker_type) { return {}; }
|
||||
virtual HTML::WorkerAgentId start_worker_agent([[maybe_unused]] HTML::WorkerAgentStartRequest&& request) { return {}; }
|
||||
virtual void close_worker_agent([[maybe_unused]] HTML::WorkerAgentId agent_id, [[maybe_unused]] HTML::WorkerAgentOwnerToken owner_token) { }
|
||||
|
||||
virtual void page_did_mutate_dom([[maybe_unused]] FlyString const& type, [[maybe_unused]] DOM::Node const& target, [[maybe_unused]] DOM::NodeList& added_nodes, [[maybe_unused]] DOM::NodeList& removed_nodes, [[maybe_unused]] GC::Ptr<DOM::Node> previous_sibling, [[maybe_unused]] GC::Ptr<DOM::Node> next_sibling, [[maybe_unused]] Optional<String> const& attribute_name) { }
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ HashTable<WebWorkerClient*> WebWorkerClient::s_all_clients;
|
|||
|
||||
void WebWorkerClient::die()
|
||||
{
|
||||
// FIXME: Notify WorkerAgent that the worker is dead
|
||||
if (on_worker_died)
|
||||
on_worker_died();
|
||||
}
|
||||
|
||||
void WebWorkerClient::did_close_worker()
|
||||
|
|
@ -21,10 +22,10 @@ void WebWorkerClient::did_close_worker()
|
|||
on_worker_close();
|
||||
}
|
||||
|
||||
void WebWorkerClient::did_finish_loading_worker_script()
|
||||
void WebWorkerClient::did_finish_loading_worker_script(bool worker_is_secure_context)
|
||||
{
|
||||
if (on_worker_script_load_success)
|
||||
on_worker_script_load_success();
|
||||
on_worker_script_load_success(worker_is_secure_context);
|
||||
}
|
||||
|
||||
void WebWorkerClient::did_fail_loading_worker_script()
|
||||
|
|
@ -46,17 +47,29 @@ Messages::WebWorkerClient::DidRequestCookieResponse WebWorkerClient::did_request
|
|||
return HTTP::Cookie::VersionedCookie {};
|
||||
}
|
||||
|
||||
void WebWorkerClient::did_request_file(ByteString path, i32 request_id)
|
||||
{
|
||||
if (on_request_file)
|
||||
on_request_file(move(path), request_id);
|
||||
}
|
||||
|
||||
void WebWorkerClient::did_post_broadcast_channel_message(Web::HTML::BroadcastChannelMessage message)
|
||||
{
|
||||
if (on_post_broadcast_channel_message)
|
||||
on_post_broadcast_channel_message(move(message));
|
||||
}
|
||||
|
||||
Messages::WebWorkerClient::RequestWorkerAgentResponse WebWorkerClient::request_worker_agent(Web::Bindings::AgentType worker_type)
|
||||
Messages::WebWorkerClient::StartWorkerAgentResponse WebWorkerClient::start_worker_agent(Web::HTML::WorkerAgentStartRequest request)
|
||||
{
|
||||
if (on_request_worker_agent)
|
||||
return on_request_worker_agent(worker_type);
|
||||
return { IPC::TransportHandle {}, IPC::TransportHandle {}, IPC::TransportHandle {} };
|
||||
if (on_start_worker_agent)
|
||||
return on_start_worker_agent(move(request));
|
||||
return { 0 };
|
||||
}
|
||||
|
||||
void WebWorkerClient::close_worker_agent(Web::HTML::WorkerAgentId agent_id, Web::HTML::WorkerAgentOwnerToken owner_token)
|
||||
{
|
||||
if (on_close_worker_agent)
|
||||
on_close_worker_agent(agent_id, owner_token);
|
||||
}
|
||||
|
||||
WebWorkerClient::WebWorkerClient(NonnullOwnPtr<IPC::Transport> transport)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/IterationDecision.h>
|
||||
#include <AK/Types.h>
|
||||
|
|
@ -14,6 +15,7 @@
|
|||
#include <LibIPC/TransportHandle.h>
|
||||
#include <LibWeb/Export.h>
|
||||
#include <LibWeb/HTML/BroadcastChannelMessage.h>
|
||||
#include <LibWeb/HTML/WorkerAgentTypes.h>
|
||||
#include <LibWeb/Worker/WebWorkerClientEndpoint.h>
|
||||
#include <LibWeb/Worker/WebWorkerServerEndpoint.h>
|
||||
|
||||
|
|
@ -35,20 +37,25 @@ public:
|
|||
void set_pid(pid_t pid) { m_pid = pid; }
|
||||
|
||||
virtual void did_close_worker() override;
|
||||
virtual void did_finish_loading_worker_script() override;
|
||||
virtual void did_finish_loading_worker_script(bool worker_is_secure_context) override;
|
||||
virtual void did_fail_loading_worker_script() override;
|
||||
virtual void did_report_worker_exception(String message, String filename, u32 lineno, u32 colno) override;
|
||||
virtual Messages::WebWorkerClient::DidRequestCookieResponse did_request_cookie(URL::URL, HTTP::Cookie::Source) override;
|
||||
virtual void did_request_file(ByteString path, i32 request_id) override;
|
||||
virtual void did_post_broadcast_channel_message(Web::HTML::BroadcastChannelMessage) override;
|
||||
virtual Messages::WebWorkerClient::RequestWorkerAgentResponse request_worker_agent(Web::Bindings::AgentType worker_type) override;
|
||||
virtual Messages::WebWorkerClient::StartWorkerAgentResponse start_worker_agent(Web::HTML::WorkerAgentStartRequest request) override;
|
||||
virtual void close_worker_agent(Web::HTML::WorkerAgentId, Web::HTML::WorkerAgentOwnerToken) override;
|
||||
|
||||
Function<void()> on_worker_close;
|
||||
Function<void()> on_worker_script_load_success;
|
||||
Function<void()> on_worker_died;
|
||||
Function<void(bool)> on_worker_script_load_success;
|
||||
Function<void()> on_worker_script_load_failure;
|
||||
Function<void(String, String, u32, u32)> on_worker_exception;
|
||||
Function<HTTP::Cookie::VersionedCookie(URL::URL const&, HTTP::Cookie::Source)> on_request_cookie;
|
||||
Function<void(ByteString, i32)> on_request_file;
|
||||
Function<void(Web::HTML::BroadcastChannelMessage)> on_post_broadcast_channel_message;
|
||||
Function<Messages::WebWorkerClient::RequestWorkerAgentResponse(Web::Bindings::AgentType)> on_request_worker_agent;
|
||||
Function<Web::HTML::WorkerAgentId(Web::HTML::WorkerAgentStartRequest)> on_start_worker_agent;
|
||||
Function<void(Web::HTML::WorkerAgentId, Web::HTML::WorkerAgentOwnerToken)> on_close_worker_agent;
|
||||
|
||||
private:
|
||||
virtual void die() override;
|
||||
|
|
|
|||
|
|
@ -3,13 +3,16 @@
|
|||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/Bindings/AgentType.h>
|
||||
#include <LibWeb/HTML/BroadcastChannelMessage.h>
|
||||
#include <LibWeb/HTML/WorkerAgentTypes.h>
|
||||
|
||||
endpoint WebWorkerClient {
|
||||
did_close_worker() =|
|
||||
did_finish_loading_worker_script() =|
|
||||
did_finish_loading_worker_script(bool worker_is_secure_context) =|
|
||||
did_fail_loading_worker_script() =|
|
||||
did_report_worker_exception(String message, String filename, u32 lineno, u32 colno) =|
|
||||
did_request_cookie(URL::URL url, HTTP::Cookie::Source source) => (HTTP::Cookie::VersionedCookie cookie)
|
||||
did_request_file(ByteString path, i32 request_id) =|
|
||||
did_post_broadcast_channel_message(Web::HTML::BroadcastChannelMessage message) =|
|
||||
request_worker_agent(Web::Bindings::AgentType worker_type) => (IPC::TransportHandle handle, IPC::TransportHandle request_server_handle, IPC::TransportHandle image_decoder_handle)
|
||||
start_worker_agent(Web::HTML::WorkerAgentStartRequest request) => (Web::HTML::WorkerAgentId agent_id)
|
||||
close_worker_agent(Web::HTML::WorkerAgentId agent_id, Web::HTML::WorkerAgentOwnerToken owner_token) =|
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include <LibWeb/Bindings/Worker.h>
|
||||
#include <LibWeb/HTML/StructuredSerialize.h>
|
||||
#include <LibWeb/HTML/Scripting/SerializedEnvironmentSettingsObject.h>
|
||||
#include <LibWeb/HTML/WorkerAgentTypes.h>
|
||||
|
||||
endpoint WebWorkerServer {
|
||||
|
||||
|
|
@ -20,8 +21,16 @@ endpoint WebWorkerServer {
|
|||
Web::HTML::SerializedEnvironmentSettingsObject outside_settings,
|
||||
Web::Bindings::AgentType agent_type) =|
|
||||
|
||||
connect_shared_worker(Web::HTML::TransferDataEncoder message_port,
|
||||
Web::HTML::SerializedEnvironmentSettingsObject outside_settings) =|
|
||||
|
||||
close_worker() =|
|
||||
|
||||
did_worker_agent_finish_loading_script(Web::HTML::WorkerAgentOwnerToken owner_token) =|
|
||||
did_worker_agent_fail_loading_script(Web::HTML::WorkerAgentOwnerToken owner_token) =|
|
||||
did_worker_agent_report_exception(Web::HTML::WorkerAgentOwnerToken owner_token, String message, String filename, u32 lineno, u32 colno) =|
|
||||
did_worker_agent_close(Web::HTML::WorkerAgentOwnerToken owner_token) =|
|
||||
|
||||
broadcast_channel_message(Web::HTML::BroadcastChannelMessage message) =|
|
||||
|
||||
handle_file_return(i32 error, Optional<IPC::File> file, i32 request_id) =|
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ set(SOURCES
|
|||
Utilities.cpp
|
||||
ViewImplementation.cpp
|
||||
WebContentClient.cpp
|
||||
WorkerProcessManager.cpp
|
||||
WebUI.cpp
|
||||
WebUI/BookmarksUI.cpp
|
||||
WebUI/ProcessesUI.cpp
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include <LibWebView/ViewImplementation.h>
|
||||
#include <LibWebView/WebContentClient.h>
|
||||
#include <LibWebView/WebUI.h>
|
||||
#include <LibWebView/WorkerProcessManager.h>
|
||||
|
||||
namespace WebView {
|
||||
|
||||
|
|
@ -55,6 +56,7 @@ WebContentClient::WebContentClient(NonnullOwnPtr<IPC::Transport> transport)
|
|||
|
||||
WebContentClient::~WebContentClient()
|
||||
{
|
||||
WorkerProcessManager::the().remove_web_content_owner(*this);
|
||||
s_clients.remove(this);
|
||||
}
|
||||
|
||||
|
|
@ -930,6 +932,7 @@ void WebContentClient::did_post_broadcast_channel_message(u64, Web::HTML::Broadc
|
|||
client.async_broadcast_channel_message(message);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
WorkerProcessManager::the().broadcast_channel_message_from_web_content(message);
|
||||
}
|
||||
|
||||
Messages::WebContentClient::DidRequestNewWebViewResponse WebContentClient::did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab activate_tab, Web::HTML::WebViewHints hints, Optional<u64> page_index)
|
||||
|
|
@ -1130,17 +1133,19 @@ void WebContentClient::did_present_backing_stores(u64 page_id, i32 front_bitmap_
|
|||
}
|
||||
}
|
||||
|
||||
Messages::WebContentClient::RequestWorkerAgentResponse WebContentClient::request_worker_agent(u64 page_id, Web::Bindings::AgentType worker_type)
|
||||
Messages::WebContentClient::StartWorkerAgentResponse WebContentClient::start_worker_agent(u64 page_id, Web::HTML::WorkerAgentStartRequest request)
|
||||
{
|
||||
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
||||
auto request_server_handle = MUST(connect_new_request_server_client());
|
||||
auto image_decoder_handle = MUST(connect_new_image_decoder_client());
|
||||
auto worker_client = MUST(WebView::launch_web_worker_process(worker_type));
|
||||
auto worker_handle = MUST(worker_client->transport().release_for_transfer());
|
||||
return { move(worker_handle), move(request_server_handle), move(image_decoder_handle) };
|
||||
auto agent_id = WorkerProcessManager::the().start_worker_agent(*this, page_id, move(request));
|
||||
return { agent_id };
|
||||
}
|
||||
|
||||
return { IPC::TransportHandle {}, IPC::TransportHandle {}, IPC::TransportHandle {} };
|
||||
return { 0 };
|
||||
}
|
||||
|
||||
void WebContentClient::close_worker_agent(u64, Web::HTML::WorkerAgentId agent_id, Web::HTML::WorkerAgentOwnerToken owner_token)
|
||||
{
|
||||
WorkerProcessManager::the().close_worker_agent(*this, agent_id, owner_token);
|
||||
}
|
||||
|
||||
Optional<ViewImplementation&> WebContentClient::view_for_page_id(u64 page_id, SourceLocation location)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include <LibWeb/HTML/FileFilter.h>
|
||||
#include <LibWeb/HTML/SelectItem.h>
|
||||
#include <LibWeb/HTML/WebViewHints.h>
|
||||
#include <LibWeb/HTML/WorkerAgentTypes.h>
|
||||
#include <LibWeb/Page/EventResult.h>
|
||||
#include <LibWeb/StorageAPI/StorageEndpoint.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
|
|
@ -184,7 +185,8 @@ private:
|
|||
virtual void did_request_paste(u64 page_id) override;
|
||||
virtual void did_change_audio_play_state(u64 page_id, Web::HTML::AudioPlayState) override;
|
||||
virtual void did_update_navigation_buttons_state(u64 page_id, bool back_enabled, bool forward_enabled) override;
|
||||
virtual Messages::WebContentClient::RequestWorkerAgentResponse request_worker_agent(u64 page_id, Web::Bindings::AgentType worker_type) override;
|
||||
virtual Messages::WebContentClient::StartWorkerAgentResponse start_worker_agent(u64 page_id, Web::HTML::WorkerAgentStartRequest request) override;
|
||||
virtual void close_worker_agent(u64 page_id, Web::HTML::WorkerAgentId agent_id, Web::HTML::WorkerAgentOwnerToken owner_token) override;
|
||||
|
||||
Optional<ViewImplementation&> view_for_page_id(u64, SourceLocation = SourceLocation::current());
|
||||
|
||||
|
|
|
|||
429
Libraries/LibWebView/WorkerProcessManager.cpp
Normal file
429
Libraries/LibWebView/WorkerProcessManager.cpp
Normal file
|
|
@ -0,0 +1,429 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Ladybird contributors
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibIPC/File.h>
|
||||
#include <LibWeb/Worker/WebWorkerClient.h>
|
||||
#include <LibWebView/Application.h>
|
||||
#include <LibWebView/CookieJar.h>
|
||||
#include <LibWebView/HelperProcess.h>
|
||||
#include <LibWebView/WebContentClient.h>
|
||||
#include <LibWebView/WorkerProcessManager.h>
|
||||
|
||||
namespace WebView {
|
||||
|
||||
WorkerProcessManager& WorkerProcessManager::the()
|
||||
{
|
||||
static WorkerProcessManager manager;
|
||||
return manager;
|
||||
}
|
||||
|
||||
Web::HTML::WorkerAgentId WorkerProcessManager::start_worker_agent(WebContentClient& owner, u64 page_id, Web::HTML::WorkerAgentStartRequest request)
|
||||
{
|
||||
auto abstract_owner = Owner {
|
||||
.client = WebContentOwner {
|
||||
.client = owner,
|
||||
.page_id = page_id,
|
||||
},
|
||||
.token = request.owner_token,
|
||||
};
|
||||
return start_worker_agent(move(abstract_owner), move(request));
|
||||
}
|
||||
|
||||
Web::HTML::WorkerAgentId WorkerProcessManager::start_worker_agent(Web::HTML::WebWorkerClient& owner, Web::HTML::WorkerAgentStartRequest request)
|
||||
{
|
||||
auto abstract_owner = Owner {
|
||||
.client = WebWorkerOwner {
|
||||
.client = owner,
|
||||
},
|
||||
.token = request.owner_token,
|
||||
};
|
||||
return start_worker_agent(move(abstract_owner), move(request));
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/workers.html#dom-sharedworker
|
||||
Web::HTML::WorkerAgentId WorkerProcessManager::start_worker_agent(Owner owner, Web::HTML::WorkerAgentStartRequest request)
|
||||
{
|
||||
// 11.1. Let workerGlobalScope be null.
|
||||
if (request.agent_type == Web::Bindings::AgentType::SharedWorker) {
|
||||
SharedWorkerKey key {
|
||||
.storage_key = request.storage_key,
|
||||
.url = request.url,
|
||||
.name = request.name,
|
||||
};
|
||||
|
||||
// 11.2. For each scope in the list of all SharedWorkerGlobalScope objects: if workerStorageKey
|
||||
// equals outsideStorageKey, scope's closing flag is false, scope's constructor URL equals
|
||||
// urlRecord, and scope's name equals options["name"], then set workerGlobalScope to scope
|
||||
// and break.
|
||||
if (auto existing_agent_id = m_shared_workers.get(key); existing_agent_id.has_value()) {
|
||||
auto maybe_agent = m_agents.find(*existing_agent_id);
|
||||
if (maybe_agent != m_agents.end()) {
|
||||
auto& agent = maybe_agent->value;
|
||||
if (!agent.closing) {
|
||||
// FIXME: 11.3. If workerGlobalScope is not null, but the user agent has been
|
||||
// configured to disallow communication between the worker represented
|
||||
// by the workerGlobalScope and the scripts whose settings object is
|
||||
// outsideSettings, then set workerGlobalScope to null.
|
||||
|
||||
// 11.4. If workerGlobalScope is not null, and any of the following are true:
|
||||
// workerGlobalScope's type is not equal to options["type"];
|
||||
// workerGlobalScope's credentials is not equal to options["credentials"]; or
|
||||
// workerGlobalScope's extended lifetime is not equal to
|
||||
// options["extendedLifetime"], then queue a global task on the DOM
|
||||
// manipulation task source given worker's relevant global object to fire an
|
||||
// event named error at worker and abort these steps.
|
||||
// 11.5.3. If workerIsSecureContext is not callerIsSecureContext, queue a global task
|
||||
// on the DOM manipulation task source given worker's relevant global object
|
||||
// to fire an event named error at worker and abort these steps.
|
||||
// AD-HOC: Error firing is routed back over IPC via notify_worker_script_load_failure;
|
||||
// the spec's queue-global-task happens inside WorkerAgentParent in the
|
||||
// requesting process.
|
||||
// FIXME: The extendedLifetime comparison becomes observable once SharedWorkerOptions is implemented.
|
||||
if (agent.worker_type != request.type
|
||||
|| agent.credentials != request.credentials
|
||||
|| agent.extended_lifetime != request.extended_lifetime
|
||||
|| (agent.worker_is_secure_context.has_value() && *agent.worker_is_secure_context != request.caller_is_secure_context)) {
|
||||
notify_worker_script_load_failure(owner);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 11.5.8. Append the relevant owner to add given outsideSettings to
|
||||
// workerGlobalScope's owner set.
|
||||
// AD-HOC: The browser-side mirror lives in `agent.owners`; the worker-process
|
||||
// owner_set() is appended inside connect_shared_worker_impl (which also
|
||||
// handles steps 11.5.5-11.5.7).
|
||||
agent.owners.append(owner);
|
||||
agent.client->async_connect_shared_worker(move(request.outside_port), request.outside_settings);
|
||||
notify_worker_script_load_success(owner);
|
||||
return agent.id;
|
||||
}
|
||||
}
|
||||
|
||||
m_shared_workers.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
// 11.6. Otherwise, in parallel, run a worker given worker, urlRecord, outsideSettings, outsidePort,
|
||||
// and options.
|
||||
// AD-HOC: For DedicatedWorker there is no shared worker manager step; we always launch a fresh
|
||||
// worker process here.
|
||||
auto agent_id = ++m_next_agent_id;
|
||||
auto client = MUST(launch_web_worker_process(request.agent_type));
|
||||
|
||||
client->on_worker_close = [this, agent_id] {
|
||||
worker_did_close(agent_id);
|
||||
};
|
||||
client->on_worker_script_load_success = [this, agent_id](bool worker_is_secure_context) {
|
||||
worker_did_finish_loading_script(agent_id, worker_is_secure_context);
|
||||
};
|
||||
client->on_worker_script_load_failure = [this, agent_id] {
|
||||
worker_did_fail_loading_script(agent_id);
|
||||
};
|
||||
client->on_worker_exception = [this, agent_id](String message, String filename, u32 lineno, u32 colno) {
|
||||
worker_did_report_exception(agent_id, move(message), move(filename), lineno, colno);
|
||||
};
|
||||
client->on_worker_died = [this, agent_id] {
|
||||
worker_did_die(agent_id);
|
||||
};
|
||||
client->on_request_cookie = [](URL::URL const& url, HTTP::Cookie::Source source) {
|
||||
HTTP::Cookie::VersionedCookie cookie;
|
||||
cookie.cookie = Application::cookie_jar().get_cookie(url, source);
|
||||
return cookie;
|
||||
};
|
||||
client->on_request_file = [this, agent_id](ByteString path, i32 request_id) {
|
||||
worker_did_request_file(agent_id, move(path), request_id);
|
||||
};
|
||||
client->on_post_broadcast_channel_message = [this, agent_id](Web::HTML::BroadcastChannelMessage message) {
|
||||
worker_did_post_broadcast_channel_message(agent_id, move(message));
|
||||
};
|
||||
client->on_start_worker_agent = [this, client = client.ptr()](Web::HTML::WorkerAgentStartRequest request) {
|
||||
return start_worker_agent(*client, move(request));
|
||||
};
|
||||
client->on_close_worker_agent = [this, client = client.ptr()](Web::HTML::WorkerAgentId agent_id, Web::HTML::WorkerAgentOwnerToken owner_token) {
|
||||
close_worker_agent(*client, agent_id, owner_token);
|
||||
};
|
||||
|
||||
auto request_server_handle = MUST(connect_new_request_server_client());
|
||||
auto image_decoder_handle = MUST(connect_new_image_decoder_client());
|
||||
client->async_connect_to_request_server(move(request_server_handle));
|
||||
client->async_connect_to_image_decoder(move(image_decoder_handle));
|
||||
|
||||
Vector<Owner> owners;
|
||||
owners.append(owner);
|
||||
|
||||
// AD-HOC: Seed worker_is_secure_context with the caller's value so reuse requests arriving before
|
||||
// the worker finishes loading still get a mismatch check.
|
||||
// worker_did_finish_loading_script overwrites this with the worker's actual value (which
|
||||
// inherits from outside settings, so should match).
|
||||
WorkerAgent agent {
|
||||
.id = agent_id,
|
||||
.client = client,
|
||||
.agent_type = request.agent_type,
|
||||
.worker_type = request.type,
|
||||
.credentials = request.credentials,
|
||||
.extended_lifetime = request.extended_lifetime,
|
||||
.worker_is_secure_context = request.caller_is_secure_context,
|
||||
.shared_worker_key = {},
|
||||
.owners = move(owners),
|
||||
};
|
||||
|
||||
if (request.agent_type == Web::Bindings::AgentType::SharedWorker) {
|
||||
agent.shared_worker_key = SharedWorkerKey {
|
||||
.storage_key = request.storage_key,
|
||||
.url = request.url,
|
||||
.name = request.name,
|
||||
};
|
||||
m_shared_workers.set(*agent.shared_worker_key, agent_id);
|
||||
}
|
||||
|
||||
m_agents.set(agent_id, move(agent));
|
||||
client->async_start_worker(request.url, request.type, request.credentials, request.name, move(request.outside_port), request.outside_settings, request.agent_type);
|
||||
|
||||
return agent_id;
|
||||
}
|
||||
|
||||
void WorkerProcessManager::close_worker_agent(WebContentClient& client, Web::HTML::WorkerAgentId agent_id, Web::HTML::WorkerAgentOwnerToken owner_token)
|
||||
{
|
||||
Owner identity {
|
||||
.client = WebContentOwner { .client = client },
|
||||
.token = owner_token,
|
||||
};
|
||||
remove_owner(agent_id, identity);
|
||||
}
|
||||
|
||||
void WorkerProcessManager::close_worker_agent(Web::HTML::WebWorkerClient& client, Web::HTML::WorkerAgentId agent_id, Web::HTML::WorkerAgentOwnerToken owner_token)
|
||||
{
|
||||
Owner identity {
|
||||
.client = WebWorkerOwner { .client = client },
|
||||
.token = owner_token,
|
||||
};
|
||||
remove_owner(agent_id, identity);
|
||||
}
|
||||
|
||||
void WorkerProcessManager::remove_web_content_owner(WebContentClient& client)
|
||||
{
|
||||
Vector<Web::HTML::WorkerAgentId> agents_to_close;
|
||||
for (auto& entry : m_agents) {
|
||||
auto& agent = entry.value;
|
||||
agent.owners.remove_all_matching([&](Owner const& owner) {
|
||||
auto const* web_content_owner = owner.client.get_pointer<WebContentOwner>();
|
||||
return web_content_owner && web_content_owner->client.ptr() == &client;
|
||||
});
|
||||
if (agent.owners.is_empty())
|
||||
agents_to_close.append(agent.id);
|
||||
}
|
||||
|
||||
for (auto agent_id : agents_to_close)
|
||||
remove_agent(agent_id);
|
||||
}
|
||||
|
||||
void WorkerProcessManager::broadcast_channel_message_from_web_content(Web::HTML::BroadcastChannelMessage const& message)
|
||||
{
|
||||
for (auto& entry : m_agents) {
|
||||
auto& agent = entry.value;
|
||||
if (agent.client->pid() == message.source_process_id)
|
||||
continue;
|
||||
agent.client->async_broadcast_channel_message(message);
|
||||
}
|
||||
}
|
||||
|
||||
void WorkerProcessManager::notify_worker_script_load_success(Owner const& owner)
|
||||
{
|
||||
owner.client.visit(
|
||||
[&](WebContentOwner const& web_content_owner) {
|
||||
if (web_content_owner.client)
|
||||
web_content_owner.client->async_did_worker_agent_finish_loading_script(owner.token);
|
||||
},
|
||||
[&](WebWorkerOwner const& web_worker_owner) {
|
||||
web_worker_owner.client->async_did_worker_agent_finish_loading_script(owner.token);
|
||||
});
|
||||
}
|
||||
|
||||
void WorkerProcessManager::notify_worker_script_load_failure(Owner const& owner)
|
||||
{
|
||||
owner.client.visit(
|
||||
[&](WebContentOwner const& web_content_owner) {
|
||||
if (web_content_owner.client)
|
||||
web_content_owner.client->async_did_worker_agent_fail_loading_script(owner.token);
|
||||
},
|
||||
[&](WebWorkerOwner const& web_worker_owner) {
|
||||
web_worker_owner.client->async_did_worker_agent_fail_loading_script(owner.token);
|
||||
});
|
||||
}
|
||||
|
||||
void WorkerProcessManager::notify_worker_exception(Owner const& owner, String const& message, String const& filename, u32 lineno, u32 colno)
|
||||
{
|
||||
owner.client.visit(
|
||||
[&](WebContentOwner const& web_content_owner) {
|
||||
if (web_content_owner.client)
|
||||
web_content_owner.client->async_did_worker_agent_report_exception(owner.token, message, filename, lineno, colno);
|
||||
},
|
||||
[&](WebWorkerOwner const& web_worker_owner) {
|
||||
web_worker_owner.client->async_did_worker_agent_report_exception(owner.token, message, filename, lineno, colno);
|
||||
});
|
||||
}
|
||||
|
||||
void WorkerProcessManager::notify_worker_close(Owner const& owner)
|
||||
{
|
||||
owner.client.visit(
|
||||
[&](WebContentOwner const& web_content_owner) {
|
||||
if (web_content_owner.client)
|
||||
web_content_owner.client->async_did_worker_agent_close(owner.token);
|
||||
},
|
||||
[&](WebWorkerOwner const& web_worker_owner) {
|
||||
web_worker_owner.client->async_did_worker_agent_close(owner.token);
|
||||
});
|
||||
}
|
||||
|
||||
void WorkerProcessManager::worker_did_finish_loading_script(Web::HTML::WorkerAgentId agent_id, bool worker_is_secure_context)
|
||||
{
|
||||
auto maybe_agent = m_agents.find(agent_id);
|
||||
if (maybe_agent == m_agents.end())
|
||||
return;
|
||||
|
||||
auto& agent = maybe_agent->value;
|
||||
agent.worker_is_secure_context = worker_is_secure_context;
|
||||
|
||||
for (auto const& owner : agent.owners)
|
||||
notify_worker_script_load_success(owner);
|
||||
}
|
||||
|
||||
void WorkerProcessManager::worker_did_fail_loading_script(Web::HTML::WorkerAgentId agent_id)
|
||||
{
|
||||
auto maybe_agent = m_agents.find(agent_id);
|
||||
if (maybe_agent == m_agents.end())
|
||||
return;
|
||||
|
||||
auto& agent = maybe_agent->value;
|
||||
if (agent.closing)
|
||||
return;
|
||||
|
||||
agent.closing = true;
|
||||
auto owners = agent.owners;
|
||||
for (auto const& owner : owners)
|
||||
notify_worker_script_load_failure(owner);
|
||||
|
||||
Core::deferred_invoke([this, agent_id] {
|
||||
remove_agent(agent_id);
|
||||
});
|
||||
}
|
||||
|
||||
void WorkerProcessManager::worker_did_report_exception(Web::HTML::WorkerAgentId agent_id, String message, String filename, u32 lineno, u32 colno)
|
||||
{
|
||||
auto maybe_agent = m_agents.find(agent_id);
|
||||
if (maybe_agent == m_agents.end())
|
||||
return;
|
||||
|
||||
for (auto const& owner : maybe_agent->value.owners)
|
||||
notify_worker_exception(owner, message, filename, lineno, colno);
|
||||
}
|
||||
|
||||
void WorkerProcessManager::worker_did_close(Web::HTML::WorkerAgentId agent_id)
|
||||
{
|
||||
auto maybe_agent = m_agents.find(agent_id);
|
||||
if (maybe_agent == m_agents.end())
|
||||
return;
|
||||
|
||||
auto& agent = maybe_agent->value;
|
||||
if (agent.closing)
|
||||
return;
|
||||
|
||||
agent.closing = true;
|
||||
auto owners = agent.owners;
|
||||
for (auto const& owner : owners)
|
||||
notify_worker_close(owner);
|
||||
|
||||
Core::deferred_invoke([this, agent_id] {
|
||||
remove_agent(agent_id);
|
||||
});
|
||||
}
|
||||
|
||||
void WorkerProcessManager::worker_did_die(Web::HTML::WorkerAgentId agent_id)
|
||||
{
|
||||
worker_did_close(agent_id);
|
||||
}
|
||||
|
||||
void WorkerProcessManager::worker_did_request_file(Web::HTML::WorkerAgentId agent_id, ByteString path, i32 request_id)
|
||||
{
|
||||
auto maybe_agent = m_agents.find(agent_id);
|
||||
if (maybe_agent == m_agents.end())
|
||||
return;
|
||||
|
||||
auto file = Core::File::open(path, Core::File::OpenMode::Read);
|
||||
if (file.is_error())
|
||||
maybe_agent->value.client->async_handle_file_return(file.error().code(), {}, request_id);
|
||||
else
|
||||
maybe_agent->value.client->async_handle_file_return(0, IPC::File::adopt_file(file.release_value()), request_id);
|
||||
}
|
||||
|
||||
void WorkerProcessManager::worker_did_post_broadcast_channel_message(Web::HTML::WorkerAgentId agent_id, Web::HTML::BroadcastChannelMessage message)
|
||||
{
|
||||
WebContentClient::for_each_client([&](auto& client) {
|
||||
if (client.pid() == message.source_process_id)
|
||||
return IterationDecision::Continue;
|
||||
client.async_broadcast_channel_message(message);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
for (auto& entry : m_agents) {
|
||||
if (entry.key == agent_id)
|
||||
continue;
|
||||
auto& agent = entry.value;
|
||||
if (agent.client->pid() == message.source_process_id)
|
||||
continue;
|
||||
agent.client->async_broadcast_channel_message(message);
|
||||
}
|
||||
}
|
||||
|
||||
void WorkerProcessManager::remove_agent(Web::HTML::WorkerAgentId agent_id)
|
||||
{
|
||||
auto maybe_agent = m_agents.find(agent_id);
|
||||
if (maybe_agent == m_agents.end())
|
||||
return;
|
||||
|
||||
auto agent = move(maybe_agent->value);
|
||||
m_agents.remove(agent_id);
|
||||
|
||||
if (agent.shared_worker_key.has_value())
|
||||
m_shared_workers.remove(*agent.shared_worker_key);
|
||||
|
||||
agent.closing = true;
|
||||
if (agent.client->is_open())
|
||||
agent.client->async_close_worker();
|
||||
}
|
||||
|
||||
void WorkerProcessManager::remove_owner(Web::HTML::WorkerAgentId agent_id, Owner const& identity)
|
||||
{
|
||||
auto maybe_agent = m_agents.find(agent_id);
|
||||
if (maybe_agent == m_agents.end())
|
||||
return;
|
||||
|
||||
auto& agent = maybe_agent->value;
|
||||
auto agent_owned_by_specified_owner = agent.owners.remove_all_matching([&](Owner const& owner) {
|
||||
if (owner.token != identity.token)
|
||||
return false;
|
||||
|
||||
if (auto const* incoming = identity.client.get_pointer<WebContentOwner>()) {
|
||||
auto const* candidate = owner.client.get_pointer<WebContentOwner>();
|
||||
return candidate && candidate->client.ptr() == incoming->client.ptr();
|
||||
}
|
||||
if (auto const* incoming = identity.client.get_pointer<WebWorkerOwner>()) {
|
||||
auto const* candidate = owner.client.get_pointer<WebWorkerOwner>();
|
||||
return candidate && candidate->client.ptr() == incoming->client.ptr();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!agent_owned_by_specified_owner)
|
||||
return;
|
||||
|
||||
if (agent.agent_type == Web::Bindings::AgentType::DedicatedWorker || agent.owners.is_empty())
|
||||
remove_agent(agent_id);
|
||||
}
|
||||
|
||||
}
|
||||
113
Libraries/LibWebView/WorkerProcessManager.h
Normal file
113
Libraries/LibWebView/WorkerProcessManager.h
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Ladybird contributors
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibWeb/HTML/BroadcastChannelMessage.h>
|
||||
#include <LibWeb/HTML/WorkerAgentTypes.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class WebWorkerClient;
|
||||
|
||||
}
|
||||
|
||||
namespace WebView {
|
||||
|
||||
class WorkerProcessManager {
|
||||
public:
|
||||
static WorkerProcessManager& the();
|
||||
|
||||
struct SharedWorkerKey {
|
||||
Web::StorageAPI::StorageKey storage_key;
|
||||
URL::URL url;
|
||||
String name;
|
||||
|
||||
bool operator==(SharedWorkerKey const&) const = default;
|
||||
};
|
||||
|
||||
Web::HTML::WorkerAgentId start_worker_agent(WebContentClient&, u64 page_id, Web::HTML::WorkerAgentStartRequest);
|
||||
Web::HTML::WorkerAgentId start_worker_agent(Web::HTML::WebWorkerClient&, Web::HTML::WorkerAgentStartRequest);
|
||||
|
||||
void close_worker_agent(WebContentClient&, Web::HTML::WorkerAgentId, Web::HTML::WorkerAgentOwnerToken);
|
||||
void close_worker_agent(Web::HTML::WebWorkerClient&, Web::HTML::WorkerAgentId, Web::HTML::WorkerAgentOwnerToken);
|
||||
void remove_web_content_owner(WebContentClient&);
|
||||
|
||||
void broadcast_channel_message_from_web_content(Web::HTML::BroadcastChannelMessage const&);
|
||||
|
||||
private:
|
||||
WorkerProcessManager() = default;
|
||||
|
||||
struct WebContentOwner {
|
||||
WeakPtr<WebContentClient> client;
|
||||
u64 page_id { 0 };
|
||||
};
|
||||
|
||||
struct WebWorkerOwner {
|
||||
NonnullRefPtr<Web::HTML::WebWorkerClient> client;
|
||||
};
|
||||
|
||||
struct Owner {
|
||||
Variant<WebContentOwner, WebWorkerOwner> client;
|
||||
Web::HTML::WorkerAgentOwnerToken token { 0 };
|
||||
};
|
||||
|
||||
Web::HTML::WorkerAgentId start_worker_agent(Owner, Web::HTML::WorkerAgentStartRequest);
|
||||
|
||||
void notify_worker_script_load_success(Owner const&);
|
||||
void notify_worker_script_load_failure(Owner const&);
|
||||
void notify_worker_exception(Owner const&, String const& message, String const& filename, u32 lineno, u32 colno);
|
||||
void notify_worker_close(Owner const&);
|
||||
|
||||
void worker_did_finish_loading_script(Web::HTML::WorkerAgentId, bool worker_is_secure_context);
|
||||
void worker_did_fail_loading_script(Web::HTML::WorkerAgentId);
|
||||
void worker_did_report_exception(Web::HTML::WorkerAgentId, String message, String filename, u32 lineno, u32 colno);
|
||||
void worker_did_close(Web::HTML::WorkerAgentId);
|
||||
void worker_did_die(Web::HTML::WorkerAgentId);
|
||||
void worker_did_request_file(Web::HTML::WorkerAgentId, ByteString path, i32 request_id);
|
||||
void worker_did_post_broadcast_channel_message(Web::HTML::WorkerAgentId, Web::HTML::BroadcastChannelMessage);
|
||||
|
||||
void remove_agent(Web::HTML::WorkerAgentId);
|
||||
void remove_owner(Web::HTML::WorkerAgentId, Owner const& identity);
|
||||
|
||||
struct WorkerAgent {
|
||||
Web::HTML::WorkerAgentId id { 0 };
|
||||
NonnullRefPtr<Web::HTML::WebWorkerClient> client;
|
||||
Web::Bindings::AgentType agent_type { Web::Bindings::AgentType::DedicatedWorker };
|
||||
Web::Bindings::WorkerType worker_type { Web::Bindings::WorkerType::Classic };
|
||||
Web::Bindings::RequestCredentials credentials { Web::Bindings::RequestCredentials::SameOrigin };
|
||||
bool extended_lifetime { false };
|
||||
Optional<bool> worker_is_secure_context;
|
||||
bool closing { false };
|
||||
Optional<SharedWorkerKey> shared_worker_key;
|
||||
Vector<Owner> owners;
|
||||
};
|
||||
|
||||
Web::HTML::WorkerAgentId m_next_agent_id { 0 };
|
||||
HashMap<Web::HTML::WorkerAgentId, WorkerAgent> m_agents;
|
||||
HashMap<SharedWorkerKey, Web::HTML::WorkerAgentId> m_shared_workers;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace AK {
|
||||
|
||||
template<>
|
||||
struct Traits<WebView::WorkerProcessManager::SharedWorkerKey> : public DefaultTraits<WebView::WorkerProcessManager::SharedWorkerKey> {
|
||||
static unsigned hash(WebView::WorkerProcessManager::SharedWorkerKey const& key)
|
||||
{
|
||||
return pair_int_hash(pair_int_hash(Traits<Web::StorageAPI::StorageKey>::hash(key.storage_key), Traits<URL::URL>::hash(key.url)), key.name.hash());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -47,6 +47,7 @@
|
|||
#include <LibWeb/HTML/Storage.h>
|
||||
#include <LibWeb/HTML/TraversableNavigable.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/HTML/WorkerAgentParent.h>
|
||||
#include <LibWeb/Infra/Strings.h>
|
||||
#include <LibWeb/Layout/Viewport.h>
|
||||
#include <LibWeb/Loader/ContentBlocker.h>
|
||||
|
|
@ -58,7 +59,6 @@
|
|||
#include <LibWeb/Painting/ViewportPaintable.h>
|
||||
#include <LibWeb/PermissionsPolicy/AutoplayAllowlist.h>
|
||||
#include <LibWeb/Platform/EventLoopPlugin.h>
|
||||
#include <LibWeb/Worker/WebWorkerClient.h>
|
||||
#include <LibWebView/Attribute.h>
|
||||
#include <LibWebView/ViewImplementation.h>
|
||||
#include <WebContent/CompositorConnection.h>
|
||||
|
|
@ -1467,12 +1467,26 @@ void ConnectionFromClient::cookies_changed(u64 page_id, Vector<HTTP::Cookie::Coo
|
|||
void ConnectionFromClient::broadcast_channel_message(Web::HTML::BroadcastChannelMessage message)
|
||||
{
|
||||
Web::HTML::BroadcastChannel::deliver_message_locally(message);
|
||||
Web::HTML::WebWorkerClient::for_each_client([&](auto& client) {
|
||||
if (client.pid() == message.source_process_id)
|
||||
return IterationDecision::Continue;
|
||||
client.async_broadcast_channel_message(message);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
void ConnectionFromClient::did_worker_agent_finish_loading_script(Web::HTML::WorkerAgentOwnerToken owner_token)
|
||||
{
|
||||
Web::HTML::WorkerAgentParent::did_finish_loading_worker_script(owner_token);
|
||||
}
|
||||
|
||||
void ConnectionFromClient::did_worker_agent_fail_loading_script(Web::HTML::WorkerAgentOwnerToken owner_token)
|
||||
{
|
||||
Web::HTML::WorkerAgentParent::did_fail_loading_worker_script(owner_token);
|
||||
}
|
||||
|
||||
void ConnectionFromClient::did_worker_agent_report_exception(Web::HTML::WorkerAgentOwnerToken owner_token, String message, String filename, u32 lineno, u32 colno)
|
||||
{
|
||||
Web::HTML::WorkerAgentParent::did_report_worker_exception(owner_token, move(message), move(filename), lineno, colno);
|
||||
}
|
||||
|
||||
void ConnectionFromClient::did_worker_agent_close(Web::HTML::WorkerAgentOwnerToken owner_token)
|
||||
{
|
||||
Web::HTML::WorkerAgentParent::did_close_worker(owner_token);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/speculative-loading.html#nav-traversal-ui:close-a-top-level-traversable
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include <LibWeb/CSS/PreferredMotion.h>
|
||||
#include <LibWeb/Compositor/Types.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/WorkerAgentTypes.h>
|
||||
#include <LibWeb/Loader/FileRequest.h>
|
||||
#include <LibWeb/Page/EventResult.h>
|
||||
#include <LibWeb/Page/InputEvent.h>
|
||||
|
|
@ -177,6 +178,10 @@ private:
|
|||
virtual void set_document_cookie_version_index(u64 page_id, i64 document_id, Core::SharedVersionIndex document_index) override;
|
||||
virtual void cookies_changed(u64 page_id, Vector<HTTP::Cookie::Cookie>) override;
|
||||
virtual void broadcast_channel_message(Web::HTML::BroadcastChannelMessage message) override;
|
||||
virtual void did_worker_agent_finish_loading_script(Web::HTML::WorkerAgentOwnerToken owner_token) override;
|
||||
virtual void did_worker_agent_fail_loading_script(Web::HTML::WorkerAgentOwnerToken owner_token) override;
|
||||
virtual void did_worker_agent_report_exception(Web::HTML::WorkerAgentOwnerToken owner_token, String message, String filename, u32 lineno, u32 colno) override;
|
||||
virtual void did_worker_agent_close(Web::HTML::WorkerAgentOwnerToken owner_token) override;
|
||||
|
||||
virtual void request_close(u64 page_id) override;
|
||||
|
||||
|
|
|
|||
|
|
@ -775,15 +775,20 @@ void PageClient::page_did_change_audio_play_state(Web::HTML::AudioPlayState play
|
|||
client().async_did_change_audio_play_state(m_id, play_state);
|
||||
}
|
||||
|
||||
Web::PageClient::WorkerAgentResponse PageClient::request_worker_agent(Web::Bindings::AgentType type)
|
||||
Web::HTML::WorkerAgentId PageClient::start_worker_agent(Web::HTML::WorkerAgentStartRequest&& request)
|
||||
{
|
||||
auto response = client().send_sync_but_allow_failure<Messages::WebContentClient::RequestWorkerAgent>(m_id, type);
|
||||
auto response = client().send_sync_but_allow_failure<Messages::WebContentClient::StartWorkerAgent>(m_id, move(request));
|
||||
if (!response) {
|
||||
dbgln("WebContent client disconnected during RequestWorkerAgent. Exiting peacefully.");
|
||||
dbgln("WebContent client disconnected during StartWorkerAgent. Exiting peacefully.");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
return { response->take_handle(), response->take_request_server_handle(), response->take_image_decoder_handle() };
|
||||
return response->agent_id();
|
||||
}
|
||||
|
||||
void PageClient::close_worker_agent(Web::HTML::WorkerAgentId agent_id, Web::HTML::WorkerAgentOwnerToken owner_token)
|
||||
{
|
||||
client().async_close_worker_agent(m_id, agent_id, owner_token);
|
||||
}
|
||||
|
||||
void PageClient::page_did_mutate_dom(FlyString const& type, Web::DOM::Node const& target, Web::DOM::NodeList& added_nodes, Web::DOM::NodeList& removed_nodes, GC::Ptr<Web::DOM::Node>, GC::Ptr<Web::DOM::Node>, Optional<String> const& attribute_name)
|
||||
|
|
|
|||
|
|
@ -197,7 +197,8 @@ private:
|
|||
virtual void page_did_request_clipboard_entries(u64 request_id) override;
|
||||
virtual void page_did_request_paste() override;
|
||||
virtual void page_did_change_audio_play_state(Web::HTML::AudioPlayState) override;
|
||||
virtual WorkerAgentResponse request_worker_agent(Web::Bindings::AgentType) override;
|
||||
virtual Web::HTML::WorkerAgentId start_worker_agent(Web::HTML::WorkerAgentStartRequest&&) override;
|
||||
virtual void close_worker_agent(Web::HTML::WorkerAgentId, Web::HTML::WorkerAgentOwnerToken) override;
|
||||
virtual void page_did_mutate_dom(FlyString const& type, Web::DOM::Node const& target, Web::DOM::NodeList& added_nodes, Web::DOM::NodeList& removed_nodes, GC::Ptr<Web::DOM::Node> previous_sibling, GC::Ptr<Web::DOM::Node> next_sibling, Optional<String> const& attribute_name) override;
|
||||
virtual void page_did_take_screenshot(Gfx::ShareableBitmap const& screenshot) override;
|
||||
virtual void received_message_from_web_ui(String const& name, JS::Value data) override;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include <LibWeb/HTML/SelectedFile.h>
|
||||
#include <LibWeb/HTML/SelectItem.h>
|
||||
#include <LibWeb/HTML/WebViewHints.h>
|
||||
#include <LibWeb/HTML/WorkerAgentTypes.h>
|
||||
#include <LibWeb/Page/EventResult.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
#include <LibWebView/Attribute.h>
|
||||
|
|
@ -142,5 +143,6 @@ endpoint WebContentClient
|
|||
|
||||
did_find_in_page(u64 page_id, size_t current_match_index, Optional<size_t> total_match_count) =|
|
||||
|
||||
request_worker_agent(u64 page_id, Web::Bindings::AgentType worker_type) => (IPC::TransportHandle handle, IPC::TransportHandle request_server_handle, IPC::TransportHandle image_decoder_handle) // FIXME: Add required attributes to select a SharedWorker Agent
|
||||
start_worker_agent(u64 page_id, Web::HTML::WorkerAgentStartRequest request) => (Web::HTML::WorkerAgentId agent_id)
|
||||
close_worker_agent(u64 page_id, Web::HTML::WorkerAgentId agent_id, Web::HTML::WorkerAgentOwnerToken owner_token) =|
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <LibWeb/HTML/BroadcastChannelMessage.h>
|
||||
#include <LibWeb/HTML/SelectedFile.h>
|
||||
#include <LibWeb/HTML/VisibilityState.h>
|
||||
#include <LibWeb/HTML/WorkerAgentTypes.h>
|
||||
#include <LibWeb/Page/InputEvent.h>
|
||||
#include <LibWeb/Page/ViewportIsFullscreen.h>
|
||||
#include <LibWeb/WebDriver/ExecuteScript.h>
|
||||
|
|
@ -148,6 +149,11 @@ endpoint WebContentServer
|
|||
cookies_changed(u64 page_id, Vector<HTTP::Cookie::Cookie> cookies) =|
|
||||
broadcast_channel_message(Web::HTML::BroadcastChannelMessage message) =|
|
||||
|
||||
did_worker_agent_finish_loading_script(Web::HTML::WorkerAgentOwnerToken owner_token) =|
|
||||
did_worker_agent_fail_loading_script(Web::HTML::WorkerAgentOwnerToken owner_token) =|
|
||||
did_worker_agent_report_exception(Web::HTML::WorkerAgentOwnerToken owner_token, String message, String filename, u32 lineno, u32 colno) =|
|
||||
did_worker_agent_close(Web::HTML::WorkerAgentOwnerToken owner_token) =|
|
||||
|
||||
request_close(u64 page_id) =|
|
||||
|
||||
exit_fullscreen(u64 page_id) =|
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibWeb/HTML/BroadcastChannel.h>
|
||||
#include <LibWeb/Worker/WebWorkerClient.h>
|
||||
#include <LibWeb/HTML/WorkerAgentParent.h>
|
||||
#include <WebWorker/ConnectionFromClient.h>
|
||||
#include <WebWorker/PageHost.h>
|
||||
#include <WebWorker/WorkerHost.h>
|
||||
|
|
@ -44,19 +44,11 @@ void ConnectionFromClient::die()
|
|||
|
||||
void ConnectionFromClient::request_file(Web::FileRequest request)
|
||||
{
|
||||
// FIXME: Route this to FSAS or browser process as appropriate instead of allowing
|
||||
// the WebWorker process filesystem access
|
||||
auto path = request.path();
|
||||
auto request_id = ++last_id;
|
||||
|
||||
auto path = request.path();
|
||||
m_requested_files.set(request_id, move(request));
|
||||
|
||||
auto file = Core::File::open(path, Core::File::OpenMode::Read);
|
||||
|
||||
if (file.is_error())
|
||||
handle_file_return(file.error().code(), {}, request_id);
|
||||
else
|
||||
handle_file_return(0, IPC::File::adopt_file(file.release_value()), request_id);
|
||||
async_did_request_file(path, request_id);
|
||||
}
|
||||
|
||||
ConnectionFromClient::ConnectionFromClient(NonnullOwnPtr<IPC::Transport> transport)
|
||||
|
|
@ -89,6 +81,13 @@ void ConnectionFromClient::start_worker(URL::URL url, Web::Bindings::WorkerType
|
|||
m_worker_host->run(page(), move(implicit_port), outside_settings, credentials, is_shared);
|
||||
}
|
||||
|
||||
void ConnectionFromClient::connect_shared_worker(Web::HTML::TransferDataEncoder message_port, Web::HTML::SerializedEnvironmentSettingsObject outside_settings)
|
||||
{
|
||||
if (!m_worker_host)
|
||||
return;
|
||||
m_worker_host->connect_shared_worker(move(message_port), move(outside_settings));
|
||||
}
|
||||
|
||||
void ConnectionFromClient::handle_file_return(i32 error, Optional<IPC::File> file, i32 request_id)
|
||||
{
|
||||
auto file_request = m_requested_files.take(request_id);
|
||||
|
|
@ -99,15 +98,29 @@ void ConnectionFromClient::handle_file_return(i32 error, Optional<IPC::File> fil
|
|||
file_request.value().on_file_request_finish(error != 0 ? Error::from_errno(error) : ErrorOr<i32> { file->take_fd() });
|
||||
}
|
||||
|
||||
void ConnectionFromClient::did_worker_agent_finish_loading_script(Web::HTML::WorkerAgentOwnerToken owner_token)
|
||||
{
|
||||
Web::HTML::WorkerAgentParent::did_finish_loading_worker_script(owner_token);
|
||||
}
|
||||
|
||||
void ConnectionFromClient::did_worker_agent_fail_loading_script(Web::HTML::WorkerAgentOwnerToken owner_token)
|
||||
{
|
||||
Web::HTML::WorkerAgentParent::did_fail_loading_worker_script(owner_token);
|
||||
}
|
||||
|
||||
void ConnectionFromClient::did_worker_agent_report_exception(Web::HTML::WorkerAgentOwnerToken owner_token, String message, String filename, u32 lineno, u32 colno)
|
||||
{
|
||||
Web::HTML::WorkerAgentParent::did_report_worker_exception(owner_token, move(message), move(filename), lineno, colno);
|
||||
}
|
||||
|
||||
void ConnectionFromClient::did_worker_agent_close(Web::HTML::WorkerAgentOwnerToken owner_token)
|
||||
{
|
||||
Web::HTML::WorkerAgentParent::did_close_worker(owner_token);
|
||||
}
|
||||
|
||||
void ConnectionFromClient::broadcast_channel_message(Web::HTML::BroadcastChannelMessage message)
|
||||
{
|
||||
Web::HTML::BroadcastChannel::deliver_message_locally(message);
|
||||
Web::HTML::WebWorkerClient::for_each_client([&](auto& client) {
|
||||
if (client.pid() == message.source_process_id)
|
||||
return IterationDecision::Continue;
|
||||
client.async_broadcast_channel_message(message);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <LibJS/Forward.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/BroadcastChannelMessage.h>
|
||||
#include <LibWeb/HTML/WorkerAgentTypes.h>
|
||||
#include <LibWeb/Loader/FileRequest.h>
|
||||
#include <LibWeb/Worker/WebWorkerClientEndpoint.h>
|
||||
#include <LibWeb/Worker/WebWorkerServerEndpoint.h>
|
||||
|
|
@ -48,7 +49,12 @@ private:
|
|||
virtual void connect_to_request_server(IPC::TransportHandle handle) override;
|
||||
virtual void connect_to_image_decoder(IPC::TransportHandle handle) override;
|
||||
virtual void start_worker(URL::URL url, Web::Bindings::WorkerType type, Web::Bindings::RequestCredentials credentials, String name, Web::HTML::TransferDataEncoder, Web::HTML::SerializedEnvironmentSettingsObject, Web::Bindings::AgentType) override;
|
||||
virtual void connect_shared_worker(Web::HTML::TransferDataEncoder, Web::HTML::SerializedEnvironmentSettingsObject) override;
|
||||
virtual void handle_file_return(i32 error, Optional<IPC::File> file, i32 request_id) override;
|
||||
virtual void did_worker_agent_finish_loading_script(Web::HTML::WorkerAgentOwnerToken owner_token) override;
|
||||
virtual void did_worker_agent_fail_loading_script(Web::HTML::WorkerAgentOwnerToken owner_token) override;
|
||||
virtual void did_worker_agent_report_exception(Web::HTML::WorkerAgentOwnerToken owner_token, String message, String filename, u32 lineno, u32 colno) override;
|
||||
virtual void did_worker_agent_close(Web::HTML::WorkerAgentOwnerToken owner_token) override;
|
||||
virtual void broadcast_channel_message(Web::HTML::BroadcastChannelMessage message) override;
|
||||
|
||||
GC::Root<PageHost> m_page_host;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||
#include <LibWeb/HTML/WorkerAgentTypes.h>
|
||||
#include <WebWorker/ConnectionFromClient.h>
|
||||
#include <WebWorker/PageHost.h>
|
||||
|
||||
|
|
@ -107,15 +108,19 @@ void PageHost::request_file(Web::FileRequest request)
|
|||
m_client.request_file(move(request));
|
||||
}
|
||||
|
||||
Web::PageClient::WorkerAgentResponse PageHost::request_worker_agent(Web::Bindings::AgentType worker_type)
|
||||
Web::HTML::WorkerAgentId PageHost::start_worker_agent(Web::HTML::WorkerAgentStartRequest&& request)
|
||||
{
|
||||
auto response = m_client.request_worker_agent(worker_type);
|
||||
return { response.take_handle(), response.take_request_server_handle(), response.take_image_decoder_handle() };
|
||||
return m_client.start_worker_agent(move(request));
|
||||
}
|
||||
|
||||
void PageHost::did_finish_loading_worker_script()
|
||||
void PageHost::close_worker_agent(Web::HTML::WorkerAgentId agent_id, Web::HTML::WorkerAgentOwnerToken owner_token)
|
||||
{
|
||||
m_client.async_did_finish_loading_worker_script();
|
||||
m_client.async_close_worker_agent(agent_id, owner_token);
|
||||
}
|
||||
|
||||
void PageHost::did_finish_loading_worker_script(bool worker_is_secure_context)
|
||||
{
|
||||
m_client.async_did_finish_loading_worker_script(worker_is_secure_context);
|
||||
}
|
||||
|
||||
void PageHost::did_fail_loading_worker_script()
|
||||
|
|
|
|||
|
|
@ -40,13 +40,14 @@ public:
|
|||
virtual void page_did_report_worker_exception(String const& message, String const& filename, u32 lineno, u32 colno) override;
|
||||
virtual void page_did_post_broadcast_channel_message(Web::HTML::BroadcastChannelMessage const& message) override;
|
||||
virtual void request_file(Web::FileRequest) override;
|
||||
virtual WorkerAgentResponse request_worker_agent(Web::Bindings::AgentType) override;
|
||||
virtual Web::HTML::WorkerAgentId start_worker_agent(Web::HTML::WorkerAgentStartRequest&&) override;
|
||||
virtual void close_worker_agent(Web::HTML::WorkerAgentId, Web::HTML::WorkerAgentOwnerToken) override;
|
||||
virtual Web::DisplayListPlayerType display_list_player_type() const override { VERIFY_NOT_REACHED(); }
|
||||
virtual bool is_headless() const override { VERIFY_NOT_REACHED(); }
|
||||
virtual Queue<Web::QueuedInputEvent>& input_event_queue() override { VERIFY_NOT_REACHED(); }
|
||||
virtual void report_finished_handling_input_event([[maybe_unused]] u64 page_id, [[maybe_unused]] Web::EventResult event_was_handled) override { VERIFY_NOT_REACHED(); }
|
||||
virtual void request_frame() override { VERIFY_NOT_REACHED(); }
|
||||
void did_finish_loading_worker_script();
|
||||
void did_finish_loading_worker_script(bool worker_is_secure_context);
|
||||
void did_fail_loading_worker_script();
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/RefPtr.h>
|
||||
#include <LibIPC/File.h>
|
||||
#include <LibJS/Runtime/ConsoleObject.h>
|
||||
#include <LibWeb/Bindings/MessageEvent.h>
|
||||
|
|
@ -15,6 +16,7 @@
|
|||
#include <LibWeb/HTML/MessagePort.h>
|
||||
#include <LibWeb/HTML/Scripting/ClassicScript.h>
|
||||
#include <LibWeb/HTML/Scripting/EnvironmentSettingsSnapshot.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Scripting/Fetching.h>
|
||||
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
|
||||
#include <LibWeb/HTML/Scripting/WorkerEnvironmentSettingsObject.h>
|
||||
|
|
@ -51,6 +53,8 @@ static Web::HTML::WorkerGlobalScope::Owner relevant_owner_to_add(Web::HTML::Seri
|
|||
// https://html.spec.whatwg.org/multipage/workers.html#run-a-worker
|
||||
void WorkerHost::run(GC::Ref<Web::Page> page, Web::HTML::TransferDataEncoder message_port_data, Web::HTML::SerializedEnvironmentSettingsObject const& outside_settings_snapshot, Web::Bindings::RequestCredentials credentials, bool is_shared)
|
||||
{
|
||||
m_is_shared = is_shared;
|
||||
|
||||
// 1. Let is shared be true if worker is a SharedWorker object, and false otherwise.
|
||||
// 2. Let owner be the relevant owner to add given outside settings.
|
||||
auto owner = relevant_owner_to_add(outside_settings_snapshot);
|
||||
|
|
@ -73,6 +77,7 @@ void WorkerHost::run(GC::Ref<Web::Page> page, Web::HTML::TransferDataEncoder mes
|
|||
// 6. Let worker global scope be the global object of realm execution context's Realm component.
|
||||
// NOTE: This is the DedicatedWorkerGlobalScope or SharedWorkerGlobalScope object created in the previous step.
|
||||
GC::Ref<Web::HTML::WorkerGlobalScope> worker_global_scope = as<Web::HTML::WorkerGlobalScope>(realm_execution_context->realm->global_object());
|
||||
m_worker_global_scope = worker_global_scope;
|
||||
|
||||
// AD-HOC: The spec assumes when setting up the worker environment settings object that the URL is already set on
|
||||
// the worker global scope. This is not the case. This URL is only known after performing the fetch, and in
|
||||
|
|
@ -92,6 +97,7 @@ void WorkerHost::run(GC::Ref<Web::Page> page, Web::HTML::TransferDataEncoder mes
|
|||
// 7. Set up a worker environment settings object with realm execution context, outside settings, and
|
||||
// unsafeWorkerCreationTime, and let inside settings be the result.
|
||||
auto inside_settings = Web::HTML::WorkerEnvironmentSettingsObject::setup(page, move(realm_execution_context), outside_settings_snapshot, unsafe_worker_creation_time);
|
||||
m_inside_settings = inside_settings;
|
||||
|
||||
// AD-HOC: Create a console object for the worker.
|
||||
auto& console_object = *inside_settings->realm().intrinsics().console_object();
|
||||
|
|
@ -201,7 +207,8 @@ void WorkerHost::run(GC::Ref<Web::Page> page, Web::HTML::TransferDataEncoder mes
|
|||
auto perform_fetch = Web::HTML::create_perform_the_fetch_hook(inside_settings->heap(), move(perform_fetch_function));
|
||||
|
||||
// In both cases, let onComplete given script be the following steps:
|
||||
auto on_complete_function = [page, inside_settings, worker_global_scope, message_port_data = move(message_port_data), url = m_url, is_shared](GC::Ptr<Web::HTML::Script> script) mutable {
|
||||
RefPtr<WorkerHost> protected_this { *this };
|
||||
auto on_complete_function = [protected_this, page, inside_settings, worker_global_scope, message_port_data = move(message_port_data), outside_settings_snapshot, url = m_url, is_shared](GC::Ptr<Web::HTML::Script> script) mutable {
|
||||
auto& realm = inside_settings->realm();
|
||||
|
||||
// 1. If script is null or if script's error to rethrow is non-null, then:
|
||||
|
|
@ -220,20 +227,25 @@ void WorkerHost::run(GC::Ref<Web::Page> page, Web::HTML::TransferDataEncoder mes
|
|||
// FIXME: 2. Associate worker with worker global scope.
|
||||
// What does this even mean?
|
||||
|
||||
// 3. Let inside port be a new MessagePort object in inside settings's realm.
|
||||
auto inside_port = Web::HTML::MessagePort::create(realm);
|
||||
GC::Ptr<Web::HTML::MessagePort> inside_port;
|
||||
|
||||
// 3. Let inside port be a new MessagePort object in inside settings's realm.
|
||||
// 4. If is shared is false, then:
|
||||
// 5. Entangle outside port and inside port.
|
||||
// AD-HOC: For shared workers we defer the inside port creation (step 3) and entanglement
|
||||
// (step 5) to connect_shared_worker_impl. Each connecting page gets its own pair
|
||||
// since the connect event fires once per owner, not once at worker startup.
|
||||
if (!is_shared) {
|
||||
inside_port = Web::HTML::MessagePort::create(realm);
|
||||
|
||||
// FIXME: 1. Set inside port's message event target to worker global scope.
|
||||
|
||||
// 2. Set worker global scope's inside port to inside port.
|
||||
worker_global_scope->set_internal_port(inside_port);
|
||||
}
|
||||
worker_global_scope->set_internal_port(*inside_port);
|
||||
|
||||
// 5. Entangle outside port and inside port.
|
||||
Web::HTML::TransferDataDecoder decoder { move(message_port_data) };
|
||||
MUST(inside_port->transfer_receiving_steps(decoder));
|
||||
Web::HTML::TransferDataDecoder decoder { move(message_port_data) };
|
||||
MUST(inside_port->transfer_receiving_steps(decoder));
|
||||
}
|
||||
|
||||
// 6. Create a new WorkerLocation object and associate it with worker global scope.
|
||||
worker_global_scope->set_location(realm.create<Web::HTML::WorkerLocation>(*worker_global_scope));
|
||||
|
|
@ -257,7 +269,7 @@ void WorkerHost::run(GC::Ref<Web::Page> page, Web::HTML::TransferDataEncoder mes
|
|||
else
|
||||
(void)as<Web::HTML::ModuleScript>(*script).run();
|
||||
|
||||
as<WebWorker::PageHost>(page->client()).did_finish_loading_worker_script();
|
||||
as<WebWorker::PageHost>(page->client()).did_finish_loading_worker_script(Web::HTML::is_secure_context(*inside_settings));
|
||||
|
||||
// FIXME: 11. Enable outside port's port message queue.
|
||||
|
||||
|
|
@ -271,19 +283,9 @@ void WorkerHost::run(GC::Ref<Web::Page> page, Web::HTML::TransferDataEncoder mes
|
|||
// with the data attribute initialized to the empty string, the ports attribute initialized
|
||||
// to a new frozen array containing inside port, and the source attribute initialized to inside port.
|
||||
if (is_shared) {
|
||||
Web::HTML::queue_global_task(Web::HTML::Task::Source::DOMManipulation, *worker_global_scope, GC::create_function(realm.heap(), [worker_global_scope, inside_port] {
|
||||
auto& realm = worker_global_scope->realm();
|
||||
auto& vm = realm.vm();
|
||||
Web::HTML::TemporaryExecutionContext const context(realm);
|
||||
|
||||
Web::Bindings::MessageEventInit event_init;
|
||||
event_init.data = GC::Ref { vm.empty_string() };
|
||||
event_init.source = Web::HTML::NullableMessageEventSource { inside_port };
|
||||
event_init.ports.append(inside_port);
|
||||
|
||||
auto message_event = Web::HTML::MessageEvent::create(realm, Web::HTML::EventNames::connect, event_init);
|
||||
worker_global_scope->dispatch_event(message_event);
|
||||
}));
|
||||
protected_this->m_accepting_shared_worker_connections = true;
|
||||
protected_this->connect_shared_worker_impl(move(message_port_data), outside_settings_snapshot, ShouldAppendOwner::No);
|
||||
protected_this->flush_pending_shared_worker_connections();
|
||||
}
|
||||
|
||||
// FIXME: 14. Enable the client message queue of the ServiceWorkerContainer object whose associated service
|
||||
|
|
@ -324,4 +326,65 @@ void WorkerHost::run(GC::Ref<Web::Page> page, Web::HTML::TransferDataEncoder mes
|
|||
}
|
||||
}
|
||||
|
||||
void WorkerHost::connect_shared_worker(Web::HTML::TransferDataEncoder message_port_data, Web::HTML::SerializedEnvironmentSettingsObject outside_settings)
|
||||
{
|
||||
if (!m_is_shared || !m_accepting_shared_worker_connections) {
|
||||
m_pending_shared_worker_connections.append(PendingSharedWorkerConnection {
|
||||
.message_port_data = move(message_port_data),
|
||||
.outside_settings = move(outside_settings),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
connect_shared_worker_impl(move(message_port_data), outside_settings, ShouldAppendOwner::Yes);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/workers.html#dom-sharedworker
|
||||
void WorkerHost::connect_shared_worker_impl(Web::HTML::TransferDataEncoder message_port_data, Web::HTML::SerializedEnvironmentSettingsObject const& outside_settings, ShouldAppendOwner should_append_owner)
|
||||
{
|
||||
VERIFY(m_worker_global_scope);
|
||||
VERIFY(m_inside_settings);
|
||||
|
||||
auto& realm = m_inside_settings->realm();
|
||||
|
||||
// 11.5.5. Let insidePort be a new MessagePort in insideSettings's realm.
|
||||
auto inside_port = Web::HTML::MessagePort::create(realm);
|
||||
|
||||
// 11.5.6. Entangle outsidePort and insidePort.
|
||||
Web::HTML::TransferDataDecoder decoder { move(message_port_data) };
|
||||
MUST(inside_port->transfer_receiving_steps(decoder));
|
||||
|
||||
// 11.5.7. Queue a global task on the DOM manipulation task source given workerGlobalScope to
|
||||
// fire an event named connect at workerGlobalScope, using MessageEvent, with the data
|
||||
// attribute initialized to the empty string, the ports attribute initialized to a new
|
||||
// frozen array containing only insidePort, and the source attribute initialized to
|
||||
// insidePort.
|
||||
Web::HTML::queue_global_task(Web::HTML::Task::Source::DOMManipulation, *m_worker_global_scope, GC::create_function(realm.heap(), [worker_global_scope = GC::Root { *m_worker_global_scope }, inside_port] {
|
||||
auto& realm = worker_global_scope->realm();
|
||||
auto& vm = realm.vm();
|
||||
Web::HTML::TemporaryExecutionContext const context(realm);
|
||||
|
||||
Web::Bindings::MessageEventInit event_init {};
|
||||
event_init.data = GC::Ref { vm.empty_string() };
|
||||
event_init.ports.append(inside_port);
|
||||
event_init.source = Web::HTML::NullableMessageEventSource { inside_port };
|
||||
|
||||
auto message_event = Web::HTML::MessageEvent::create(realm, Web::HTML::EventNames::connect, event_init);
|
||||
worker_global_scope->dispatch_event(message_event);
|
||||
}));
|
||||
|
||||
// 11.5.8. Append the relevant owner to add given outsideSettings to workerGlobalScope's owner set.
|
||||
if (should_append_owner == ShouldAppendOwner::Yes) {
|
||||
auto owner = relevant_owner_to_add(outside_settings);
|
||||
m_worker_global_scope->owner_set().append(owner);
|
||||
}
|
||||
}
|
||||
|
||||
void WorkerHost::flush_pending_shared_worker_connections()
|
||||
{
|
||||
auto pending_connections = move(m_pending_shared_worker_connections);
|
||||
for (auto& connection : pending_connections)
|
||||
connect_shared_worker_impl(move(connection.message_port_data), connection.outside_settings, ShouldAppendOwner::Yes);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||
#include <LibWeb/Bindings/Worker.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/Scripting/SerializedEnvironmentSettingsObject.h>
|
||||
#include <LibWeb/HTML/Scripting/WorkerEnvironmentSettingsObject.h>
|
||||
#include <LibWeb/HTML/StructuredSerialize.h>
|
||||
#include <LibWeb/HTML/WorkerGlobalScope.h>
|
||||
|
||||
namespace WebWorker {
|
||||
|
||||
|
|
@ -22,13 +25,33 @@ public:
|
|||
~WorkerHost();
|
||||
|
||||
void run(GC::Ref<Web::Page>, Web::HTML::TransferDataEncoder message_port_data, Web::HTML::SerializedEnvironmentSettingsObject const&, Web::Bindings::RequestCredentials, bool is_shared);
|
||||
void connect_shared_worker(Web::HTML::TransferDataEncoder message_port_data, Web::HTML::SerializedEnvironmentSettingsObject);
|
||||
|
||||
private:
|
||||
struct PendingSharedWorkerConnection {
|
||||
Web::HTML::TransferDataEncoder message_port_data;
|
||||
Web::HTML::SerializedEnvironmentSettingsObject outside_settings;
|
||||
};
|
||||
|
||||
enum class ShouldAppendOwner {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
void connect_shared_worker_impl(Web::HTML::TransferDataEncoder message_port_data, Web::HTML::SerializedEnvironmentSettingsObject const&, ShouldAppendOwner);
|
||||
void flush_pending_shared_worker_connections();
|
||||
|
||||
GC::Root<Web::HTML::WorkerDebugConsoleClient> m_console;
|
||||
GC::Root<Web::HTML::WorkerGlobalScope> m_worker_global_scope;
|
||||
GC::Root<Web::HTML::WorkerEnvironmentSettingsObject> m_inside_settings;
|
||||
|
||||
URL::URL m_url;
|
||||
Web::Bindings::WorkerType m_type;
|
||||
String m_name;
|
||||
bool m_is_shared { false };
|
||||
bool m_accepting_shared_worker_connections { false };
|
||||
// WorkerHost is only touched on the WebWorker main thread.
|
||||
Vector<PendingSharedWorkerConnection> m_pending_shared_worker_connections;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
worker2: connect 2
|
||||
worker2: echo 2: after-gc
|
||||
4
Tests/LibWeb/Text/expected/Worker/SharedWorker-reuse.txt
Normal file
4
Tests/LibWeb/Text/expected/Worker/SharedWorker-reuse.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
worker1: connect 1
|
||||
worker2: connect 2
|
||||
worker1: echo 1: one
|
||||
worker2: echo 2: two
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
worker1: connect 1
|
||||
worker2: error
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
asyncTest(done => {
|
||||
const workerScript = `
|
||||
let connections = 0;
|
||||
onconnect = event => {
|
||||
connections++;
|
||||
const connectionNumber = connections;
|
||||
const port = event.ports[0];
|
||||
port.onmessage = event => {
|
||||
port.postMessage("echo " + connectionNumber + ": " + event.data);
|
||||
};
|
||||
port.start();
|
||||
port.postMessage("connect " + connectionNumber);
|
||||
};
|
||||
`;
|
||||
|
||||
const workerURL = URL.createObjectURL(new Blob([workerScript], { type: "text/javascript" }));
|
||||
let worker1 = new SharedWorker(workerURL, "shared-worker-close-one-owner");
|
||||
const worker2 = new SharedWorker(workerURL, "shared-worker-close-one-owner");
|
||||
|
||||
let worker1Connected = false;
|
||||
let worker2Connected = false;
|
||||
|
||||
function dropWorker1AndPingViaWorker2() {
|
||||
worker1.port.onmessage = null;
|
||||
worker1 = null;
|
||||
internals.gc();
|
||||
|
||||
// worker2 must still talk to the same shared worker after worker1 is gone.
|
||||
worker2.port.postMessage("after-gc");
|
||||
}
|
||||
|
||||
function whenBothConnected() {
|
||||
if (worker1Connected && worker2Connected)
|
||||
dropWorker1AndPingViaWorker2();
|
||||
}
|
||||
|
||||
worker1.port.onmessage = event => {
|
||||
if (event.data === "connect 1") {
|
||||
worker1Connected = true;
|
||||
whenBothConnected();
|
||||
}
|
||||
};
|
||||
worker1.port.start();
|
||||
|
||||
worker2.port.onmessage = event => {
|
||||
println("worker2: " + event.data);
|
||||
if (event.data === "connect 2") {
|
||||
worker2Connected = true;
|
||||
whenBothConnected();
|
||||
} else if (event.data === "echo 2: after-gc") {
|
||||
done();
|
||||
}
|
||||
};
|
||||
worker2.port.start();
|
||||
});
|
||||
</script>
|
||||
46
Tests/LibWeb/Text/input/Worker/SharedWorker-reuse.html
Normal file
46
Tests/LibWeb/Text/input/Worker/SharedWorker-reuse.html
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
asyncTest(done => {
|
||||
const workerScript = `
|
||||
let connections = 0;
|
||||
onconnect = event => {
|
||||
connections++;
|
||||
const connectionNumber = connections;
|
||||
const port = event.ports[0];
|
||||
port.onmessage = event => {
|
||||
port.postMessage("echo " + connectionNumber + ": " + event.data);
|
||||
};
|
||||
port.start();
|
||||
port.postMessage("connect " + connectionNumber);
|
||||
};
|
||||
`;
|
||||
|
||||
const workerURL = URL.createObjectURL(new Blob([workerScript], { type: "text/javascript" }));
|
||||
const worker1 = new SharedWorker(workerURL, "shared-worker-reuse");
|
||||
const worker2 = new SharedWorker(workerURL, "shared-worker-reuse");
|
||||
|
||||
let messages = 0;
|
||||
function maybeDone() {
|
||||
if (++messages === 4)
|
||||
done();
|
||||
}
|
||||
|
||||
worker1.port.onmessage = event => {
|
||||
println("worker1: " + event.data);
|
||||
if (event.data === "connect 1")
|
||||
worker1.port.postMessage("one");
|
||||
maybeDone();
|
||||
};
|
||||
|
||||
worker2.port.onmessage = event => {
|
||||
println("worker2: " + event.data);
|
||||
if (event.data === "connect 2")
|
||||
worker2.port.postMessage("two");
|
||||
maybeDone();
|
||||
};
|
||||
|
||||
worker1.port.start();
|
||||
worker2.port.start();
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
asyncTest(done => {
|
||||
const workerScript = `
|
||||
let connections = 0;
|
||||
onconnect = event => {
|
||||
connections++;
|
||||
const port = event.ports[0];
|
||||
port.start();
|
||||
port.postMessage("connect " + connections);
|
||||
};
|
||||
`;
|
||||
|
||||
const workerURL = URL.createObjectURL(new Blob([workerScript], { type: "text/javascript" }));
|
||||
|
||||
let worker1Message = null;
|
||||
let worker2Errored = false;
|
||||
let finished = false;
|
||||
|
||||
function maybeDone() {
|
||||
if (finished || worker1Message === null || !worker2Errored)
|
||||
return;
|
||||
finished = true;
|
||||
println("worker1: " + worker1Message);
|
||||
println("worker2: error");
|
||||
done();
|
||||
}
|
||||
|
||||
const worker1 = new SharedWorker(workerURL, { name: "shared-worker-type-mismatch", type: "classic" });
|
||||
worker1.port.onmessage = event => {
|
||||
worker1Message = event.data;
|
||||
maybeDone();
|
||||
};
|
||||
worker1.port.start();
|
||||
|
||||
const worker2 = new SharedWorker(workerURL, { name: "shared-worker-type-mismatch", type: "module" });
|
||||
worker2.onerror = () => {
|
||||
worker2Errored = true;
|
||||
maybeDone();
|
||||
};
|
||||
worker2.port.onmessage = event => {
|
||||
if (finished)
|
||||
return;
|
||||
finished = true;
|
||||
println("worker2: unexpected " + event.data);
|
||||
done();
|
||||
};
|
||||
worker2.port.start();
|
||||
});
|
||||
</script>
|
||||
Loading…
Reference in a new issue