blob: 79bb60d145627ec8ccbece9a4316c424ccdae599 [file] [log] [blame]
// Copyright 2022 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 <iterator>
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
#include "third_party/blink/renderer/core/event_type_names.h"
#include "third_party/blink/renderer/core/events/message_event.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/messaging/blink_cloneable_message.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_associated_receiver.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_associated_remote.h"
#include "third_party/blink/renderer/platform/testing/task_environment.h"
#include "v8/include/v8.h"
namespace blink {
class BroadcastChannelTester : public GarbageCollected<BroadcastChannelTester>,
public mojom::blink::BroadcastChannelClient {
public:
explicit BroadcastChannelTester(ExecutionContext* execution_context)
: receiver_(this, execution_context), remote_(execution_context) {
// Ideally, these would share a pipe. This is more convenient.
mojo::PendingAssociatedReceiver<mojom::blink::BroadcastChannelClient>
receiver0;
mojo::PendingAssociatedRemote<mojom::blink::BroadcastChannelClient>
remote0 = receiver0.InitWithNewEndpointAndPassRemote();
receiver0.EnableUnassociatedUsage();
mojo::PendingAssociatedReceiver<mojom::blink::BroadcastChannelClient>
receiver1;
mojo::PendingAssociatedRemote<mojom::blink::BroadcastChannelClient>
remote1 = receiver1.InitWithNewEndpointAndPassRemote();
receiver1.EnableUnassociatedUsage();
scoped_refptr<base::SequencedTaskRunner> task_runner =
execution_context->GetTaskRunner(TaskType::kInternalTest);
receiver_.Bind(std::move(receiver0), task_runner);
remote_.Bind(std::move(remote1), task_runner);
channel_ = MakeGarbageCollected<BroadcastChannel>(
base::PassKey<BroadcastChannelTester>(), execution_context,
"BroadcastChannelTester", std::move(receiver1), std::move(remote0));
auto* listener = MakeGarbageCollected<EventListener>(this);
channel_->addEventListener(event_type_names::kMessage, listener);
channel_->addEventListener(event_type_names::kMessageerror, listener);
}
BroadcastChannel* channel() const { return channel_.Get(); }
const HeapVector<Member<MessageEvent>>& received_events() const {
return received_events_;
}
const Vector<BlinkCloneableMessage>& sent_messages() const {
return sent_messages_;
}
void AwaitNextUpdate(base::OnceClosure closure) {
on_next_update_.push_back(std::move(closure));
}
void PostMessage(BlinkCloneableMessage message) {
remote_->OnMessage(std::move(message));
}
void Trace(Visitor* visitor) const {
visitor->Trace(channel_);
visitor->Trace(received_events_);
visitor->Trace(receiver_);
visitor->Trace(remote_);
}
// BroadcastChannelClient
void OnMessage(BlinkCloneableMessage message) override {
sent_messages_.push_back(std::move(message));
ReportUpdate();
}
private:
class EventListener : public NativeEventListener {
public:
explicit EventListener(BroadcastChannelTester* tester) : tester_(tester) {}
void Trace(Visitor* visitor) const override {
NativeEventListener::Trace(visitor);
visitor->Trace(tester_);
}
void Invoke(ExecutionContext*, Event* event) override {
tester_->received_events_.push_back(static_cast<MessageEvent*>(event));
tester_->ReportUpdate();
}
private:
Member<BroadcastChannelTester> tester_;
};
void ReportUpdate() {
Vector<base::OnceClosure> closures = std::move(on_next_update_);
for (auto& closure : closures)
std::move(closure).Run();
}
Member<BroadcastChannel> channel_;
HeapVector<Member<MessageEvent>> received_events_;
Vector<BlinkCloneableMessage> sent_messages_;
Vector<base::OnceClosure> on_next_update_;
HeapMojoAssociatedReceiver<mojom::blink::BroadcastChannelClient,
BroadcastChannelTester>
receiver_;
HeapMojoAssociatedRemote<mojom::blink::BroadcastChannelClient> remote_;
};
namespace {
BlinkCloneableMessage MakeNullMessage() {
BlinkCloneableMessage message;
message.message = SerializedScriptValue::NullValue();
message.sender_agent_cluster_id = base::UnguessableToken::Create();
return message;
}
TEST(BroadcastChannelTest, DispatchMessageEvent) {
test::TaskEnvironment task_environment;
DummyPageHolder holder;
ExecutionContext* execution_context = holder.GetFrame().DomWindow();
auto* tester =
MakeGarbageCollected<BroadcastChannelTester>(execution_context);
base::RunLoop run_loop;
tester->AwaitNextUpdate(run_loop.QuitClosure());
tester->PostMessage(MakeNullMessage());
run_loop.Run();
ASSERT_EQ(tester->received_events().size(), 1u);
EXPECT_EQ(tester->received_events()[0]->type(), event_type_names::kMessage);
}
TEST(BroadcastChannelTest, AgentClusterLockedMatch) {
test::TaskEnvironment task_environment;
DummyPageHolder holder;
ExecutionContext* execution_context = holder.GetFrame().DomWindow();
auto* tester =
MakeGarbageCollected<BroadcastChannelTester>(execution_context);
base::RunLoop run_loop;
tester->AwaitNextUpdate(run_loop.QuitClosure());
BlinkCloneableMessage message = MakeNullMessage();
message.sender_agent_cluster_id = execution_context->GetAgentClusterID();
message.locked_to_sender_agent_cluster = true;
tester->PostMessage(std::move(message));
run_loop.Run();
ASSERT_EQ(tester->received_events().size(), 1u);
EXPECT_EQ(tester->received_events()[0]->type(), event_type_names::kMessage);
}
TEST(BroadcastChannelTest, AgentClusterLockedMismatch) {
test::TaskEnvironment task_environment;
DummyPageHolder holder;
ExecutionContext* execution_context = holder.GetFrame().DomWindow();
auto* tester =
MakeGarbageCollected<BroadcastChannelTester>(execution_context);
base::RunLoop run_loop;
tester->AwaitNextUpdate(run_loop.QuitClosure());
BlinkCloneableMessage message = MakeNullMessage();
message.locked_to_sender_agent_cluster = true;
tester->PostMessage(std::move(message));
run_loop.Run();
ASSERT_EQ(tester->received_events().size(), 1u);
EXPECT_EQ(tester->received_events()[0]->type(),
event_type_names::kMessageerror);
}
TEST(BroadcastChannelTest, MessageCannotDeserialize) {
test::TaskEnvironment task_environment;
DummyPageHolder holder;
LocalDOMWindow* window = holder.GetFrame().DomWindow();
auto* tester = MakeGarbageCollected<BroadcastChannelTester>(window);
SerializedScriptValue::ScopedOverrideCanDeserializeInForTesting
override_can_deserialize_in(base::BindLambdaForTesting(
[&](const SerializedScriptValue& value,
ExecutionContext* execution_context, bool can_deserialize) {
EXPECT_EQ(execution_context, window);
EXPECT_TRUE(can_deserialize);
return false;
}));
base::RunLoop run_loop;
tester->AwaitNextUpdate(run_loop.QuitClosure());
tester->PostMessage(MakeNullMessage());
run_loop.Run();
ASSERT_EQ(tester->received_events().size(), 1u);
EXPECT_EQ(tester->received_events()[0]->type(),
event_type_names::kMessageerror);
}
TEST(BroadcastChannelTest, OutgoingMessagesMarkedWithAgentClusterId) {
test::TaskEnvironment task_environment;
DummyPageHolder holder;
ExecutionContext* execution_context = holder.GetFrame().DomWindow();
ScriptState* script_state = ToScriptStateForMainWorld(&holder.GetFrame());
auto* tester =
MakeGarbageCollected<BroadcastChannelTester>(execution_context);
base::RunLoop run_loop;
tester->AwaitNextUpdate(run_loop.QuitClosure());
{
ScriptState::Scope scope(script_state);
tester->channel()->postMessage(
ScriptValue::CreateNull(script_state->GetIsolate()),
ASSERT_NO_EXCEPTION);
}
run_loop.Run();
ASSERT_EQ(tester->sent_messages().size(), 1u);
EXPECT_EQ(tester->sent_messages()[0].sender_agent_cluster_id,
execution_context->GetAgentClusterID());
EXPECT_FALSE(tester->sent_messages()[0].locked_to_sender_agent_cluster);
}
// TODO(crbug.com/1413818): iOS doesn't support WebAssembly yet.
#if BUILDFLAG(IS_IOS)
#define MAYBE_OutgoingAgentClusterLockedMessage \
DISABLED_OutgoingAgentClusterLockedMessage
#else
#define MAYBE_OutgoingAgentClusterLockedMessage \
OutgoingAgentClusterLockedMessage
#endif
TEST(BroadcastChannelTest, MAYBE_OutgoingAgentClusterLockedMessage) {
test::TaskEnvironment task_environment;
DummyPageHolder holder;
ExecutionContext* execution_context = holder.GetFrame().DomWindow();
ScriptState* script_state = ToScriptStateForMainWorld(&holder.GetFrame());
v8::Isolate* isolate = script_state->GetIsolate();
auto* tester =
MakeGarbageCollected<BroadcastChannelTester>(execution_context);
base::RunLoop run_loop;
tester->AwaitNextUpdate(run_loop.QuitClosure());
{
// WebAssembly modules are always agent cluster locked. This is a trivial
// one with no functionality, just the minimal magic and version.
static constexpr uint8_t kTrivialModuleBytes[] = {0x00, 0x61, 0x73, 0x6d,
0x01, 0x00, 0x00, 0x00};
ScriptState::Scope scope(script_state);
v8::Local<v8::WasmModuleObject> trivial_module =
v8::WasmModuleObject::Compile(isolate, {std::data(kTrivialModuleBytes),
std::size(kTrivialModuleBytes)})
.ToLocalChecked();
tester->channel()->postMessage(ScriptValue(isolate, trivial_module),
ASSERT_NO_EXCEPTION);
}
run_loop.Run();
ASSERT_EQ(tester->sent_messages().size(), 1u);
EXPECT_EQ(tester->sent_messages()[0].sender_agent_cluster_id,
execution_context->GetAgentClusterID());
EXPECT_TRUE(tester->sent_messages()[0].locked_to_sender_agent_cluster);
}
} // namespace
} // namespace blink