// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/fuchsia/mem_buffer_util.h"
#include "base/test/test_future.h"
#include "content/public/test/browser_test.h"
#include "fuchsia_web/common/test/fit_adapter.h"
#include "fuchsia_web/common/test/frame_for_test.h"
#include "fuchsia_web/common/test/frame_test_util.h"
#include "fuchsia_web/common/test/test_navigation_listener.h"
#include "fuchsia_web/webengine/browser/frame_impl_browser_test_base.h"
namespace {
constexpr char kPage1Path[] = "/title1.html";
constexpr char kPage1Title[] = "title 1";
// Defines a suite of tests that exercise Frame-level post message
// functionality.
class PostMessageTest : public FrameImplTestBase {
public:
PostMessageTest() = default;
~PostMessageTest() override = default;
PostMessageTest(const PostMessageTest&) = delete;
PostMessageTest& operator=(const PostMessageTest&) = delete;
};
IN_PROC_BROWSER_TEST_F(PostMessageTest, SendData) {
auto frame = FrameForTest::Create(context(), {});
net::test_server::EmbeddedTestServerHandle test_server_handle;
ASSERT_TRUE(test_server_handle =
embedded_test_server()->StartAndReturnHandle());
GURL post_message_url(
embedded_test_server()->GetURL("/window_post_message.html"));
EXPECT_TRUE(LoadUrlAndExpectResponse(frame.GetNavigationController(),
fuchsia::web::LoadUrlParams(),
post_message_url.spec()));
frame.navigation_listener().RunUntilUrlAndTitleEquals(post_message_url,
"postmessage");
fuchsia::web::WebMessage message;
message.set_data(base::MemBufferFromString(kPage1Path, "test"));
base::test::TestFuture<fuchsia::web::Frame_PostMessage_Result> post_result;
frame->PostMessage(post_message_url.DeprecatedGetOriginAsURL().spec(),
std::move(message),
CallbackToFitFunction(post_result.GetCallback()));
ASSERT_TRUE(post_result.Wait());
frame.navigation_listener().RunUntilUrlAndTitleEquals(
embedded_test_server()->GetURL(kPage1Path), kPage1Title);
EXPECT_TRUE(post_result.Get().is_response());
}
// Send a MessagePort to the content, then perform bidirectional messaging
// through the port.
IN_PROC_BROWSER_TEST_F(PostMessageTest, PassMessagePort) {
auto frame = FrameForTest::Create(context(), {});
net::test_server::EmbeddedTestServerHandle test_server_handle;
ASSERT_TRUE(test_server_handle =
embedded_test_server()->StartAndReturnHandle());
GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
EXPECT_TRUE(LoadUrlAndExpectResponse(frame.GetNavigationController(),
fuchsia::web::LoadUrlParams(),
post_message_url.spec()));
frame.navigation_listener().RunUntilUrlAndTitleEquals(post_message_url,
"messageport");
fuchsia::web::MessagePortPtr message_port;
{
base::test::TestFuture<fuchsia::web::Frame_PostMessage_Result> post_result;
frame->PostMessage(
post_message_url.DeprecatedGetOriginAsURL().spec(),
CreateWebMessageWithMessagePortRequest(
message_port.NewRequest(), base::MemBufferFromString("hi", "test")),
CallbackToFitFunction(post_result.GetCallback()));
base::test::TestFuture<fuchsia::web::WebMessage> receiver;
message_port->ReceiveMessage(CallbackToFitFunction(receiver.GetCallback()));
ASSERT_TRUE(receiver.Wait());
ASSERT_TRUE(receiver.Get().has_data());
EXPECT_EQ("got_port", *base::StringFromMemBuffer(receiver.Get().data()));
}
{
fuchsia::web::WebMessage msg;
msg.set_data(base::MemBufferFromString("ping", "test"));
base::test::TestFuture<fuchsia::web::MessagePort_PostMessage_Result>
post_result;
message_port->PostMessage(std::move(msg),
CallbackToFitFunction(post_result.GetCallback()));
base::test::TestFuture<fuchsia::web::WebMessage> receiver;
message_port->ReceiveMessage(CallbackToFitFunction(receiver.GetCallback()));
ASSERT_TRUE(post_result.Wait());
ASSERT_TRUE(receiver.Wait());
ASSERT_TRUE(receiver.Get().has_data());
EXPECT_EQ("ack ping", *base::StringFromMemBuffer(receiver.Get().data()));
EXPECT_TRUE(post_result.Get().is_response());
}
}
// Send a MessagePort to the content, then perform bidirectional messaging
// over its channel.
IN_PROC_BROWSER_TEST_F(PostMessageTest, MessagePortDisconnected) {
auto frame = FrameForTest::Create(context(), {});
net::test_server::EmbeddedTestServerHandle test_server_handle;
ASSERT_TRUE(test_server_handle =
embedded_test_server()->StartAndReturnHandle());
GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
EXPECT_TRUE(LoadUrlAndExpectResponse(frame.GetNavigationController(),
fuchsia::web::LoadUrlParams(),
post_message_url.spec()));
frame.navigation_listener().RunUntilUrlAndTitleEquals(post_message_url,
"messageport");
fuchsia::web::MessagePortPtr message_port;
{
base::test::TestFuture<fuchsia::web::Frame_PostMessage_Result> post_result;
frame->PostMessage(
post_message_url.DeprecatedGetOriginAsURL().spec(),
CreateWebMessageWithMessagePortRequest(
message_port.NewRequest(), base::MemBufferFromString("hi", "test")),
CallbackToFitFunction(post_result.GetCallback()));
base::test::TestFuture<fuchsia::web::WebMessage> receiver;
message_port->ReceiveMessage(CallbackToFitFunction(receiver.GetCallback()));
ASSERT_TRUE(post_result.Wait());
ASSERT_TRUE(receiver.Wait());
ASSERT_TRUE(receiver.IsReady());
EXPECT_EQ("got_port", *base::StringFromMemBuffer(receiver.Get().data()));
EXPECT_TRUE(post_result.Get().is_response());
}
// Navigating off-page should tear down the Mojo channel, thereby causing the
// MessagePortImpl to self-destruct and tear down its FIDL channel.
{
base::RunLoop run_loop;
message_port.set_error_handler(
[&run_loop](zx_status_t) { run_loop.Quit(); });
EXPECT_TRUE(LoadUrlAndExpectResponse(frame.GetNavigationController(),
fuchsia::web::LoadUrlParams(),
url::kAboutBlankURL));
run_loop.Run();
}
}
// Send a MessagePort to the content, and through that channel, receive a
// different MessagePort that was created by the content. Verify the second
// channel's liveness by sending a ping to it.
IN_PROC_BROWSER_TEST_F(PostMessageTest, UseContentProvidedPort) {
auto frame = FrameForTest::Create(context(), {});
net::test_server::EmbeddedTestServerHandle test_server_handle;
ASSERT_TRUE(test_server_handle =
embedded_test_server()->StartAndReturnHandle());
GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
EXPECT_TRUE(LoadUrlAndExpectResponse(frame.GetNavigationController(),
fuchsia::web::LoadUrlParams(),
post_message_url.spec()));
frame.navigation_listener().RunUntilUrlAndTitleEquals(post_message_url,
"messageport");
fuchsia::web::MessagePortPtr incoming_message_port;
{
fuchsia::web::MessagePortPtr message_port;
base::test::TestFuture<fuchsia::web::Frame_PostMessage_Result> post_result;
frame->PostMessage(
"*",
CreateWebMessageWithMessagePortRequest(
message_port.NewRequest(), base::MemBufferFromString("hi", "test")),
CallbackToFitFunction(post_result.GetCallback()));
base::test::TestFuture<fuchsia::web::WebMessage> receiver;
message_port->ReceiveMessage(CallbackToFitFunction(receiver.GetCallback()));
ASSERT_TRUE(receiver.Wait());
ASSERT_TRUE(receiver.Get().has_data());
EXPECT_EQ("got_port", *base::StringFromMemBuffer(receiver.Get().data()));
ASSERT_TRUE(receiver.Get().has_incoming_transfer());
ASSERT_EQ(receiver.Get().incoming_transfer().size(), 1u);
incoming_message_port = receiver.Take()
.mutable_incoming_transfer()
->at(0)
.message_port()
.Bind();
EXPECT_TRUE(post_result.Get().is_response());
}
// Get the content to send three 'ack ping' messages, which will accumulate in
// the MessagePortImpl buffer.
for (int i = 0; i < 3; ++i) {
base::test::TestFuture<fuchsia::web::MessagePort_PostMessage_Result>
post_result;
fuchsia::web::WebMessage msg;
msg.set_data(base::MemBufferFromString("ping", "test"));
incoming_message_port->PostMessage(
std::move(msg), CallbackToFitFunction(post_result.GetCallback()));
ASSERT_TRUE(post_result.Wait());
EXPECT_TRUE(post_result.Get().is_response());
}
// Receive another acknowledgement from content on a side channel to ensure
// that all the "ack pings" are ready to be consumed.
{
fuchsia::web::MessagePortPtr ack_message_port;
// Quit the runloop only after we've received a WebMessage AND a PostMessage
// result.
base::test::TestFuture<fuchsia::web::Frame_PostMessage_Result> post_result;
frame->PostMessage("*",
CreateWebMessageWithMessagePortRequest(
ack_message_port.NewRequest(),
base::MemBufferFromString("hi", "test")),
CallbackToFitFunction(post_result.GetCallback()));
base::test::TestFuture<fuchsia::web::WebMessage> receiver;
ack_message_port->ReceiveMessage(
CallbackToFitFunction(receiver.GetCallback()));
ASSERT_TRUE(receiver.Wait());
ASSERT_TRUE(receiver.Get().has_data());
EXPECT_EQ("got_port", *base::StringFromMemBuffer(receiver.Get().data()));
EXPECT_TRUE(post_result.Get().is_response());
}
// Pull the three 'ack ping's from the buffer.
for (int i = 0; i < 3; ++i) {
base::test::TestFuture<fuchsia::web::WebMessage> receiver;
incoming_message_port->ReceiveMessage(
CallbackToFitFunction(receiver.GetCallback()));
ASSERT_TRUE(receiver.Wait());
ASSERT_TRUE(receiver.Get().has_data());
EXPECT_EQ("ack ping", *base::StringFromMemBuffer(receiver.Get().data()));
}
}
IN_PROC_BROWSER_TEST_F(PostMessageTest, BadOriginDropped) {
auto frame = FrameForTest::Create(context(), {});
net::test_server::EmbeddedTestServerHandle test_server_handle;
ASSERT_TRUE(test_server_handle =
embedded_test_server()->StartAndReturnHandle());
GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
EXPECT_TRUE(LoadUrlAndExpectResponse(frame.GetNavigationController(),
fuchsia::web::LoadUrlParams(),
post_message_url.spec()));
frame.navigation_listener().RunUntilUrlAndTitleEquals(post_message_url,
"messageport");
// PostMessage() to invalid origins should be ignored. We pass in a
// MessagePort but nothing should happen to it.
fuchsia::web::MessagePortPtr bad_origin_incoming_message_port;
base::test::TestFuture<fuchsia::web::Frame_PostMessage_Result>
unused_post_result;
frame->PostMessage("https://example.com",
CreateWebMessageWithMessagePortRequest(
bad_origin_incoming_message_port.NewRequest(),
base::MemBufferFromString("bad origin, bad!", "test")),
CallbackToFitFunction(unused_post_result.GetCallback()));
base::test::TestFuture<fuchsia::web::WebMessage> unused_message_read;
bad_origin_incoming_message_port->ReceiveMessage(
CallbackToFitFunction(unused_message_read.GetCallback()));
// PostMessage() with a valid origin should succeed.
// Verify it by looking for an ack message on the MessagePort we passed in.
// Since message events are handled in order, observing the result of this
// operation will verify whether the previous PostMessage() was received but
// discarded.
fuchsia::web::MessagePortPtr incoming_message_port;
fuchsia::web::MessagePortPtr message_port;
base::test::TestFuture<fuchsia::web::Frame_PostMessage_Result> post_result;
frame->PostMessage("*",
CreateWebMessageWithMessagePortRequest(
message_port.NewRequest(),
base::MemBufferFromString("good origin", "test")),
CallbackToFitFunction(post_result.GetCallback()));
base::test::TestFuture<fuchsia::web::WebMessage> receiver;
message_port->ReceiveMessage(CallbackToFitFunction(receiver.GetCallback()));
ASSERT_TRUE(receiver.Wait());
ASSERT_TRUE(receiver.Get().has_data());
EXPECT_EQ("got_port", *base::StringFromMemBuffer(receiver.Get().data()));
ASSERT_TRUE(receiver.Get().has_incoming_transfer());
ASSERT_EQ(receiver.Get().incoming_transfer().size(), 1u);
incoming_message_port =
receiver.Take().mutable_incoming_transfer()->at(0).message_port().Bind();
EXPECT_TRUE(post_result.Get().is_response());
// Verify that the first PostMessage() call wasn't handled.
EXPECT_FALSE(unused_message_read.IsReady());
}
} // namespace