blob: 02ed844e0a7a6b54cf048633be54eae5b5d611ab [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// 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 "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
#include "third_party/blink/public/mojom/frame/back_forward_cache_controller.mojom-blink.h"
#include "third_party/blink/public/mojom/navigation/renderer_eviction_reason.mojom-blink.h"
#include "third_party/blink/public/mojom/use_counter/metrics/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/event_target_names.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/core/workers/worker_global_scope.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
namespace {
// To ensure proper ordering of messages sent to/from multiple BroadcastChannel
// instances in the same thread, this uses one BroadcastChannelProvider
// 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. Note that this
// approach only works in the case of workers, since each worker has it's own
// thread.
mojo::Remote<mojom::blink::BroadcastChannelProvider>&
GetWorkerThreadSpecificProvider(WorkerGlobalScope& worker_global_scope) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
ThreadSpecific<mojo::Remote<mojom::blink::BroadcastChannelProvider>>,
provider, ());
if (!provider.IsSet()) {
worker_global_scope.GetBrowserInterfaceBroker().GetInterface(
provider->BindNewPipeAndPassReceiver());
}
return *provider;
}
} // namespace
// static
BroadcastChannel* BroadcastChannel::Create(ExecutionContext* execution_context,
const String& name) {
LocalDOMWindow* window = DynamicTo<LocalDOMWindow>(execution_context);
if (window && window->IsCrossSiteSubframe())
UseCounter::Count(window, WebFeature::kThirdPartyBroadcastChannel);
return MakeGarbageCollected<BroadcastChannel>(execution_context, name);
}
BroadcastChannel::~BroadcastChannel() = default;
void BroadcastChannel::Dispose() {
CloseInternal();
}
void BroadcastChannel::postMessage(const ScriptValue& message,
ExceptionState& exception_state) {
// If the receiver is not bound because `close` was called on this
// BroadcastChannel instance, raise an exception per the spec. Otherwise,
// in cases like the instance being created in an iframe that is now detached,
// just ignore the postMessage call.
if (!receiver_.is_bound()) {
if (explicitly_closed_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Channel is closed");
}
return;
}
// Silently ignore the postMessage call if this BroadcastChannel instance is
// associated with a closing worker. This case needs to be handled explicitly
// because the mojo connection to the worker won't be torn down until the
// worker actually goes away.
ExecutionContext* execution_context = GetExecutionContext();
if (execution_context->IsWorkerGlobalScope()) {
WorkerGlobalScope* worker_global_scope =
DynamicTo<WorkerGlobalScope>(execution_context);
DCHECK(worker_global_scope);
if (worker_global_scope->IsClosing()) {
return;
}
}
scoped_refptr<SerializedScriptValue> value = SerializedScriptValue::Serialize(
message.GetIsolate(), message.V8Value(),
SerializedScriptValue::SerializeOptions(), exception_state);
if (exception_state.HadException())
return;
// Defer postMessage() from a prerendered page until page activation.
// https://wicg.github.io/nav-speculation/prerendering.html#patch-broadcast-channel
if (execution_context->IsWindow()) {
Document* document = To<LocalDOMWindow>(execution_context)->document();
if (document->IsPrerendering()) {
document->AddPostPrerenderingActivationStep(blink::BindOnce(
&BroadcastChannel::PostMessageInternal, WrapWeakPersistent(this),
std::move(value),
execution_context->GetSecurityOrigin()->IsolatedCopy(),
execution_context->GetAgentClusterID()));
return;
}
}
PostMessageInternal(std::move(value),
execution_context->GetSecurityOrigin()->IsolatedCopy(),
execution_context->GetAgentClusterID());
}
void BroadcastChannel::PostMessageInternal(
scoped_refptr<SerializedScriptValue> value,
scoped_refptr<SecurityOrigin> sender_origin,
const base::UnguessableToken sender_agent_cluster_id) {
if (!receiver_.is_bound())
return;
BlinkCloneableMessage msg;
msg.message = std::move(value);
msg.sender_origin = std::move(sender_origin);
msg.sender_agent_cluster_id = sender_agent_cluster_id;
msg.locked_to_sender_agent_cluster = msg.message->IsLockedToAgentCluster();
remote_client_->OnMessage(std::move(msg));
}
void BroadcastChannel::close() {
explicitly_closed_ = true;
CloseInternal();
}
void BroadcastChannel::CloseInternal() {
remote_client_.reset();
if (receiver_.is_bound())
receiver_.reset();
if (associated_remote_.is_bound())
associated_remote_.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() {
CloseInternal();
}
void BroadcastChannel::Trace(Visitor* visitor) const {
ExecutionContextLifecycleObserver::Trace(visitor);
EventTarget::Trace(visitor);
visitor->Trace(receiver_);
visitor->Trace(remote_client_);
visitor->Trace(associated_remote_);
}
void BroadcastChannel::OnMessage(BlinkCloneableMessage message) {
auto* context = GetExecutionContext();
// Queue a task to dispatch the event.
MessageEvent* event;
if ((!message.locked_to_sender_agent_cluster ||
context->IsSameAgentCluster(message.sender_agent_cluster_id)) &&
message.message->CanDeserializeIn(context)) {
event = MessageEvent::Create(nullptr, std::move(message.message),
context->GetSecurityOrigin(),
MessageEvent::kMessageIsSameOrigin,
/* last_event_id=*/{}, /* source=*/nullptr);
} else {
event = MessageEvent::CreateError(context->GetSecurityOrigin());
}
if (base::FeatureList::IsEnabled(features::kBFCacheOpenBroadcastChannel) &&
context->is_in_back_forward_cache()) {
LocalDOMWindow* window = DynamicTo<LocalDOMWindow>(context);
CHECK(window);
if (LocalFrame* frame = window->GetFrame()) {
base::UmaHistogramEnumeration(
"BackForwardCache.Eviction.Renderer",
mojom::blink::RendererEvictionReason::kBroadcastChannelOnMessage);
// We don't need to report the source location of a broadcast channel.
frame->GetBackForwardCacheControllerHostRemote()
.EvictFromBackForwardCache(
mojom::blink::RendererEvictionReason::kBroadcastChannelOnMessage,
/*source=*/nullptr);
}
return;
}
// <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() {
CloseInternal();
}
BroadcastChannel::BroadcastChannel(ExecutionContext* execution_context,
const String& name)
: BroadcastChannel(execution_context,
name,
mojo::NullAssociatedReceiver(),
mojo::NullAssociatedRemote()) {}
BroadcastChannel::BroadcastChannel(
base::PassKey<StorageAccessHandle>,
ExecutionContext* execution_context,
const String& name,
mojom::blink::BroadcastChannelProvider* provider)
: ActiveScriptWrappable<BroadcastChannel>({}),
ExecutionContextLifecycleObserver(execution_context),
name_(name),
receiver_(this, execution_context),
remote_client_(execution_context),
associated_remote_(execution_context) {
if (!base::FeatureList::IsEnabled(features::kBFCacheOpenBroadcastChannel)) {
feature_handle_for_scheduler_ =
execution_context->GetScheduler()->RegisterFeature(
SchedulingPolicy::Feature::kBroadcastChannel,
{SchedulingPolicy::DisableBackForwardCache()});
}
provider->ConnectToChannel(
name_,
receiver_.BindNewEndpointAndPassRemote(
execution_context->GetTaskRunner(TaskType::kInternalDefault)),
remote_client_.BindNewEndpointAndPassReceiver(
execution_context->GetTaskRunner(TaskType::kInternalDefault)));
SetupDisconnectHandlers();
}
BroadcastChannel::BroadcastChannel(
base::PassKey<BroadcastChannelTester>,
ExecutionContext* execution_context,
const String& name,
mojo::PendingAssociatedReceiver<mojom::blink::BroadcastChannelClient>
receiver,
mojo::PendingAssociatedRemote<mojom::blink::BroadcastChannelClient> remote)
: BroadcastChannel(execution_context,
name,
std::move(receiver),
std::move(remote)) {}
BroadcastChannel::BroadcastChannel(
ExecutionContext* execution_context,
const String& name,
mojo::PendingAssociatedReceiver<mojom::blink::BroadcastChannelClient>
receiver,
mojo::PendingAssociatedRemote<mojom::blink::BroadcastChannelClient> remote)
: ActiveScriptWrappable<BroadcastChannel>({}),
ExecutionContextLifecycleObserver(execution_context),
name_(name),
receiver_(this, execution_context),
remote_client_(execution_context),
associated_remote_(execution_context) {
if (!base::FeatureList::IsEnabled(features::kBFCacheOpenBroadcastChannel)) {
feature_handle_for_scheduler_ =
execution_context->GetScheduler()->RegisterFeature(
SchedulingPolicy::Feature::kBroadcastChannel,
{SchedulingPolicy::DisableBackForwardCache()});
}
// 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().
// The BroadcastChannel spec indicates that messages should be delivered to
// BroadcastChannel objects in the order in which they were created, so it's
// important that the ordering of ConnectToChannel messages (used to create
// the corresponding state in the browser process) is preserved. We accomplish
// this using two approaches, depending on the context:
//
// - In the frame case, we create a new navigation associated remote for each
// BroadcastChannel instance and leverage it to ensure in-order delivery
// and delivery to the RenderFrameHostImpl object that corresponds to the
// current frame.
//
// - In the worker case, since each worker runs in its own thread, we use a
// shared remote for all BroadcastChannel objects created on that thread to
// ensure in-order delivery of messages to the appropriate *WorkerHost
// object.
auto receiver_task_runner =
execution_context->GetTaskRunner(TaskType::kInternalDefault);
auto client_task_runner =
execution_context->GetTaskRunner(TaskType::kInternalDefault);
if (receiver.is_valid() && remote.is_valid()) {
receiver_.Bind(std::move(receiver), receiver_task_runner);
remote_client_.Bind(std::move(remote), client_task_runner);
} else if (auto* window = DynamicTo<LocalDOMWindow>(execution_context)) {
LocalFrame* frame = window->GetFrame();
if (!frame)
return;
frame->GetRemoteNavigationAssociatedInterfaces()->GetInterface(
associated_remote_.BindNewEndpointAndPassReceiver(
execution_context->GetTaskRunner(TaskType::kInternalDefault)));
associated_remote_->ConnectToChannel(
name_, receiver_.BindNewEndpointAndPassRemote(receiver_task_runner),
remote_client_.BindNewEndpointAndPassReceiver(client_task_runner));
} else if (auto* worker_global_scope =
DynamicTo<WorkerGlobalScope>(execution_context)) {
if (worker_global_scope->IsClosing())
return;
mojo::Remote<mojom::blink::BroadcastChannelProvider>& provider =
GetWorkerThreadSpecificProvider(*worker_global_scope);
provider->ConnectToChannel(
name_, receiver_.BindNewEndpointAndPassRemote(receiver_task_runner),
remote_client_.BindNewEndpointAndPassReceiver(client_task_runner));
} else {
NOTREACHED();
}
SetupDisconnectHandlers();
}
void BroadcastChannel::SetupDisconnectHandlers() {
receiver_.set_disconnect_handler(
BindOnce(&BroadcastChannel::OnError, WrapWeakPersistent(this)));
remote_client_.set_disconnect_handler(
BindOnce(&BroadcastChannel::OnError, WrapWeakPersistent(this)));
}
bool BroadcastChannel::IsRemoteClientConnectedForTesting() const {
return remote_client_.is_connected();
}
} // namespace blink