blob: 6ddd79325c69bc58b60a421bb1b33e0810ffdd0b [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/broadcastchannel/broadcast_channel.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
#include "third_party/blink/public/mojom/web_feature/web_feature.mojom-shared.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
#include "third_party/blink/renderer/core/events/message_event.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/platform/mojo/mojo_helper.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
namespace {
// To ensure proper ordering of messages send to/from multiple BroadcastChannel
// instances in the same thread this uses one BroadcastChannelService
// connection as basis for all connections to channels from the same thread. The
// actual connections used to send/receive messages are then created using
// associated interfaces, ensuring proper message ordering.
mojo::Remote<mojom::blink::BroadcastChannelProvider>&
GetThreadSpecificProvider() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
ThreadSpecific<mojo::Remote<mojom::blink::BroadcastChannelProvider>>,
provider, ());
if (!provider.IsSet()) {
Platform::Current()->GetBrowserInterfaceBroker()->GetInterface(
provider->BindNewPipeAndPassReceiver());
}
return *provider;
}
} // namespace
// static
BroadcastChannel* BroadcastChannel::Create(ExecutionContext* execution_context,
const String& name,
ExceptionState& exception_state) {
LocalDOMWindow* window = DynamicTo<LocalDOMWindow>(execution_context);
if (window && window->IsCrossSiteSubframe())
UseCounter::Count(window, WebFeature::kThirdPartyBroadcastChannel);
if (execution_context->GetSecurityOrigin()->IsOpaque()) {
// TODO(mek): Decide what to do here depending on
// https://github.com/whatwg/html/issues/1319
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"Can't create BroadcastChannel in an opaque origin");
return nullptr;
}
return MakeGarbageCollected<BroadcastChannel>(execution_context, name);
}
BroadcastChannel::~BroadcastChannel() = default;
void BroadcastChannel::Dispose() {
close();
}
void BroadcastChannel::postMessage(const ScriptValue& message,
ExceptionState& exception_state) {
if (!receiver_.is_bound()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Channel is closed");
return;
}
scoped_refptr<SerializedScriptValue> value = SerializedScriptValue::Serialize(
message.GetIsolate(), message.V8Value(),
SerializedScriptValue::SerializeOptions(), exception_state);
if (exception_state.HadException())
return;
BlinkCloneableMessage msg;
msg.message = std::move(value);
msg.sender_origin =
GetExecutionContext()->GetSecurityOrigin()->IsolatedCopy();
msg.sender_agent_cluster_id = GetExecutionContext()->GetAgentClusterID();
remote_client_->OnMessage(std::move(msg));
}
void BroadcastChannel::close() {
remote_client_.reset();
if (receiver_.is_bound())
receiver_.reset();
feature_handle_for_scheduler_.reset();
}
const AtomicString& BroadcastChannel::InterfaceName() const {
return event_target_names::kBroadcastChannel;
}
bool BroadcastChannel::HasPendingActivity() const {
return receiver_.is_bound() && HasEventListeners(event_type_names::kMessage);
}
void BroadcastChannel::ContextDestroyed() {
close();
}
void BroadcastChannel::Trace(Visitor* visitor) const {
ExecutionContextLifecycleObserver::Trace(visitor);
EventTargetWithInlineData::Trace(visitor);
}
void BroadcastChannel::OnMessage(BlinkCloneableMessage message) {
// Queue a task to dispatch the event.
MessageEvent* event;
if (!message.locked_to_sender_agent_cluster ||
GetExecutionContext()->IsSameAgentCluster(
message.sender_agent_cluster_id)) {
event = MessageEvent::Create(
nullptr, std::move(message.message),
GetExecutionContext()->GetSecurityOrigin()->ToString());
} else {
event = MessageEvent::CreateError(
GetExecutionContext()->GetSecurityOrigin()->ToString());
}
// <specdef
// href="https://html.spec.whatwg.org/multipage/web-messaging.html#dom-broadcastchannel-postmessage">
// <spec>The tasks must use the DOM manipulation task source, and, for
// those where the event loop specified by the target BroadcastChannel
// object's BroadcastChannel settings object is a window event loop,
// must be associated with the responsible document specified by that
// target BroadcastChannel object's BroadcastChannel settings object.
// </spec>
DispatchEvent(*event);
}
void BroadcastChannel::OnError() {
close();
}
BroadcastChannel::BroadcastChannel(ExecutionContext* execution_context,
const String& name)
: ExecutionContextLifecycleObserver(execution_context),
origin_(execution_context->GetSecurityOrigin()),
name_(name),
feature_handle_for_scheduler_(
execution_context->GetScheduler()->RegisterFeature(
SchedulingPolicy::Feature::kBroadcastChannel,
{SchedulingPolicy::DisableBackForwardCache()})) {
mojo::Remote<mojom::blink::BroadcastChannelProvider>& provider =
GetThreadSpecificProvider();
// Note: We cannot associate per-frame task runner here, but postTask
// to it manually via EnqueueEvent, since the current expectation
// is to receive messages even after close for which queued before
// close.
// https://github.com/whatwg/html/issues/1319
// Relying on Mojo binding will cancel the enqueued messages
// at close().
// Local BroadcastChannelClient for messages send from the browser to this
// channel and Remote BroadcastChannelClient for messages send from this
// channel to the browser.
provider->ConnectToChannel(origin_, name_,
receiver_.BindNewEndpointAndPassRemote(),
remote_client_.BindNewEndpointAndPassReceiver());
receiver_.set_disconnect_handler(
WTF::Bind(&BroadcastChannel::OnError, WrapWeakPersistent(this)));
remote_client_.set_disconnect_handler(
WTF::Bind(&BroadcastChannel::OnError, WrapWeakPersistent(this)));
}
} // namespace blink