mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-04-19 10:20:22 +00:00
Previously `BroadcastChannelRepository` held strong references to `BroadcastChannel` instances, which is removed only after `close()` on corresponding `BroadcastChannel` was called, which might never happen. This would have to be revisited once we will implement broadcast channels messaging across different WebContent processes, but for now using weak references in the repository saves us from leaking all unclosed `BroadcastChannel`s.
215 lines
8.9 KiB
C++
215 lines
8.9 KiB
C++
/*
|
|
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
|
|
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibJS/Runtime/Realm.h>
|
|
#include <LibWeb/Bindings/BroadcastChannelPrototype.h>
|
|
#include <LibWeb/Bindings/Intrinsics.h>
|
|
#include <LibWeb/HTML/BroadcastChannel.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/StorageAPI/StorageKey.h>
|
|
|
|
namespace Web::HTML {
|
|
|
|
class BroadcastChannelRepository {
|
|
public:
|
|
void register_channel(GC::Ref<BroadcastChannel>);
|
|
void unregister_channel(GC::Ref<BroadcastChannel>);
|
|
auto const& registered_channels_for_key(StorageAPI::StorageKey) const;
|
|
|
|
private:
|
|
HashMap<StorageAPI::StorageKey, Vector<GC::Weak<BroadcastChannel>>> m_channels;
|
|
};
|
|
|
|
void BroadcastChannelRepository::register_channel(GC::Ref<BroadcastChannel> channel)
|
|
{
|
|
auto storage_key = StorageAPI::obtain_a_storage_key_for_non_storage_purposes(relevant_settings_object(*channel));
|
|
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();
|
|
relevant_channels.remove_first_matching([&](auto c) { return c == channel; });
|
|
}
|
|
|
|
auto const& BroadcastChannelRepository::registered_channels_for_key(StorageAPI::StorageKey key) const
|
|
{
|
|
auto maybe_channels = m_channels.get(key);
|
|
VERIFY(maybe_channels.has_value());
|
|
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);
|
|
|
|
GC::Ref<BroadcastChannel> BroadcastChannel::construct_impl(JS::Realm& realm, FlyString const& name)
|
|
{
|
|
auto channel = realm.create<BroadcastChannel>(realm, name);
|
|
s_broadcast_channel_repository.register_channel(channel);
|
|
return channel;
|
|
}
|
|
|
|
BroadcastChannel::BroadcastChannel(JS::Realm& realm, FlyString const& name)
|
|
: DOM::EventTarget(realm)
|
|
, m_channel_name(name)
|
|
{
|
|
}
|
|
|
|
void BroadcastChannel::initialize(JS::Realm& realm)
|
|
{
|
|
WEB_SET_PROTOTYPE_FOR_INTERFACE(BroadcastChannel);
|
|
Base::initialize(realm);
|
|
}
|
|
|
|
void BroadcastChannel::finalize()
|
|
{
|
|
s_broadcast_channel_repository.unregister_channel(*this);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/web-messaging.html#eligible-for-messaging
|
|
bool BroadcastChannel::is_eligible_for_messaging() const
|
|
{
|
|
// A BroadcastChannel object is said to be eligible for messaging when its relevant global object is either:
|
|
auto const& global = relevant_global_object(*this);
|
|
|
|
// * a Window object whose associated Document is fully active, or
|
|
if (auto* window = as_if<Window>(global))
|
|
return window->associated_document().is_fully_active();
|
|
|
|
// * a WorkerGlobalScope object whose closing flag is false and is not suspendable.
|
|
// FIXME: Suspendable worker
|
|
if (auto* worker_global_scope = as_if<WorkerGlobalScope>(global)) {
|
|
return !worker_global_scope->is_closing();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/web-messaging.html#dom-broadcastchannel-postmessage
|
|
WebIDL::ExceptionOr<void> BroadcastChannel::post_message(JS::Value message)
|
|
{
|
|
auto& vm = this->vm();
|
|
|
|
// 1. If this is not eligible for messaging, then return.
|
|
if (!is_eligible_for_messaging())
|
|
return {};
|
|
|
|
// 2. If this's closed flag is true, then throw an "InvalidStateError" DOMException.
|
|
if (m_closed_flag)
|
|
return WebIDL::InvalidStateError::create(realm(), "BroadcastChannel.postMessage() on a closed channel"_utf16);
|
|
|
|
// 3. Let serialized be StructuredSerialize(message). Rethrow any exceptions.
|
|
auto serialized = TRY(structured_serialize(vm, message));
|
|
|
|
// 4. Let sourceOrigin be this's relevant settings object's origin.
|
|
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));
|
|
|
|
// 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);
|
|
|
|
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())
|
|
continue;
|
|
|
|
destinations.append(*channel);
|
|
}
|
|
|
|
// 7. Remove source from destinations.
|
|
destinations.remove_first_matching([&](auto destination) { return destination == this; });
|
|
|
|
// 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] {
|
|
// 1. If destination's closed flag is true, then abort these steps.
|
|
if (destination->m_closed_flag)
|
|
return;
|
|
|
|
// 2. Let targetRealm be destination's relevant realm.
|
|
auto& target_realm = relevant_realm(destination);
|
|
|
|
// 3. Let data be StructuredDeserialize(serialized, targetRealm).
|
|
// If this throws an exception, catch it, fire an event named messageerror at destination, using MessageEvent, with the
|
|
// origin attribute initialized to the serialization of sourceOrigin, and then abort these steps.
|
|
auto data_or_error = structured_deserialize(vm, serialized, target_realm);
|
|
if (data_or_error.is_exception()) {
|
|
MessageEventInit event_init {};
|
|
event_init.origin = 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
|
|
// origin attribute initialized to the serialization of sourceOrigin.
|
|
MessageEventInit event_init {};
|
|
event_init.data = data_or_error.release_value();
|
|
event_init.origin = 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
|
|
void BroadcastChannel::close()
|
|
{
|
|
// The close() method steps are to set this's closed flag to true.
|
|
m_closed_flag = true;
|
|
|
|
s_broadcast_channel_repository.unregister_channel(*this);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/web-messaging.html#handler-broadcastchannel-onmessage
|
|
void BroadcastChannel::set_onmessage(GC::Ptr<WebIDL::CallbackType> event_handler)
|
|
{
|
|
set_event_handler_attribute(HTML::EventNames::message, event_handler);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/web-messaging.html#handler-broadcastchannel-onmessage
|
|
GC::Ptr<WebIDL::CallbackType> BroadcastChannel::onmessage()
|
|
{
|
|
return event_handler_attribute(HTML::EventNames::message);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/web-messaging.html#handler-broadcastchannel-onmessageerror
|
|
void BroadcastChannel::set_onmessageerror(GC::Ptr<WebIDL::CallbackType> event_handler)
|
|
{
|
|
set_event_handler_attribute(HTML::EventNames::messageerror, event_handler);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/web-messaging.html#handler-broadcastchannel-onmessageerror
|
|
GC::Ptr<WebIDL::CallbackType> BroadcastChannel::onmessageerror()
|
|
{
|
|
return event_handler_attribute(HTML::EventNames::messageerror);
|
|
}
|
|
|
|
}
|