chromium/fuchsia_web/webengine/browser/post_message_browsertest.cc

// 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