ladybird/Libraries/LibWeb/HTML/BroadcastChannel.cpp
Andreas Kling 164ed80244 Meta: Enable exit-time destructor warnings for libraries
Enable -Wexit-time-destructors for all in-tree library targets and
update process-lifetime library statics so they no longer register
exit-time destructors. Long-lived caches, lookup tables, singleton
registries, and generated constants now use NeverDestroyed or leaked
references where the data is intended to live until process exit.

Update LibWeb, LibLine, and the binding generators so regenerated
sources follow the same rule instead of reintroducing destructed
statics.
2026-06-04 19:20:49 +02:00

261 lines
10 KiB
C++

/*
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (c) 2024-2026, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/NeverDestroyed.h>
#include <AK/QuickSort.h>
#include <LibCore/System.h>
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/Bindings/BroadcastChannel.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Bindings/MessageEvent.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/MessagePort.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>
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;
[[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 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 NeverDestroyed<Vector<GC::Weak<BroadcastChannel>>> empty_channels;
auto maybe_channels = m_channels.get(key);
if (!maybe_channels.has_value())
return *empty_channels;
return maybe_channels.value();
}
static BroadcastChannelRepository& broadcast_channel_repository()
{
static NeverDestroyed<BroadcastChannelRepository> repository;
return *repository;
}
GC_DEFINE_ALLOCATOR(BroadcastChannel);
GC::Ref<BroadcastChannel> BroadcastChannel::construct_impl(JS::Realm& realm, FlyString const& name)
{
auto channel = realm.create<BroadcastChannel>(realm, name);
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()
{
Base::finalize();
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 = 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);
Bindings::principal_host_defined_page(realm()).client().page_did_post_broadcast_channel_message(message_to_send);
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;
// * The result of running obtain a storage key for non-storage purposes with their relevant settings object equals sourceStorageKey.
auto same_origin_broadcast_channels = 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() != message.channel_name)
continue;
destinations.append(*channel);
}
// 7. Remove source from destinations.
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, destination, message] {
// 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 its
// origin initialized to sourceOrigin, and then abort these steps.
auto data_or_error = structured_deserialize(vm, message.serialized_message, target_realm);
if (data_or_error.is_exception()) {
Bindings::MessageEventInit event_init;
auto event = MessageEvent::create(target_realm, HTML::EventNames::messageerror, event_init, message.source_origin);
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
// its origin initialized to sourceOrigin.
Bindings::MessageEventInit event_init;
event_init.data = data_or_error.release_value();
auto event = MessageEvent::create(target_realm, HTML::EventNames::message, event_init, message.source_origin);
event->set_is_trusted(true);
destination->dispatch_event(event);
}));
}
}
// 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;
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);
}
}