2024-07-13 14:51:28 +01:00
/*
* Copyright ( c ) 2024 , Jamie Mansfield < jmansfield @ cadixdev . org >
2024-11-23 20:45:26 +13:00
* Copyright ( c ) 2024 , Shannon Booth < shannon @ serenityos . org >
2024-07-13 14:51:28 +01:00
*
* 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>
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>
# include <LibWeb/StorageAPI/StorageKey.h>
2024-07-13 14:51:28 +01:00
namespace Web : : HTML {
2024-11-23 20:45:26 +13:00
class BroadcastChannelRepository {
public :
void register_channel ( GC : : Root < BroadcastChannel > ) ;
void unregister_channel ( GC : : Ref < BroadcastChannel > ) ;
Vector < GC : : Root < BroadcastChannel > > const & registered_channels_for_key ( StorageAPI : : StorageKey ) const ;
private :
HashMap < StorageAPI : : StorageKey , Vector < GC : : Root < BroadcastChannel > > > m_channels ;
} ;
void BroadcastChannelRepository : : register_channel ( GC : : Root < BroadcastChannel > channel )
{
auto storage_key = Web : : StorageAPI : : obtain_a_storage_key_for_non_storage_purposes ( relevant_settings_object ( * channel ) ) ;
auto maybe_channels = m_channels . find ( storage_key ) ;
if ( maybe_channels ! = m_channels . end ( ) ) {
maybe_channels - > value . append ( move ( channel ) ) ;
return ;
}
Vector < GC : : Root < BroadcastChannel > > channels ;
channels . append ( move ( channel ) ) ;
m_channels . set ( storage_key , move ( channels ) ) ;
}
void BroadcastChannelRepository : : unregister_channel ( GC : : Ref < BroadcastChannel > channel )
{
auto storage_key = Web : : 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 ; } ) ;
}
Vector < GC : : Root < BroadcastChannel > > 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 ;
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
}
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
if ( is < Window > ( global ) )
return static_cast < Window const & > ( global ) . associated_document ( ) . is_fully_active ( ) ;
// * a WorkerGlobalScope object whose closing flag is false and whose worker is not a suspendable worker.
// FIXME: Suspendable worker
if ( is < WorkerGlobalScope > ( global ) )
return ! static_cast < WorkerGlobalScope const & > ( global ) . 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 )
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.
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:
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.
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 { } ;
}
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 ) ;
}
}