mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-25 17:25:08 +02:00
LibWeb: Implement cross process BroadcastChannel delivery
Route BroadcastChannel messages over IPC so matching channels can receive them across WebContent and WebWorker processes, rather than only within a single process. Each channel now serializes its payload, sends it upward over IPC, and receiving processes deliver it locally after matching by storage key and channel name.
This commit is contained in:
committed by
Shannon Booth
parent
74591fe837
commit
de14978046
Notes:
github-actions[bot]
2026-04-14 16:44:24 +00:00
Author: https://github.com/shannonbooth Commit: https://github.com/LadybirdBrowser/ladybird/commit/de14978046f Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/8865
@@ -463,6 +463,7 @@ set(SOURCES
|
||||
HTML/BarProp.cpp
|
||||
HTML/BeforeUnloadEvent.cpp
|
||||
HTML/BroadcastChannel.cpp
|
||||
HTML/BroadcastChannelMessage.cpp
|
||||
HTML/BrowsingContext.cpp
|
||||
HTML/BrowsingContextGroup.cpp
|
||||
HTML/Canvas/CanvasDrawImage.cpp
|
||||
|
||||
@@ -871,6 +871,7 @@ enum class RequireWellFormed;
|
||||
enum class SandboxingFlagSet;
|
||||
|
||||
struct Agent;
|
||||
struct BroadcastChannelMessage;
|
||||
struct DeserializedTransferRecord;
|
||||
struct EmbedderPolicy;
|
||||
struct Environment;
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
||||
* Copyright (c) 2024-2026, Shannon Booth <shannon@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/QuickSort.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibWeb/Bindings/BroadcastChannelPrototype.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||
#include <LibWeb/Bindings/PrincipalHostDefined.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/HTML/BroadcastChannel.h>
|
||||
#include <LibWeb/HTML/BroadcastChannelMessage.h>
|
||||
#include <LibWeb/HTML/EventNames.h>
|
||||
#include <LibWeb/HTML/MessageEvent.h>
|
||||
#include <LibWeb/HTML/StructuredSerialize.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/HTML/WorkerGlobalScope.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
#include <LibWeb/StorageAPI/StorageKey.h>
|
||||
#include <LibWeb/Worker/WebWorkerClient.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
@@ -24,32 +31,44 @@ public:
|
||||
void register_channel(GC::Ref<BroadcastChannel>);
|
||||
void unregister_channel(GC::Ref<BroadcastChannel>);
|
||||
auto const& registered_channels_for_key(StorageAPI::StorageKey) const;
|
||||
[[nodiscard]] u64 next_channel_id() { return ++m_next_channel_id; }
|
||||
|
||||
private:
|
||||
HashMap<StorageAPI::StorageKey, Vector<GC::Weak<BroadcastChannel>>> m_channels;
|
||||
u64 m_next_channel_id { 0 };
|
||||
};
|
||||
|
||||
void BroadcastChannelRepository::register_channel(GC::Ref<BroadcastChannel> channel)
|
||||
{
|
||||
auto storage_key = StorageAPI::obtain_a_storage_key_for_non_storage_purposes(relevant_settings_object(*channel));
|
||||
channel->m_channel_id = next_channel_id();
|
||||
m_channels.ensure(storage_key).append(channel);
|
||||
}
|
||||
|
||||
void BroadcastChannelRepository::unregister_channel(GC::Ref<BroadcastChannel> channel)
|
||||
{
|
||||
auto storage_key = StorageAPI::obtain_a_storage_key_for_non_storage_purposes(relevant_settings_object(channel));
|
||||
auto& relevant_channels = m_channels.get(storage_key).value();
|
||||
auto maybe_channels = m_channels.get(storage_key);
|
||||
if (!maybe_channels.has_value())
|
||||
return;
|
||||
|
||||
auto& relevant_channels = maybe_channels.value();
|
||||
relevant_channels.remove_first_matching([&](auto c) { return c == channel; });
|
||||
if (relevant_channels.is_empty())
|
||||
m_channels.remove(storage_key);
|
||||
}
|
||||
|
||||
auto const& BroadcastChannelRepository::registered_channels_for_key(StorageAPI::StorageKey key) const
|
||||
{
|
||||
static Vector<GC::Weak<BroadcastChannel>> s_empty_channels;
|
||||
|
||||
auto maybe_channels = m_channels.get(key);
|
||||
VERIFY(maybe_channels.has_value());
|
||||
if (!maybe_channels.has_value())
|
||||
return s_empty_channels;
|
||||
|
||||
return maybe_channels.value();
|
||||
}
|
||||
|
||||
// FIXME: This should not be static, and live at a storage partitioned level of the user agent.
|
||||
static BroadcastChannelRepository s_broadcast_channel_repository;
|
||||
|
||||
GC_DEFINE_ALLOCATOR(BroadcastChannel);
|
||||
@@ -118,35 +137,65 @@ WebIDL::ExceptionOr<void> BroadcastChannel::post_message(JS::Value message)
|
||||
auto source_origin = relevant_settings_object(*this).origin();
|
||||
|
||||
// 5. Let sourceStorageKey be the result of running obtain a storage key for non-storage purposes with this's relevant settings object.
|
||||
auto source_storage_key = Web::StorageAPI::obtain_a_storage_key_for_non_storage_purposes(relevant_settings_object(*this));
|
||||
auto source_storage_key = StorageAPI::obtain_a_storage_key_for_non_storage_purposes(relevant_settings_object(*this));
|
||||
|
||||
BroadcastChannelMessage message_to_send {
|
||||
.storage_key = source_storage_key,
|
||||
.channel_name = name().to_string(),
|
||||
.source_origin = source_origin,
|
||||
.serialized_message = serialized,
|
||||
.source_process_id = Core::System::getpid(),
|
||||
.source_channel_id = m_channel_id,
|
||||
};
|
||||
|
||||
// Steps 6-9.
|
||||
deliver_message_locally(message_to_send);
|
||||
|
||||
// NB: Other WebContent processes receive this via the browser-process IPC fanout.
|
||||
// Child worker processes are not part of that routing path, so forward to them directly here.
|
||||
Bindings::principal_host_defined_page(realm()).client().page_did_post_broadcast_channel_message(message_to_send);
|
||||
|
||||
WebWorkerClient::for_each_client([&](WebWorkerClient& client) {
|
||||
client.async_broadcast_channel_message(message_to_send);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/web-messaging.html#dom-broadcastchannel-postmessage
|
||||
void BroadcastChannel::deliver_message_locally(BroadcastChannelMessage const& message)
|
||||
{
|
||||
auto& vm = Bindings::main_thread_vm();
|
||||
|
||||
// 6. Let destinations be a list of BroadcastChannel objects that match the following criteria:
|
||||
GC::RootVector<GC::Ref<BroadcastChannel>> destinations(vm.heap());
|
||||
|
||||
// * The result of running obtain a storage key for non-storage purposes with their relevant settings object equals sourceStorageKey.
|
||||
auto same_origin_broadcast_channels = s_broadcast_channel_repository.registered_channels_for_key(source_storage_key);
|
||||
|
||||
auto same_origin_broadcast_channels = s_broadcast_channel_repository.registered_channels_for_key(message.storage_key);
|
||||
for (auto const& channel : same_origin_broadcast_channels) {
|
||||
// * They are eligible for messaging.
|
||||
if (!channel->is_eligible_for_messaging())
|
||||
continue;
|
||||
|
||||
// * Their channel name is this's channel name.
|
||||
if (channel->name() != name())
|
||||
if (channel->name() != message.channel_name)
|
||||
continue;
|
||||
|
||||
destinations.append(*channel);
|
||||
}
|
||||
|
||||
// 7. Remove source from destinations.
|
||||
destinations.remove_first_matching([&](auto destination) { return destination == this; });
|
||||
destinations.remove_all_matching([&](auto destination) {
|
||||
return message.source_process_id == Core::System::getpid() && destination->m_channel_id == message.source_channel_id;
|
||||
});
|
||||
|
||||
// FIXME: 8. Sort destinations such that all BroadcastChannel objects whose relevant agents are the same are sorted in creation order, oldest first.
|
||||
// (This does not define a complete ordering. Within this constraint, user agents may sort the list in any implementation-defined manner.)
|
||||
|
||||
// 9. For each destination in destinations, queue a global task on the DOM manipulation task source given destination's relevant global object to perform the following steps:
|
||||
for (auto destination : destinations) {
|
||||
HTML::queue_global_task(HTML::Task::Source::DOMManipulation, relevant_global_object(destination), GC::create_function(vm.heap(), [&vm, serialized, destination, source_origin] {
|
||||
HTML::queue_global_task(HTML::Task::Source::DOMManipulation, relevant_global_object(destination), GC::create_function(vm.heap(), [&vm, destination, message] {
|
||||
// 1. If destination's closed flag is true, then abort these steps.
|
||||
if (destination->m_closed_flag)
|
||||
return;
|
||||
@@ -157,28 +206,26 @@ WebIDL::ExceptionOr<void> BroadcastChannel::post_message(JS::Value message)
|
||||
// 3. Let data be StructuredDeserialize(serialized, targetRealm).
|
||||
// If this throws an exception, catch it, fire an event named messageerror at destination, using MessageEvent, with its
|
||||
// origin initialized to sourceOrigin, and then abort these steps.
|
||||
auto data_or_error = structured_deserialize(vm, serialized, target_realm);
|
||||
auto data_or_error = structured_deserialize(vm, message.serialized_message, target_realm);
|
||||
if (data_or_error.is_exception()) {
|
||||
MessageEventInit event_init {};
|
||||
event_init.origin = source_origin;
|
||||
event_init.origin = message.source_origin.serialize();
|
||||
auto event = MessageEvent::create(target_realm, HTML::EventNames::messageerror, event_init);
|
||||
event->set_is_trusted(true);
|
||||
destination->dispatch_event(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Fire an event named message at destination, using MessageEvent, with the data attribute initialized to data and the
|
||||
// 4. Fire an event named message at destination, using MessageEvent, with the data attribute initialized to data and
|
||||
// its origin initialized to sourceOrigin.
|
||||
MessageEventInit event_init {};
|
||||
event_init.data = data_or_error.release_value();
|
||||
event_init.origin = source_origin;
|
||||
event_init.origin = message.source_origin.serialize();
|
||||
auto event = MessageEvent::create(target_realm, HTML::EventNames::message, event_init);
|
||||
event->set_is_trusted(true);
|
||||
destination->dispatch_event(event);
|
||||
}));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/web-messaging.html#dom-broadcastchannel-close
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
||||
* Copyright (c) 2024-2026, Shannon Booth <shannon@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class BroadcastChannelRepository;
|
||||
|
||||
class BroadcastChannel final : public DOM::EventTarget {
|
||||
WEB_PLATFORM_OBJECT(BroadcastChannel, DOM::EventTarget);
|
||||
GC_DECLARE_ALLOCATOR(BroadcastChannel);
|
||||
@@ -36,7 +38,11 @@ public:
|
||||
void set_onmessageerror(GC::Ptr<WebIDL::CallbackType>);
|
||||
GC::Ptr<WebIDL::CallbackType> onmessageerror();
|
||||
|
||||
static WEB_API void deliver_message_locally(BroadcastChannelMessage const&);
|
||||
|
||||
private:
|
||||
friend class BroadcastChannelRepository;
|
||||
|
||||
BroadcastChannel(JS::Realm&, FlyString const& name);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
@@ -45,6 +51,7 @@ private:
|
||||
bool is_eligible_for_messaging() const;
|
||||
|
||||
FlyString m_channel_name;
|
||||
u64 m_channel_id { 0 };
|
||||
bool m_closed_flag { false };
|
||||
};
|
||||
|
||||
|
||||
46
Libraries/LibWeb/HTML/BroadcastChannelMessage.cpp
Normal file
46
Libraries/LibWeb/HTML/BroadcastChannelMessage.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Shannon Booth <shannon@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibIPC/Decoder.h>
|
||||
#include <LibIPC/Encoder.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/HTML/BroadcastChannelMessage.h>
|
||||
|
||||
namespace IPC {
|
||||
|
||||
template<>
|
||||
ErrorOr<void> encode(Encoder& encoder, Web::HTML::BroadcastChannelMessage const& message)
|
||||
{
|
||||
TRY(encoder.encode(message.storage_key));
|
||||
TRY(encoder.encode(message.channel_name));
|
||||
TRY(encoder.encode(message.source_origin));
|
||||
TRY(encoder.encode(message.serialized_message));
|
||||
TRY(encoder.encode(message.source_process_id));
|
||||
TRY(encoder.encode(message.source_channel_id));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<Web::HTML::BroadcastChannelMessage> decode(Decoder& decoder)
|
||||
{
|
||||
auto storage_key = TRY(decoder.decode<Web::StorageAPI::StorageKey>());
|
||||
auto channel_name = TRY(decoder.decode<String>());
|
||||
auto source_origin = TRY(decoder.decode<URL::Origin>());
|
||||
auto serialized_message = TRY(decoder.decode<Web::HTML::SerializationRecord>());
|
||||
auto source_process_id = TRY(decoder.decode<i32>());
|
||||
auto source_channel_id = TRY(decoder.decode<u64>());
|
||||
|
||||
return Web::HTML::BroadcastChannelMessage {
|
||||
.storage_key = move(storage_key),
|
||||
.channel_name = move(channel_name),
|
||||
.source_origin = move(source_origin),
|
||||
.serialized_message = move(serialized_message),
|
||||
.source_process_id = source_process_id,
|
||||
.source_channel_id = source_channel_id,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
38
Libraries/LibWeb/HTML/BroadcastChannelMessage.h
Normal file
38
Libraries/LibWeb/HTML/BroadcastChannelMessage.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Shannon Booth <shannon@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibIPC/Forward.h>
|
||||
#include <LibURL/Origin.h>
|
||||
#include <LibWeb/Export.h>
|
||||
#include <LibWeb/HTML/StructuredSerializeTypes.h>
|
||||
#include <LibWeb/StorageAPI/StorageKey.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
struct WEB_API BroadcastChannelMessage {
|
||||
StorageAPI::StorageKey storage_key;
|
||||
String channel_name;
|
||||
URL::Origin source_origin;
|
||||
SerializationRecord serialized_message;
|
||||
pid_t source_process_id { -1 };
|
||||
u64 source_channel_id { 0 };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace IPC {
|
||||
|
||||
template<>
|
||||
WEB_API ErrorOr<void> encode(Encoder&, Web::HTML::BroadcastChannelMessage const&);
|
||||
|
||||
template<>
|
||||
WEB_API ErrorOr<Web::HTML::BroadcastChannelMessage> decode(Decoder&);
|
||||
|
||||
}
|
||||
@@ -72,6 +72,10 @@ void WorkerAgentParent::setup_worker_ipc_callbacks(JS::Realm& realm)
|
||||
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);
|
||||
|
||||
@@ -473,6 +473,7 @@ public:
|
||||
virtual void page_did_receive_network_response_body([[maybe_unused]] u64 request_id, [[maybe_unused]] ReadonlyBytes data) { }
|
||||
virtual void page_did_finish_network_request([[maybe_unused]] u64 request_id, [[maybe_unused]] u64 body_size, [[maybe_unused]] Requests::RequestTimingInfo const& timing_info, [[maybe_unused]] Optional<Requests::NetworkError> const& network_error) { }
|
||||
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;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
HashTable<WebWorkerClient*> WebWorkerClient::s_all_clients;
|
||||
|
||||
void WebWorkerClient::die()
|
||||
{
|
||||
// FIXME: Notify WorkerAgent that the worker is dead
|
||||
@@ -38,6 +40,12 @@ Messages::WebWorkerClient::DidRequestCookieResponse WebWorkerClient::did_request
|
||||
return HTTP::Cookie::VersionedCookie {};
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (on_request_worker_agent)
|
||||
@@ -48,6 +56,12 @@ Messages::WebWorkerClient::RequestWorkerAgentResponse WebWorkerClient::request_w
|
||||
WebWorkerClient::WebWorkerClient(NonnullOwnPtr<IPC::Transport> transport)
|
||||
: IPC::ConnectionToServer<WebWorkerClientEndpoint, WebWorkerServerEndpoint>(*this, move(transport))
|
||||
{
|
||||
s_all_clients.set(this);
|
||||
}
|
||||
|
||||
WebWorkerClient::~WebWorkerClient()
|
||||
{
|
||||
s_all_clients.remove(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/IterationDecision.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibHTTP/Cookie/Cookie.h>
|
||||
#include <LibIPC/ConnectionToServer.h>
|
||||
#include <LibIPC/TransportHandle.h>
|
||||
#include <LibWeb/Export.h>
|
||||
#include <LibWeb/HTML/BroadcastChannelMessage.h>
|
||||
#include <LibWeb/Worker/WebWorkerClientEndpoint.h>
|
||||
#include <LibWeb/Worker/WebWorkerServerEndpoint.h>
|
||||
|
||||
@@ -22,7 +25,11 @@ class WEB_API WebWorkerClient final
|
||||
C_OBJECT_ABSTRACT(WebWorkerClient);
|
||||
|
||||
public:
|
||||
template<typename Callback>
|
||||
static void for_each_client(Callback callback);
|
||||
|
||||
explicit WebWorkerClient(NonnullOwnPtr<IPC::Transport>);
|
||||
~WebWorkerClient();
|
||||
|
||||
pid_t pid() const { return m_pid; }
|
||||
void set_pid(pid_t pid) { m_pid = pid; }
|
||||
@@ -31,18 +38,30 @@ public:
|
||||
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_post_broadcast_channel_message(Web::HTML::BroadcastChannelMessage) override;
|
||||
virtual Messages::WebWorkerClient::RequestWorkerAgentResponse request_worker_agent(Web::Bindings::AgentType worker_type) override;
|
||||
|
||||
Function<void()> on_worker_close;
|
||||
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(Web::HTML::BroadcastChannelMessage)> on_post_broadcast_channel_message;
|
||||
Function<Messages::WebWorkerClient::RequestWorkerAgentResponse(Web::Bindings::AgentType)> on_request_worker_agent;
|
||||
|
||||
private:
|
||||
virtual void die() override;
|
||||
|
||||
pid_t m_pid { -1 };
|
||||
static HashTable<WebWorkerClient*> s_all_clients;
|
||||
};
|
||||
|
||||
template<typename Callback>
|
||||
void WebWorkerClient::for_each_client(Callback callback)
|
||||
{
|
||||
for (auto* client : s_all_clients) {
|
||||
if (callback(*client) == IterationDecision::Break)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
#include <LibIPC/TransportHandle.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/Bindings/AgentType.h>
|
||||
#include <LibWeb/HTML/BroadcastChannelMessage.h>
|
||||
|
||||
endpoint WebWorkerClient {
|
||||
did_close_worker() =|
|
||||
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_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)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <LibIPC/File.h>
|
||||
#include <LibIPC/TransportHandle.h>
|
||||
#include <LibWeb/Bindings/AgentType.h>
|
||||
#include <LibWeb/HTML/BroadcastChannelMessage.h>
|
||||
#include <LibWeb/Bindings/WorkerPrototype.h>
|
||||
#include <LibWeb/HTML/StructuredSerialize.h>
|
||||
#include <LibWeb/HTML/Scripting/SerializedEnvironmentSettingsObject.h>
|
||||
@@ -21,5 +22,7 @@ endpoint WebWorkerServer {
|
||||
|
||||
close_worker() =|
|
||||
|
||||
broadcast_channel_message(Web::HTML::BroadcastChannelMessage message) =|
|
||||
|
||||
handle_file_return(i32 error, Optional<IPC::File> file, i32 request_id) =|
|
||||
}
|
||||
|
||||
@@ -603,6 +603,16 @@ void WebContentClient::did_clear_storage(Web::StorageAPI::StorageEndpointType st
|
||||
Application::storage_jar().clear_storage_key(storage_endpoint, storage_key);
|
||||
}
|
||||
|
||||
void WebContentClient::did_post_broadcast_channel_message(u64, 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;
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
||||
|
||||
@@ -120,6 +120,7 @@ private:
|
||||
virtual void did_remove_storage_item(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key, String bottle_key) override;
|
||||
virtual Messages::WebContentClient::DidRequestStorageKeysResponse did_request_storage_keys(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key) override;
|
||||
virtual void did_clear_storage(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key) override;
|
||||
virtual void did_post_broadcast_channel_message(u64 page_id, Web::HTML::BroadcastChannelMessage message) override;
|
||||
virtual Messages::WebContentClient::DidRequestNewWebViewResponse did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab, Web::HTML::WebViewHints, Optional<u64> page_index) override;
|
||||
virtual void did_request_activate_tab(u64 page_id) override;
|
||||
virtual void did_close_browsing_context(u64 page_id) override;
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include <LibWeb/DOM/Text.h>
|
||||
#include <LibWeb/Dump.h>
|
||||
#include <LibWeb/Fetch/Fetching/Fetching.h>
|
||||
#include <LibWeb/HTML/BroadcastChannel.h>
|
||||
#include <LibWeb/HTML/BrowsingContext.h>
|
||||
#include <LibWeb/HTML/HTMLInputElement.h>
|
||||
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
|
||||
@@ -55,6 +56,7 @@
|
||||
#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/ConnectionFromClient.h>
|
||||
@@ -1394,6 +1396,17 @@ 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;
|
||||
});
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/speculative-loading.html#nav-traversal-ui:close-a-top-level-traversable
|
||||
void ConnectionFromClient::request_close(u64 page_id)
|
||||
{
|
||||
|
||||
@@ -168,6 +168,7 @@ private:
|
||||
virtual void set_document_cookie_version_buffer(u64 page_id, Core::AnonymousBuffer document_cookie_version_buffer) override;
|
||||
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 request_close(u64 page_id) override;
|
||||
|
||||
|
||||
@@ -640,6 +640,11 @@ void PageClient::page_did_clear_storage(Web::StorageAPI::StorageEndpointType sto
|
||||
}
|
||||
}
|
||||
|
||||
void PageClient::page_did_post_broadcast_channel_message(Web::HTML::BroadcastChannelMessage const& message)
|
||||
{
|
||||
client().async_did_post_broadcast_channel_message(m_id, message);
|
||||
}
|
||||
|
||||
void PageClient::page_did_update_resource_count(i32 count_waiting)
|
||||
{
|
||||
client().async_did_update_resource_count(m_id, count_waiting);
|
||||
|
||||
@@ -198,6 +198,7 @@ private:
|
||||
virtual void page_did_receive_network_response_headers(u64 request_id, u32 status_code, Optional<String>, Vector<HTTP::Header> const&) override;
|
||||
virtual void page_did_receive_network_response_body(u64 request_id, ReadonlyBytes) override;
|
||||
virtual void page_did_finish_network_request(u64 request_id, u64 body_size, Requests::RequestTimingInfo const&, Optional<Requests::NetworkError> const&) override;
|
||||
virtual void page_did_post_broadcast_channel_message(Web::HTML::BroadcastChannelMessage const&) override;
|
||||
|
||||
void setup_palette();
|
||||
ConnectionFromClient& client() const;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <LibWeb/CSS/StyleSheetIdentifier.h>
|
||||
#include <LibWeb/HTML/ActivateTab.h>
|
||||
#include <LibWeb/HTML/AudioPlayState.h>
|
||||
#include <LibWeb/HTML/BroadcastChannelMessage.h>
|
||||
#include <LibWeb/HTML/FileFilter.h>
|
||||
#include <LibWeb/HTML/SelectedFile.h>
|
||||
#include <LibWeb/HTML/SelectItem.h>
|
||||
@@ -92,6 +93,7 @@ endpoint WebContentClient
|
||||
did_remove_storage_item(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key, String bottle_key) => ()
|
||||
did_request_storage_keys(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key) => (Vector<String> keys)
|
||||
did_clear_storage(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key) => ()
|
||||
did_post_broadcast_channel_message(u64 page_id, Web::HTML::BroadcastChannelMessage message) =|
|
||||
|
||||
did_update_resource_count(u64 page_id, i32 count_waiting) =|
|
||||
did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab activate_tab, Web::HTML::WebViewHints hints, Optional<u64> page_index) => (String handle)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <LibWeb/CSS/Selector.h>
|
||||
#include <LibWeb/CSS/StyleSheetIdentifier.h>
|
||||
#include <LibWeb/HTML/ColorPickerUpdateState.h>
|
||||
#include <LibWeb/HTML/BroadcastChannelMessage.h>
|
||||
#include <LibWeb/HTML/SelectedFile.h>
|
||||
#include <LibWeb/HTML/VisibilityState.h>
|
||||
#include <LibWeb/Page/InputEvent.h>
|
||||
@@ -143,6 +144,7 @@ endpoint WebContentServer
|
||||
set_document_cookie_version_buffer(u64 page_id, Core::AnonymousBuffer document_cookie_version_buffer) =|
|
||||
set_document_cookie_version_index(u64 page_id, i64 document_id, Core::SharedVersionIndex document_index) =|
|
||||
cookies_changed(u64 page_id, Vector<HTTP::Cookie::Cookie> cookies) =|
|
||||
broadcast_channel_message(Web::HTML::BroadcastChannelMessage message) =|
|
||||
|
||||
request_close(u64 page_id) =|
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
*/
|
||||
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibWeb/HTML/BroadcastChannel.h>
|
||||
#include <LibWeb/Worker/WebWorkerClient.h>
|
||||
#include <WebWorker/ConnectionFromClient.h>
|
||||
#include <WebWorker/PageHost.h>
|
||||
#include <WebWorker/WorkerHost.h>
|
||||
@@ -97,4 +99,15 @@ 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::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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <LibIPC/ConnectionFromClient.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/BroadcastChannelMessage.h>
|
||||
#include <LibWeb/Loader/FileRequest.h>
|
||||
#include <LibWeb/Worker/WebWorkerClientEndpoint.h>
|
||||
#include <LibWeb/Worker/WebWorkerServerEndpoint.h>
|
||||
@@ -48,6 +49,7 @@ private:
|
||||
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 handle_file_return(i32 error, Optional<IPC::File> file, i32 request_id) override;
|
||||
virtual void broadcast_channel_message(Web::HTML::BroadcastChannelMessage message) override;
|
||||
|
||||
GC::Root<PageHost> m_page_host;
|
||||
|
||||
|
||||
@@ -97,6 +97,11 @@ void PageHost::page_did_report_worker_exception(String const& message, String co
|
||||
m_client.async_did_report_worker_exception(message, filename, lineno, colno);
|
||||
}
|
||||
|
||||
void PageHost::page_did_post_broadcast_channel_message(Web::HTML::BroadcastChannelMessage const& message)
|
||||
{
|
||||
m_client.async_did_post_broadcast_channel_message(message);
|
||||
}
|
||||
|
||||
void PageHost::request_file(Web::FileRequest request)
|
||||
{
|
||||
m_client.request_file(move(request));
|
||||
|
||||
@@ -38,6 +38,7 @@ public:
|
||||
virtual size_t screen_count() const override { return 1; }
|
||||
virtual HTTP::Cookie::VersionedCookie page_did_request_cookie(URL::URL const&, HTTP::Cookie::Source) override;
|
||||
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::DisplayListPlayerType display_list_player_type() const override { VERIFY_NOT_REACHED(); }
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
Harness status: OK
|
||||
|
||||
Found 4 tests
|
||||
|
||||
4 Pass
|
||||
Pass Opaque origin should be serialized to "null"
|
||||
Pass BroadcastChannel messages from opaque origins should be self-contained
|
||||
Pass BroadcastChannel messages from data URL dedicated workers should be self-contained
|
||||
Pass BroadcastChannel messages from data URL shared workers should be self-contained
|
||||
@@ -0,0 +1,193 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset=utf-8>
|
||||
<meta name="timeout" content="long">
|
||||
<title></title>
|
||||
<script src=../../resources/testharness.js></script>
|
||||
<script src=../../resources/testharnessreport.js></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
<!--
|
||||
promise_test(t => {
|
||||
return new Promise((resolve) => {
|
||||
let ifr = document.createElement("iframe");
|
||||
ifr.src =
|
||||
"data:text/html,<script> let bc = new BroadcastChannel(\"test\");" +
|
||||
"bc.onmessage = (e) => {" +
|
||||
" if (e.data == \"ping\") bc.postMessage('pong');"+
|
||||
" else parent.postMessage({workerMessageOrigin: e.data, messageOrigin: e.origin}, \"*\"); };" +
|
||||
"new Worker(URL.createObjectURL(new Blob([\"" +
|
||||
"let bc2 = new BroadcastChannel('test'); bc2.postMessage('ping'); " +
|
||||
"bc2.onmessage = e => bc2.postMessage(e.origin);" +
|
||||
"\"], {type: 'text/javascript'}))); </script>";
|
||||
window.addEventListener("message", t.step_func(e => {
|
||||
assert_equals(e.data.workerMessageOrigin, "null");
|
||||
assert_equals(e.data.messageOrigin, "null");
|
||||
resolve();
|
||||
}), {once: true});
|
||||
t.add_cleanup(() => { document.body.removeChild(ifr) });
|
||||
document.body.appendChild(ifr);
|
||||
});
|
||||
}, "Opaque origin should be serialized to \"null\"");
|
||||
|
||||
|
||||
const iframe_src = (channel_name, iframe_name) => `data:text/html,<script>
|
||||
let bc2 = new BroadcastChannel("${channel_name}");
|
||||
bc2.onmessage = (e) => {
|
||||
if (e.data == "from-${iframe_name}") {
|
||||
parent.postMessage("${iframe_name}-done", "*");
|
||||
} else {
|
||||
parent.postMessage("fail", "*");
|
||||
}
|
||||
};
|
||||
let bc3 = new BroadcastChannel("${channel_name}");
|
||||
bc3.postMessage("from-${iframe_name}");
|
||||
</script>`;
|
||||
|
||||
promise_test(t => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const channel_name = "opaque-origin-test-2";
|
||||
const bc1 = new BroadcastChannel(channel_name);
|
||||
bc1.onmessage = t.unreached_func("Received message from an opaque origin");
|
||||
|
||||
// We'll create an iframe and have it send a BroadcastChannel message
|
||||
// between two instances. Once the message is received, it will postMessage
|
||||
// back and we'll repeat this with another iframe. If the first
|
||||
// BroadcastChannel message is received by `bc1`, or if the second
|
||||
// BroadcastChannel message is received by `bc1` or `bc2` in the first
|
||||
// iframe, then the test should fail.
|
||||
|
||||
window.addEventListener("message", e => {
|
||||
if(e.data == "iframe1-done") {
|
||||
let iframe2 = document.createElement("iframe");
|
||||
iframe2.src = iframe_src(channel_name, "iframe2");
|
||||
t.add_cleanup(() => { document.body.removeChild(iframe2) });
|
||||
document.body.appendChild(iframe2);
|
||||
} else if(e.data == "iframe2-done") {
|
||||
resolve();
|
||||
} else if(e.data == "fail") {
|
||||
reject("One opaque origin received a message from the other");
|
||||
} else {
|
||||
reject("An unexpected error occurred");
|
||||
}
|
||||
});
|
||||
|
||||
let iframe1 = document.createElement("iframe");
|
||||
iframe1.src = iframe_src(channel_name, "iframe1");
|
||||
t.add_cleanup(() => { document.body.removeChild(iframe1) });
|
||||
document.body.appendChild(iframe1);
|
||||
});
|
||||
}, "BroadcastChannel messages from opaque origins should be self-contained");
|
||||
|
||||
const data_url_worker_src = (channel_name, worker_name) => {
|
||||
const source = `
|
||||
const handler = (reply) => {
|
||||
let bc2 = new BroadcastChannel("${channel_name}");
|
||||
bc2.onmessage = (e) => {
|
||||
if (e.data == "from-${worker_name}") {
|
||||
reply("${worker_name}-done");
|
||||
} else {
|
||||
reply("fail");
|
||||
}
|
||||
};
|
||||
let bc3 = new BroadcastChannel("${channel_name}");
|
||||
bc3.postMessage("from-${worker_name}");
|
||||
};
|
||||
// For dedicated workers:
|
||||
self.addEventListener("message", () => handler(self.postMessage));
|
||||
// For shared workers:
|
||||
self.addEventListener("connect", (e) => {
|
||||
var port = e.ports[0];
|
||||
port.onmessage = () => handler(msg => port.postMessage(msg));
|
||||
port.start();
|
||||
|
||||
});
|
||||
`;
|
||||
return "data:,".concat(encodeURIComponent(source));
|
||||
}
|
||||
|
||||
promise_test(t => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const channel_name = "opaque-origin-test-3";
|
||||
const bc1 = new BroadcastChannel(channel_name);
|
||||
bc1.onmessage = e => { reject("Received message from an opaque origin"); };
|
||||
|
||||
// Same as the previous test but with data URL dedicated workers (which
|
||||
// should have opaque origins per the HTML spec).
|
||||
const worker_name_prefix = "data-url-dedicated-worker";
|
||||
const worker_1_name = `${worker_name_prefix}-1`;
|
||||
const worker_2_name = `${worker_name_prefix}-2`;
|
||||
|
||||
const handler = e => {
|
||||
if(e.data == `${worker_1_name}-done`) {
|
||||
const worker2 = new Worker(data_url_worker_src(channel_name, worker_2_name));
|
||||
t.add_cleanup(() => worker2.terminate());
|
||||
worker2.addEventListener("message", handler);
|
||||
worker2.postMessage("go!");
|
||||
} else if(e.data == `${worker_2_name}-done`) {
|
||||
resolve();
|
||||
} else if(e.data == "fail") {
|
||||
reject("One opaque origin received a message from the other");
|
||||
} else {
|
||||
reject("An unexpected error occurred");
|
||||
}
|
||||
};
|
||||
|
||||
let worker1 = new Worker(data_url_worker_src(channel_name, worker_1_name));
|
||||
t.add_cleanup(() => worker1.terminate());
|
||||
worker1.addEventListener("message", handler);
|
||||
worker1.postMessage("go!");
|
||||
});
|
||||
}, "BroadcastChannel messages from data URL dedicated workers should be self-contained");
|
||||
|
||||
promise_test(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const channel_name = "opaque-origin-test-4";
|
||||
const bc1 = new BroadcastChannel(channel_name);
|
||||
|
||||
// Same as the previous test but with data URL shared workers (which
|
||||
// should have opaque origins per the HTML spec).
|
||||
const worker_name_prefix = "data-url-shared-worker";
|
||||
const worker_1_name = `${worker_name_prefix}-1`;
|
||||
const worker_2_name = `${worker_name_prefix}-2`;
|
||||
|
||||
const handler = e => {
|
||||
if (e.data == `${worker_1_name}-done`) {
|
||||
const worker_script = data_url_worker_src(channel_name, worker_2_name);
|
||||
const worker2 = new SharedWorker(worker_script, worker_2_name);
|
||||
worker2.port.addEventListener("message", handler);
|
||||
worker2.port.start();
|
||||
worker2.port.postMessage("go!");
|
||||
} else if(e.data == `${worker_2_name}-done`) {
|
||||
resolve();
|
||||
} else if(e.data == "fail") {
|
||||
reject("One opaque origin received a message from the other");
|
||||
} else {
|
||||
reject("An unexpected error occurred");
|
||||
}
|
||||
};
|
||||
|
||||
bc1.onmessage = e => {
|
||||
if (e.data == "go!") {
|
||||
const worker_script = data_url_worker_src(channel_name, worker_1_name);
|
||||
const worker1 = new SharedWorker(worker_script, worker_1_name);
|
||||
worker1.port.addEventListener("message", handler);
|
||||
worker1.port.start();
|
||||
worker1.port.postMessage("go!");
|
||||
} else {
|
||||
reject("Received message from an opaque origin");
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure that the BroadcastChannel instance above can receive messages
|
||||
// before we create the first shared worker.
|
||||
const bc2 = new BroadcastChannel(channel_name);
|
||||
bc2.postMessage("go!");
|
||||
});
|
||||
}, "BroadcastChannel messages from data URL shared workers should be self-contained");
|
||||
//-->
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user