chromium/fuchsia_web/runners/cast/api_bindings_client_browsertest.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <fuchsia/web/cpp/fidl.h>
#include <lib/fidl/cpp/binding.h>

#include "base/barrier_closure.h"
#include "base/files/file_util.h"
#include "base/fuchsia/mem_buffer_util.h"
#include "base/path_service.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "components/cast/message_port/fuchsia/create_web_message.h"
#include "components/cast/message_port/fuchsia/message_port_fuchsia.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/runners/cast/api_bindings_client.h"
#include "fuchsia_web/runners/cast/named_message_port_connector_fuchsia.h"
#include "fuchsia_web/runners/cast/test/fake_api_bindings.h"
#include "fuchsia_web/webengine/test/web_engine_browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

class ApiBindingsClientTest : public WebEngineBrowserTest {
 public:
  ApiBindingsClientTest() : api_service_binding_(&api_service_) {
    set_test_server_root(base::FilePath("fuchsia_web/runners/cast/testdata"));
  }

  ~ApiBindingsClientTest() override = default;

  ApiBindingsClientTest(const ApiBindingsClientTest&) = delete;
  ApiBindingsClientTest& operator=(const ApiBindingsClientTest&) = delete;

  void SetUp() override { WebEngineBrowserTest::SetUp(); }

 protected:
  void StartClient(bool disconnect_before_attach,
                   base::OnceClosure on_error_closure) {
    base::ScopedAllowBlockingForTesting allow_blocking;

    // Get the bindings from |api_service_|.
    base::RunLoop run_loop;
    client_ = std::make_unique<ApiBindingsClient>(
        api_service_binding_.NewBinding(), run_loop.QuitClosure());
    ASSERT_FALSE(client_->HasBindings());
    run_loop.Run();
    ASSERT_TRUE(client_->HasBindings());

    frame_ = FrameForTest::Create(context(), fuchsia::web::CreateFrameParams());
    connector_ =
        std::make_unique<NamedMessagePortConnectorFuchsia>(frame_.get());

    if (disconnect_before_attach)
      api_service_binding_.Unbind();

    base::RunLoop().RunUntilIdle();

    client_->AttachToFrame(frame_.get(), connector_.get(),
                           std::move(on_error_closure));
  }

  void SetUpOnMainThread() override {
    WebEngineBrowserTest::SetUpOnMainThread();
    ASSERT_TRUE(embedded_test_server()->Start());
  }

  void TearDownOnMainThread() override {
    // Destroy |client_| before the MessageLoop is destroyed.
    client_ = nullptr;

    // Destroy the NamedMessagePortConnector before the Frame.
    connector_ = nullptr;

    // Destroy the Frame before the test terminates
    frame_ = {};

    WebEngineBrowserTest::TearDownOnMainThread();
  }

  FrameForTest frame_;
  std::unique_ptr<NamedMessagePortConnectorFuchsia> connector_;
  FakeApiBindingsImpl api_service_;
  fidl::Binding<chromium::cast::ApiBindings> api_service_binding_;
  std::unique_ptr<ApiBindingsClient> client_;
};

// Tests API registration, injection, and message IPC.
// Registers a port that echoes messages received over a MessagePort back to the
// sender.
IN_PROC_BROWSER_TEST_F(ApiBindingsClientTest, EndToEnd) {
  // Define the injected bindings.
  std::vector<chromium::cast::ApiBinding> binding_list;
  chromium::cast::ApiBinding echo_binding;
  echo_binding.set_before_load_script(base::MemBufferFromString(
      "window.echo = cast.__platform__.PortConnector.bind('echoService');",
      "test"));
  binding_list.emplace_back(std::move(echo_binding));
  api_service_.set_bindings(std::move(binding_list));

  StartClient(false, base::MakeExpectedNotRunClosure(FROM_HERE));

  base::RunLoop post_message_responses_loop;
  base::RepeatingClosure post_message_response_closure =
      base::BarrierClosure(2, post_message_responses_loop.QuitClosure());

  // Navigate to a test page that makes use of the injected bindings.
  const GURL test_url = embedded_test_server()->GetURL("/echo.html");
  EXPECT_TRUE(LoadUrlAndExpectResponse(frame_.GetNavigationController(),
                                       fuchsia::web::LoadUrlParams(),
                                       test_url.spec()));
  frame_.navigation_listener().RunUntilUrlEquals(test_url);

  std::string connect_message;
  std::unique_ptr<cast_api_bindings::MessagePort> connect_port;
  connector_->GetConnectMessage(&connect_message, &connect_port);
  frame_->PostMessage(
      "*", CreateWebMessage(connect_message, std::move(connect_port)),
      [&post_message_response_closure](
          fuchsia::web::Frame_PostMessage_Result result) {
        ASSERT_TRUE(result.is_response());
        post_message_response_closure.Run();
      });

  // Connect to the echo service hosted by the page and send a ping to it.
  fuchsia::web::WebMessage message;
  message.set_data(base::MemBufferFromString("ping", "ping-msg"));
  fuchsia::web::MessagePortPtr port =
      api_service_.RunAndReturnConnectedPort("echoService").Bind();
  port->PostMessage(std::move(message),
                    [&post_message_response_closure](
                        fuchsia::web::MessagePort_PostMessage_Result result) {
                      ASSERT_TRUE(result.is_response());
                      post_message_response_closure.Run();
                    });

  // Handle the ping response.
  base::test::TestFuture<fuchsia::web::WebMessage> response;
  port->ReceiveMessage(CallbackToFitFunction(response.GetCallback()));
  ASSERT_TRUE(response.Wait());

  std::optional<std::string> response_string =
      base::StringFromMemBuffer(response.Get().data());
  ASSERT_TRUE(response_string.has_value());
  EXPECT_EQ("ack ping", *response_string);

  // Ensure that we've received acks for all messages.
  post_message_responses_loop.Run();
}

IN_PROC_BROWSER_TEST_F(ApiBindingsClientTest,
                       ClientDisconnectsBeforeFrameAttached) {
  bool error_signaled = false;
  StartClient(
      true, base::BindOnce([](bool* error_signaled) { *error_signaled = true; },
                           base::Unretained(&error_signaled)));

  // Verify that the error is signalled asynchronously.
  EXPECT_FALSE(error_signaled);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(error_signaled);
}

}  // namespace