2024-07-13 14:51:28 +01:00
/*
* Copyright ( c ) 2024 , Jamie Mansfield < jmansfield @ cadixdev . org >
2026-04-10 21:18:00 +02:00
* Copyright ( c ) 2024 - 2026 , Shannon Booth < shannon @ serenityos . org >
2024-07-13 14:51:28 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2026-04-10 21:18:00 +02:00
# include <AK/QuickSort.h>
# include <LibCore/System.h>
2024-07-13 14:51:28 +01:00
# include <LibJS/Runtime/Realm.h>
# include <LibWeb/Bindings/BroadcastChannelPrototype.h>
# include <LibWeb/Bindings/Intrinsics.h>
2026-04-10 21:18:00 +02:00
# include <LibWeb/Bindings/MainThreadVM.h>
# include <LibWeb/Bindings/PrincipalHostDefined.h>
2026-02-11 07:33:58 +01:00
# include <LibWeb/DOM/Document.h>
2024-07-13 14:51:28 +01:00
# include <LibWeb/HTML/BroadcastChannel.h>
2026-04-10 21:18:00 +02:00
# include <LibWeb/HTML/BroadcastChannelMessage.h>
2024-07-13 14:51:28 +01:00
# include <LibWeb/HTML/EventNames.h>
2024-11-23 20:45:26 +13:00
# include <LibWeb/HTML/MessageEvent.h>
2025-07-17 13:40:50 -04:00
# include <LibWeb/HTML/StructuredSerialize.h>
2024-11-23 20:45:26 +13:00
# include <LibWeb/HTML/Window.h>
# include <LibWeb/HTML/WorkerGlobalScope.h>
2026-04-10 21:18:00 +02:00
# include <LibWeb/Page/Page.h>
2024-11-23 20:45:26 +13:00
# include <LibWeb/StorageAPI/StorageKey.h>
2026-04-10 21:18:00 +02:00
# include <LibWeb/Worker/WebWorkerClient.h>
2024-07-13 14:51:28 +01:00
namespace Web : : HTML {
2024-11-23 20:45:26 +13:00
class BroadcastChannelRepository {
public :
2025-12-26 01:28:41 +01:00
void register_channel ( GC : : Ref < BroadcastChannel > ) ;
2024-11-23 20:45:26 +13:00
void unregister_channel ( GC : : Ref < BroadcastChannel > ) ;
2025-12-26 01:28:41 +01:00
auto const & registered_channels_for_key ( StorageAPI : : StorageKey ) const ;
2026-04-10 21:18:00 +02:00
[[nodiscard]] u64 next_channel_id ( ) { return + + m_next_channel_id ; }
2024-11-23 20:45:26 +13:00
private :
2025-12-26 01:28:41 +01:00
HashMap < StorageAPI : : StorageKey , Vector < GC : : Weak < BroadcastChannel > > > m_channels ;
2026-04-10 21:18:00 +02:00
u64 m_next_channel_id { 0 } ;
2024-11-23 20:45:26 +13:00
} ;
2025-12-26 01:28:41 +01:00
void BroadcastChannelRepository : : register_channel ( GC : : Ref < BroadcastChannel > channel )
2024-11-23 20:45:26 +13:00
{
2025-12-26 01:28:41 +01:00
auto storage_key = StorageAPI : : obtain_a_storage_key_for_non_storage_purposes ( relevant_settings_object ( * channel ) ) ;
2026-04-10 21:18:00 +02:00
channel - > m_channel_id = next_channel_id ( ) ;
2025-12-26 01:28:41 +01:00
m_channels . ensure ( storage_key ) . append ( channel ) ;
2024-11-23 20:45:26 +13:00
}
void BroadcastChannelRepository : : unregister_channel ( GC : : Ref < BroadcastChannel > channel )
{
2025-12-26 01:28:41 +01:00
auto storage_key = StorageAPI : : obtain_a_storage_key_for_non_storage_purposes ( relevant_settings_object ( channel ) ) ;
2026-04-10 21:18:00 +02:00
auto maybe_channels = m_channels . get ( storage_key ) ;
if ( ! maybe_channels . has_value ( ) )
return ;
auto & relevant_channels = maybe_channels . value ( ) ;
2024-11-23 20:45:26 +13:00
relevant_channels . remove_first_matching ( [ & ] ( auto c ) { return c = = channel ; } ) ;
2026-04-10 21:18:00 +02:00
if ( relevant_channels . is_empty ( ) )
m_channels . remove ( storage_key ) ;
2024-11-23 20:45:26 +13:00
}
2025-12-26 01:28:41 +01:00
auto const & BroadcastChannelRepository : : registered_channels_for_key ( StorageAPI : : StorageKey key ) const
2024-11-23 20:45:26 +13:00
{
2026-04-10 21:18:00 +02:00
static Vector < GC : : Weak < BroadcastChannel > > s_empty_channels ;
2024-11-23 20:45:26 +13:00
auto maybe_channels = m_channels . get ( key ) ;
2026-04-10 21:18:00 +02:00
if ( ! maybe_channels . has_value ( ) )
return s_empty_channels ;
2024-11-23 20:45:26 +13:00
return maybe_channels . value ( ) ;
}
static BroadcastChannelRepository s_broadcast_channel_repository ;
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( BroadcastChannel ) ;
2024-07-13 14:51:28 +01:00
2024-11-15 04:01:23 +13:00
GC : : Ref < BroadcastChannel > BroadcastChannel : : construct_impl ( JS : : Realm & realm , FlyString const & name )
2024-07-13 14:51:28 +01:00
{
2024-11-23 20:45:26 +13:00
auto channel = realm . create < BroadcastChannel > ( realm , name ) ;
s_broadcast_channel_repository . register_channel ( channel ) ;
return channel ;
2024-07-13 14:51:28 +01:00
}
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 ) ;
2025-04-20 16:22:57 +02:00
Base : : initialize ( realm ) ;
2024-07-13 14:51:28 +01:00
}
2025-12-26 01:28:41 +01:00
void BroadcastChannel : : finalize ( )
{
2026-01-29 20:46:37 +01:00
Base : : finalize ( ) ;
2025-12-26 01:28:41 +01:00
s_broadcast_channel_repository . unregister_channel ( * this ) ;
}
2024-11-23 20:45:26 +13:00
// 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
2025-11-27 14:01:01 +00:00
if ( auto * window = as_if < Window > ( global ) )
return window - > associated_document ( ) . is_fully_active ( ) ;
2024-11-23 20:45:26 +13:00
2025-11-27 14:01:01 +00:00
// * a WorkerGlobalScope object whose closing flag is false and is not suspendable.
2024-11-23 20:45:26 +13:00
// FIXME: Suspendable worker
2025-11-27 14:01:01 +00:00
if ( auto * worker_global_scope = as_if < WorkerGlobalScope > ( global ) ) {
return ! worker_global_scope - > is_closing ( ) ;
}
2024-11-23 20:45:26 +13:00
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 )
2025-08-07 19:31:52 -04:00
return WebIDL : : InvalidStateError : : create ( realm ( ) , " BroadcastChannel.postMessage() on a closed channel " _utf16 ) ;
2024-11-23 20:45:26 +13:00
// 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.
2026-04-10 21:18:00 +02:00
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 ( ) ;
2024-11-23 20:45:26 +13:00
// 6. Let destinations be a list of BroadcastChannel objects that match the following criteria:
2024-12-26 14:32:52 +01:00
GC : : RootVector < GC : : Ref < BroadcastChannel > > destinations ( vm . heap ( ) ) ;
2024-11-23 20:45:26 +13:00
// * The result of running obtain a storage key for non-storage purposes with their relevant settings object equals sourceStorageKey.
2026-04-10 21:18:00 +02:00
auto same_origin_broadcast_channels = s_broadcast_channel_repository . registered_channels_for_key ( message . storage_key ) ;
2024-11-23 20:45:26 +13:00
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.
2026-04-10 21:18:00 +02:00
if ( channel - > name ( ) ! = message . channel_name )
2024-11-23 20:45:26 +13:00
continue ;
destinations . append ( * channel ) ;
}
// 7. Remove source from destinations.
2026-04-10 21:18:00 +02:00
destinations . remove_all_matching ( [ & ] ( auto destination ) {
return message . source_process_id = = Core : : System : : getpid ( ) & & destination - > m_channel_id = = message . source_channel_id ;
} ) ;
2024-11-23 20:45:26 +13:00
// 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 ) {
2026-04-10 21:18:00 +02:00
HTML : : queue_global_task ( HTML : : Task : : Source : : DOMManipulation , relevant_global_object ( destination ) , GC : : create_function ( vm . heap ( ) , [ & vm , destination , message ] {
2024-11-23 20:45:26 +13:00
// 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).
2026-03-17 06:55:56 +01:00
// 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.
2026-04-10 21:18:00 +02:00
auto data_or_error = structured_deserialize ( vm , message . serialized_message , target_realm ) ;
2024-11-23 20:45:26 +13:00
if ( data_or_error . is_exception ( ) ) {
MessageEventInit event_init { } ;
2026-04-10 21:18:00 +02:00
event_init . origin = message . source_origin . serialize ( ) ;
2024-11-23 20:45:26 +13:00
auto event = MessageEvent : : create ( target_realm , HTML : : EventNames : : messageerror , event_init ) ;
event - > set_is_trusted ( true ) ;
destination - > dispatch_event ( event ) ;
return ;
}
2026-04-10 21:18:00 +02:00
// 4. Fire an event named message at destination, using MessageEvent, with the data attribute initialized to data and
2026-03-17 06:55:56 +01:00
// its origin initialized to sourceOrigin.
2024-11-23 20:45:26 +13:00
MessageEventInit event_init { } ;
event_init . data = data_or_error . release_value ( ) ;
2026-04-10 21:18:00 +02:00
event_init . origin = message . source_origin . serialize ( ) ;
2024-11-23 20:45:26 +13:00
auto event = MessageEvent : : create ( target_realm , HTML : : EventNames : : message , event_init ) ;
event - > set_is_trusted ( true ) ;
destination - > dispatch_event ( event ) ;
} ) ) ;
}
}
2024-07-13 14:51:28 +01:00
// 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 ;
2024-11-23 20:45:26 +13:00
s_broadcast_channel_repository . unregister_channel ( * this ) ;
2024-07-13 14:51:28 +01:00
}
// https://html.spec.whatwg.org/multipage/web-messaging.html#handler-broadcastchannel-onmessage
2024-11-23 16:26:03 +13:00
void BroadcastChannel : : set_onmessage ( GC : : Ptr < WebIDL : : CallbackType > event_handler )
2024-07-13 14:51:28 +01:00
{
set_event_handler_attribute ( HTML : : EventNames : : message , event_handler ) ;
}
// https://html.spec.whatwg.org/multipage/web-messaging.html#handler-broadcastchannel-onmessage
2024-11-23 16:26:03 +13:00
GC : : Ptr < WebIDL : : CallbackType > BroadcastChannel : : onmessage ( )
2024-07-13 14:51:28 +01:00
{
return event_handler_attribute ( HTML : : EventNames : : message ) ;
}
// https://html.spec.whatwg.org/multipage/web-messaging.html#handler-broadcastchannel-onmessageerror
2024-11-23 16:26:03 +13:00
void BroadcastChannel : : set_onmessageerror ( GC : : Ptr < WebIDL : : CallbackType > event_handler )
2024-07-13 14:51:28 +01:00
{
set_event_handler_attribute ( HTML : : EventNames : : messageerror , event_handler ) ;
}
// https://html.spec.whatwg.org/multipage/web-messaging.html#handler-broadcastchannel-onmessageerror
2024-11-23 16:26:03 +13:00
GC : : Ptr < WebIDL : : CallbackType > BroadcastChannel : : onmessageerror ( )
2024-07-13 14:51:28 +01:00
{
return event_handler_attribute ( HTML : : EventNames : : messageerror ) ;
}
}