BroadcastChannel.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. /*
  2. * Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
  3. * Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibJS/Runtime/Realm.h>
  8. #include <LibWeb/Bindings/BroadcastChannelPrototype.h>
  9. #include <LibWeb/Bindings/Intrinsics.h>
  10. #include <LibWeb/HTML/BroadcastChannel.h>
  11. #include <LibWeb/HTML/EventNames.h>
  12. #include <LibWeb/HTML/MessageEvent.h>
  13. #include <LibWeb/HTML/Window.h>
  14. #include <LibWeb/HTML/WorkerGlobalScope.h>
  15. #include <LibWeb/StorageAPI/StorageKey.h>
  16. namespace Web::HTML {
  17. class BroadcastChannelRepository {
  18. public:
  19. void register_channel(GC::Root<BroadcastChannel>);
  20. void unregister_channel(GC::Ref<BroadcastChannel>);
  21. Vector<GC::Root<BroadcastChannel>> const& registered_channels_for_key(StorageAPI::StorageKey) const;
  22. private:
  23. HashMap<StorageAPI::StorageKey, Vector<GC::Root<BroadcastChannel>>> m_channels;
  24. };
  25. void BroadcastChannelRepository::register_channel(GC::Root<BroadcastChannel> channel)
  26. {
  27. auto storage_key = Web::StorageAPI::obtain_a_storage_key_for_non_storage_purposes(relevant_settings_object(*channel));
  28. auto maybe_channels = m_channels.find(storage_key);
  29. if (maybe_channels != m_channels.end()) {
  30. maybe_channels->value.append(move(channel));
  31. return;
  32. }
  33. Vector<GC::Root<BroadcastChannel>> channels;
  34. channels.append(move(channel));
  35. m_channels.set(storage_key, move(channels));
  36. }
  37. void BroadcastChannelRepository::unregister_channel(GC::Ref<BroadcastChannel> channel)
  38. {
  39. auto storage_key = Web::StorageAPI::obtain_a_storage_key_for_non_storage_purposes(relevant_settings_object(channel));
  40. auto& relevant_channels = m_channels.get(storage_key).value();
  41. relevant_channels.remove_first_matching([&](auto c) { return c == channel; });
  42. }
  43. Vector<GC::Root<BroadcastChannel>> const& BroadcastChannelRepository::registered_channels_for_key(StorageAPI::StorageKey key) const
  44. {
  45. auto maybe_channels = m_channels.get(key);
  46. VERIFY(maybe_channels.has_value());
  47. return maybe_channels.value();
  48. }
  49. // FIXME: This should not be static, and live at a storage partitioned level of the user agent.
  50. static BroadcastChannelRepository s_broadcast_channel_repository;
  51. GC_DEFINE_ALLOCATOR(BroadcastChannel);
  52. GC::Ref<BroadcastChannel> BroadcastChannel::construct_impl(JS::Realm& realm, FlyString const& name)
  53. {
  54. auto channel = realm.create<BroadcastChannel>(realm, name);
  55. s_broadcast_channel_repository.register_channel(channel);
  56. return channel;
  57. }
  58. BroadcastChannel::BroadcastChannel(JS::Realm& realm, FlyString const& name)
  59. : DOM::EventTarget(realm)
  60. , m_channel_name(name)
  61. {
  62. }
  63. void BroadcastChannel::initialize(JS::Realm& realm)
  64. {
  65. Base::initialize(realm);
  66. WEB_SET_PROTOTYPE_FOR_INTERFACE(BroadcastChannel);
  67. }
  68. // https://html.spec.whatwg.org/multipage/web-messaging.html#eligible-for-messaging
  69. bool BroadcastChannel::is_eligible_for_messaging() const
  70. {
  71. // A BroadcastChannel object is said to be eligible for messaging when its relevant global object is either:
  72. auto const& global = relevant_global_object(*this);
  73. // * a Window object whose associated Document is fully active, or
  74. if (is<Window>(global))
  75. return static_cast<Window const&>(global).associated_document().is_fully_active();
  76. // * a WorkerGlobalScope object whose closing flag is false and whose worker is not a suspendable worker.
  77. // FIXME: Suspendable worker
  78. if (is<WorkerGlobalScope>(global))
  79. return !static_cast<WorkerGlobalScope const&>(global).is_closing();
  80. return false;
  81. }
  82. // https://html.spec.whatwg.org/multipage/web-messaging.html#dom-broadcastchannel-postmessage
  83. WebIDL::ExceptionOr<void> BroadcastChannel::post_message(JS::Value message)
  84. {
  85. auto& vm = this->vm();
  86. // 1. If this is not eligible for messaging, then return.
  87. if (!is_eligible_for_messaging())
  88. return {};
  89. // 2. If this's closed flag is true, then throw an "InvalidStateError" DOMException.
  90. if (m_closed_flag)
  91. return WebIDL::InvalidStateError::create(realm(), "BroadcastChannel.postMessage() on a closed channel"_string);
  92. // 3. Let serialized be StructuredSerialize(message). Rethrow any exceptions.
  93. auto serialized = TRY(structured_serialize(vm, message));
  94. // 4. Let sourceOrigin be this's relevant settings object's origin.
  95. auto source_origin = relevant_settings_object(*this).origin();
  96. // 5. Let sourceStorageKey be the result of running obtain a storage key for non-storage purposes with this's relevant settings object.
  97. auto source_storage_key = Web::StorageAPI::obtain_a_storage_key_for_non_storage_purposes(relevant_settings_object(*this));
  98. // 6. Let destinations be a list of BroadcastChannel objects that match the following criteria:
  99. GC::RootVector<GC::Ref<BroadcastChannel>> destinations(vm.heap());
  100. // * The result of running obtain a storage key for non-storage purposes with their relevant settings object equals sourceStorageKey.
  101. auto same_origin_broadcast_channels = s_broadcast_channel_repository.registered_channels_for_key(source_storage_key);
  102. for (auto const& channel : same_origin_broadcast_channels) {
  103. // * They are eligible for messaging.
  104. if (!channel->is_eligible_for_messaging())
  105. continue;
  106. // * Their channel name is this's channel name.
  107. if (channel->name() != name())
  108. continue;
  109. destinations.append(*channel);
  110. }
  111. // 7. Remove source from destinations.
  112. destinations.remove_first_matching([&](auto destination) { return destination == this; });
  113. // FIXME: 8. Sort destinations such that all BroadcastChannel objects whose relevant agents are the same are sorted in creation order, oldest first.
  114. // (This does not define a complete ordering. Within this constraint, user agents may sort the list in any implementation-defined manner.)
  115. // 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:
  116. for (auto destination : destinations) {
  117. HTML::queue_global_task(HTML::Task::Source::DOMManipulation, relevant_global_object(destination), GC::create_function(vm.heap(), [&vm, serialized, destination, source_origin] {
  118. // 1. If destination's closed flag is true, then abort these steps.
  119. if (destination->m_closed_flag)
  120. return;
  121. // 2. Let targetRealm be destination's relevant realm.
  122. auto& target_realm = relevant_realm(destination);
  123. // 3. Let data be StructuredDeserialize(serialized, targetRealm).
  124. // If this throws an exception, catch it, fire an event named messageerror at destination, using MessageEvent, with the
  125. // origin attribute initialized to the serialization of sourceOrigin, and then abort these steps.
  126. auto data_or_error = structured_deserialize(vm, serialized, target_realm);
  127. if (data_or_error.is_exception()) {
  128. MessageEventInit event_init {};
  129. event_init.origin = source_origin.serialize();
  130. auto event = MessageEvent::create(target_realm, HTML::EventNames::messageerror, event_init);
  131. event->set_is_trusted(true);
  132. destination->dispatch_event(event);
  133. return;
  134. }
  135. // 4. Fire an event named message at destination, using MessageEvent, with the data attribute initialized to data and the
  136. // origin attribute initialized to the serialization of sourceOrigin.
  137. MessageEventInit event_init {};
  138. event_init.data = data_or_error.release_value();
  139. event_init.origin = source_origin.serialize();
  140. auto event = MessageEvent::create(target_realm, HTML::EventNames::message, event_init);
  141. event->set_is_trusted(true);
  142. destination->dispatch_event(event);
  143. }));
  144. }
  145. return {};
  146. }
  147. // https://html.spec.whatwg.org/multipage/web-messaging.html#dom-broadcastchannel-close
  148. void BroadcastChannel::close()
  149. {
  150. // The close() method steps are to set this's closed flag to true.
  151. m_closed_flag = true;
  152. s_broadcast_channel_repository.unregister_channel(*this);
  153. }
  154. // https://html.spec.whatwg.org/multipage/web-messaging.html#handler-broadcastchannel-onmessage
  155. void BroadcastChannel::set_onmessage(GC::Ptr<WebIDL::CallbackType> event_handler)
  156. {
  157. set_event_handler_attribute(HTML::EventNames::message, event_handler);
  158. }
  159. // https://html.spec.whatwg.org/multipage/web-messaging.html#handler-broadcastchannel-onmessage
  160. GC::Ptr<WebIDL::CallbackType> BroadcastChannel::onmessage()
  161. {
  162. return event_handler_attribute(HTML::EventNames::message);
  163. }
  164. // https://html.spec.whatwg.org/multipage/web-messaging.html#handler-broadcastchannel-onmessageerror
  165. void BroadcastChannel::set_onmessageerror(GC::Ptr<WebIDL::CallbackType> event_handler)
  166. {
  167. set_event_handler_attribute(HTML::EventNames::messageerror, event_handler);
  168. }
  169. // https://html.spec.whatwg.org/multipage/web-messaging.html#handler-broadcastchannel-onmessageerror
  170. GC::Ptr<WebIDL::CallbackType> BroadcastChannel::onmessageerror()
  171. {
  172. return event_handler_attribute(HTML::EventNames::messageerror);
  173. }
  174. }