text: // Copyright 2019 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #ifndef CHROMECAST_BROWSER_CAST_WEB_CONTENTS_BROWSERTEST_H_
  #define CHROMECAST_BROWSER_CAST_WEB_CONTENTS_BROWSERTEST_H_
  
  #include <algorithm>
  #include <memory>
  #include <string>
  
  #include "base/callback_helpers.h"
  #include "base/check_op.h"
  #include "base/command_line.h"
  #include "base/containers/flat_set.h"
  #include "base/files/file_util.h"
  #include "base/path_service.h"
  #include "base/run_loop.h"
  #include "base/strings/strcat.h"
  #include "base/strings/string_piece.h"
  #include "base/strings/stringprintf.h"
  #include "base/strings/utf_string_conversions.h"
  #include "base/test/bind.h"
  #include "base/threading/sequenced_task_runner_handle.h"
  #include "chromecast/base/chromecast_switches.h"
  #include "chromecast/base/metrics/cast_metrics_helper.h"
  #include "chromecast/browser/cast_browser_context.h"
  #include "chromecast/browser/cast_browser_process.h"
  #include "chromecast/browser/cast_web_contents_impl.h"
  #include "chromecast/browser/cast_web_contents_observer.h"
  #include "chromecast/browser/mojom/cast_web_service.mojom.h"
  #include "chromecast/browser/test_interfaces.test-mojom.h"
  #include "chromecast/mojo/interface_bundle.h"
  #include "content/public/browser/browser_thread.h"
  #include "content/public/browser/web_contents.h"
  #include "content/public/browser/web_contents_delegate.h"
  #include "content/public/browser/web_contents_observer.h"
  #include "content/public/common/content_switches.h"
  #include "content/public/test/browser_test.h"
  #include "content/public/test/browser_test_base.h"
  #include "content/public/test/browser_test_utils.h"
  #include "content/public/test/url_loader_interceptor.h"
  #include "mojo/public/cpp/bindings/pending_receiver.h"
  #include "mojo/public/cpp/bindings/pending_remote.h"
  #include "mojo/public/cpp/bindings/receiver_set.h"
  #include "mojo/public/cpp/bindings/remote.h"
  #include "net/http/http_status_code.h"
  #include "net/test/embedded_test_server/embedded_test_server.h"
  #include "net/test/embedded_test_server/http_request.h"
  #include "net/test/embedded_test_server/http_response.h"
  #include "services/service_manager/public/cpp/interface_provider.h"
  #include "services/service_manager/public/mojom/interface_provider.mojom.h"
  #include "testing/gmock/include/gmock/gmock.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "url/gurl.h"
  
  using ::testing::_;
  using ::testing::AllOf;
  using ::testing::AtLeast;
  using ::testing::ElementsAre;
  using ::testing::Eq;
  using ::testing::Expectation;
  using ::testing::InSequence;
  using ::testing::Invoke;
  using ::testing::InvokeWithoutArgs;
  using ::testing::Mock;
  using ::testing::NiceMock;
  using ::testing::Property;
  using ::testing::WithArgs;
  
  namespace content {
  class WebContents;
  }
  
  namespace chromecast {
  
  namespace {
  
  const base::FilePath::CharType kTestDataPath[] =
      FILE_PATH_LITERAL("chromecast/browser/test/data");
  
  base::FilePath GetTestDataPath() {
    return base::FilePath(kTestDataPath);
  }
  
  base::FilePath GetTestDataFilePath(const std::string& name) {
    base::FilePath file_path;
    CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &file_path));
    return file_path.Append(GetTestDataPath()).AppendASCII(name);
  }
  
  std::unique_ptr<net::test_server::HttpResponse> DefaultHandler(
      net::HttpStatusCode status_code,
      const net::test_server::HttpRequest& request) {
    auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
    http_response->set_code(status_code);
    return http_response;
  }
  
  // =============================================================================
  // Mocks
  // =============================================================================
  class MockCastWebContentsObserver : public CastWebContentsObserver {
   public:
    MockCastWebContentsObserver() {}
  
    MockCastWebContentsObserver(const MockCastWebContentsObserver&) = delete;
    MockCastWebContentsObserver& operator=(const MockCastWebContentsObserver&) =
        delete;
  
    ~MockCastWebContentsObserver() override = default;
  
    MOCK_METHOD1(PageStateChanged, void(PageState page_state));
    MOCK_METHOD2(PageStopped, void(PageState page_state, int error_code));
    MOCK_METHOD3(RenderFrameCreated,
                 void(int render_process_id,
                      int render_frame_id,
                      mojo::PendingAssociatedRemote<
                          chromecast::mojom::IdentificationSettingsManager>
                          settings_manager));
    MOCK_METHOD0(ResourceLoadFailed, void());
    MOCK_METHOD1(UpdateTitle, void(const std::string& title));
  };
  
  class MockWebContentsDelegate : public content::WebContentsDelegate {
   public:
    MockWebContentsDelegate() = default;
    ~MockWebContentsDelegate() override = default;
  
    MOCK_METHOD1(CloseContents, void(content::WebContents* source));
  };
  
  class TitleChangeObserver : public CastWebContentsObserver {
   public:
    TitleChangeObserver() = default;
  
    TitleChangeObserver(const TitleChangeObserver&) = delete;
    TitleChangeObserver& operator=(const TitleChangeObserver&) = delete;
  
    ~TitleChangeObserver() override = default;
  
    // Spins a Runloop until the title of the page matches the |expected_title|
    // that have been set.
    void RunUntilTitleEquals(const std::string& expected_title) {
      expected_title_ = expected_title;
      // Spin the runloop until the expected conditions are met.
      if (current_title_ != expected_title_) {
        expected_title_ = expected_title;
        base::RunLoop run_loop;
        quit_closure_ = run_loop.QuitClosure();
        run_loop.Run();
      }
    }
  
    // CastWebContentsObserver implementation:
    void UpdateTitle(const std::string& title) override {
      // Resumes execution of RunUntilTitleEquals() if |title| matches
      // expectations.
      current_title_ = title;
      if (!quit_closure_.is_null() && current_title_ == expected_title_) {
        DCHECK_EQ(current_title_, expected_title_);
        std::move(quit_closure_).Run();
      }
    }
  
   private:
    std::string current_title_;
    std::string expected_title_;
  
    base::OnceClosure quit_closure_;
  };
  
  class TestMessageReceiver : public blink::WebMessagePort::MessageReceiver {
   public:
    TestMessageReceiver() = default;
  
    TestMessageReceiver(const TestMessageReceiver&) = delete;
    TestMessageReceiver& operator=(const TestMessageReceiver&) = delete;
  
    ~TestMessageReceiver() override = default;
  
    void WaitForNextIncomingMessage(
        base::OnceCallback<
            void(std::string, absl::optional<blink::WebMessagePort>)> callback) {
      DCHECK(message_received_callback_.is_null())
          << "Only one waiting event is allowed.";
      message_received_callback_ = std::move(callback);
    }
  
    void SetOnPipeErrorCallback(base::OnceCallback<void()> callback) {
      on_pipe_error_callback_ = std::move(callback);
    }
  
   private:
    bool OnMessage(blink::WebMessagePort::Message message) override {
      std::string message_text;
      if (!base::UTF16ToUTF8(message.data.data(), message.data.size(),
                             &message_text)) {
        return false;
      }
  
      absl::optional<blink::WebMessagePort> incoming_port = absl::nullopt;
      // Only one MessagePort should be sent to here.
      if (!message.ports.empty()) {
        DCHECK(message.ports.size() == 1)
            << "Only one control port can be provided";
        incoming_port = absl::make_optional<blink::WebMessagePort>(
            std::move(message.ports[0]));
      }
  
      if (message_received_callback_) {
        std::move(message_received_callback_)
            .Run(message_text, std::move(incoming_port));
      }
      return true;
    }
  
    void OnPipeError() override {
      if (on_pipe_error_callback_)
        std::move(on_pipe_error_callback_).Run();
    }
  
    base::OnceCallback<void(std::string,
                            absl::optional<blink::WebMessagePort> incoming_port)>
        message_received_callback_;
  
    base::OnceCallback<void()> on_pipe_error_callback_;
  };
  
  }  // namespace
  
  // =============================================================================
  // Test class
  // =============================================================================
  class CastWebContentsBrowserTest : public content::BrowserTestBase,
                                     public content::WebContentsObserver {
   public:
    CastWebContentsBrowserTest(const CastWebContentsBrowserTest&) = delete;
    CastWebContentsBrowserTest& operator=(const CastWebContentsBrowserTest&) =
        delete;
  
   protected:
    CastWebContentsBrowserTest() = default;
    ~CastWebContentsBrowserTest() override = default;
  
    void SetUp() final {
      SetUpCommandLine(base::CommandLine::ForCurrentProcess());
      BrowserTestBase::SetUp();
    }
    void SetUpCommandLine(base::CommandLine* command_line) override {
      command_line->AppendSwitchASCII(switches::kTestType, "browser");
      command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, "MojoJS");
    }
    void PreRunTestOnMainThread() override {
      // Pump startup related events.
      DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
      base::RunLoop().RunUntilIdle();
  
      metrics::CastMetricsHelper::GetInstance()->SetDummySessionIdForTesting();
      content::WebContents::CreateParams create_params(
          shell::CastBrowserProcess::GetInstance()->browser_context(), nullptr);
      web_contents_ = content::WebContents::Create(create_params);
      web_contents_->SetDelegate(&mock_wc_delegate_);
  
      mojom::CastWebViewParamsPtr params = mojom::CastWebViewParams::New();
      params->is_root_window = true;
      cast_web_contents_ = std::make_unique<CastWebContentsImpl>(
          web_contents_.get(), std::move(params));
      mock_cast_wc_observer_.Observe(cast_web_contents_.get());
      title_change_observer_.Observe(cast_web_contents_.get());
  
      render_frames_.clear();
      content::WebContentsObserver::Observe(web_contents_.get());
  
      run_loop_ = std::make_unique<base::RunLoop>();
    }
    void PostRunTestOnMainThread() override {
      cast_web_contents_.reset();
      web_contents_.reset();
    }
  
    // content::WebContentsObserver implementation:
    void RenderFrameCreated(content::RenderFrameHost* render_frame_host) final {
      render_frames_.insert(render_frame_host);
    }
  
    void StartTestServer() {
      ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
      embedded_test_server()->StartAcceptingConnections();
    }
  
    void QuitRunLoop() {
      DCHECK(run_loop_);
      if (run_loop_->running()) {
        run_loop_->QuitWhenIdle();
      }
    }
  
    MockWebContentsDelegate mock_wc_delegate_;
    NiceMock<MockCastWebContentsObserver> mock_cast_wc_observer_;
    TitleChangeObserver title_change_observer_;
    std::unique_ptr<content::WebContents> web_contents_;
    std::unique_ptr<CastWebContentsImpl> cast_web_contents_;
    std::unique_ptr<base::RunLoop> run_loop_;
  
    base::flat_set<content::RenderFrameHost*> render_frames_;
  };
  
  MATCHER_P2(CheckPageState, cwc_ptr, expected_state, "") {
    if (arg != cwc_ptr)
      return false;
    return arg->page_state() == expected_state;
  }
  
  // =============================================================================
  // Test cases
  // =============================================================================
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, Lifecycle) {
    // ===========================================================================
    // Test: Load a blank page successfully, verify LOADED state.
    // ===========================================================================
    run_loop_ = std::make_unique<base::RunLoop>();
    {
      InSequence seq;
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADING));
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADED))
          .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    }
  
    cast_web_contents_->LoadUrl(GURL(url::kAboutBlankURL));
    run_loop_->Run();
  
    // ===========================================================================
    // Test: Load a blank page via WebContents API, verify LOADED state.
    // ===========================================================================
    run_loop_ = std::make_unique<base::RunLoop>();
    {
      InSequence seq;
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADING));
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADED))
          .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    }
  
    web_contents_->GetController().LoadURL(GURL(url::kAboutBlankURL),
                                           content::Referrer(),
                                           ui::PAGE_TRANSITION_TYPED, "");
    run_loop_->Run();
  
    // ===========================================================================
    // Test: Inject an iframe, verify no events are received for the frame.
    // ===========================================================================
    EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(_)).Times(0);
    EXPECT_CALL(mock_cast_wc_observer_, PageStopped(_, _)).Times(0);
    std::string script =
        "var iframe = document.createElement('iframe');"
        "document.body.appendChild(iframe);"
        "iframe.src = 'about:blank';";
    ASSERT_TRUE(ExecJs(web_contents_.get(), script));
  
    // ===========================================================================
    // Test: Inject an iframe and navigate it to an error page. Verify no events.
    // ===========================================================================
    EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(_)).Times(0);
    EXPECT_CALL(mock_cast_wc_observer_, PageStopped(_, _)).Times(0);
    script = "iframe.src = 'https://www.fake-non-existent-cast-page.com';";
    ASSERT_TRUE(ExecJs(web_contents_.get(), script));
  
    // ===========================================================================
    // Test: Close the CastWebContents. WebContentsDelegate will be told to close
    // the page, and then after the timeout elapses CWC will enter the CLOSED
    // state and notify that the page has stopped.
    // ===========================================================================
    run_loop_ = std::make_unique<base::RunLoop>();
    EXPECT_CALL(mock_wc_delegate_, CloseContents(web_contents_.get()))
        .Times(AtLeast(1));
    EXPECT_CALL(mock_cast_wc_observer_, PageStopped(PageState::CLOSED, net::OK))
        .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    cast_web_contents_->ClosePage();
    run_loop_->Run();
  
    // ===========================================================================
    // Test: Destroy the underlying WebContents. Verify DESTROYED state.
    // ===========================================================================
    run_loop_ = std::make_unique<base::RunLoop>();
    EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::DESTROYED))
        .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    web_contents_.reset();
    run_loop_->Run();
    cast_web_contents_.reset();
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, WebContentsDestroyed) {
    run_loop_ = std::make_unique<base::RunLoop>();
    {
      InSequence seq;
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADING));
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADED))
          .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    }
  
    cast_web_contents_->LoadUrl(GURL(url::kAboutBlankURL));
    run_loop_->Run();
  
    // ===========================================================================
    // Test: Destroy the WebContents. Verify PageStopped(DESTROYED, net::OK).
    // ===========================================================================
    run_loop_ = std::make_unique<base::RunLoop>();
    EXPECT_CALL(mock_cast_wc_observer_,
                PageStopped(PageState::DESTROYED, net::OK))
        .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    web_contents_.reset();
    run_loop_->Run();
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, ErrorPageCrash) {
    // ===========================================================================
    // Test: If the page's main render process crashes, enter ERROR state.
    // ===========================================================================
    run_loop_ = std::make_unique<base::RunLoop>();
    {
      InSequence seq;
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADING));
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADED))
          .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    }
  
    cast_web_contents_->LoadUrl(GURL(url::kAboutBlankURL));
    run_loop_->Run();
  
    run_loop_ = std::make_unique<base::RunLoop>();
    EXPECT_CALL(mock_cast_wc_observer_,
                PageStopped(PageState::ERROR, net::ERR_UNEXPECTED))
        .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    CrashTab(web_contents_.get());
    run_loop_->Run();
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, ErrorLocalFileMissing) {
    // ===========================================================================
    // Test: Loading a page with an HTTP error should enter ERROR state.
    // ===========================================================================
    run_loop_ = std::make_unique<base::RunLoop>();
    {
      InSequence seq;
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADING));
      EXPECT_CALL(mock_cast_wc_observer_, PageStopped(PageState::ERROR, _))
          .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    }
  
    base::FilePath path = GetTestDataFilePath("this_file_does_not_exist.html");
    cast_web_contents_->LoadUrl(content::GetFileUrlWithQuery(path, ""));
    run_loop_->Run();
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, ErrorLoadFailSubFrames) {
    // ===========================================================================
    // Test: Ignore load errors in sub-frames.
    // ===========================================================================
    run_loop_ = std::make_unique<base::RunLoop>();
    {
      InSequence seq;
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADING));
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADED))
          .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    }
  
    cast_web_contents_->LoadUrl(GURL(url::kAboutBlankURL));
    run_loop_->Run();
  
    // Create a sub-frame.
    EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(_)).Times(0);
    EXPECT_CALL(mock_cast_wc_observer_, PageStopped(_, _)).Times(0);
    std::string script =
        "var iframe = document.createElement('iframe');"
        "document.body.appendChild(iframe);"
        "iframe.src = 'about:blank';";
    ASSERT_TRUE(ExecJs(web_contents_.get(), script));
  
    ASSERT_EQ(2, (int)render_frames_.size());
    auto it =
        std::find_if(render_frames_.begin(), render_frames_.end(),
                     [this](content::RenderFrameHost* frame) {
                       return frame->GetParent() == web_contents_->GetMainFrame();
                     });
    ASSERT_NE(render_frames_.end(), it);
    content::RenderFrameHost* sub_frame = *it;
    ASSERT_NE(nullptr, sub_frame);
    cast_web_contents_->DidFailLoad(sub_frame, sub_frame->GetLastCommittedURL(),
                                    net::ERR_FAILED);
  
    // ===========================================================================
    // Test: Ignore main frame load failures with net::ERR_ABORTED.
    // ===========================================================================
    EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(_)).Times(0);
    EXPECT_CALL(mock_cast_wc_observer_, PageStopped(_, _)).Times(0);
    cast_web_contents_->DidFailLoad(
        web_contents_->GetMainFrame(),
        web_contents_->GetMainFrame()->GetLastCommittedURL(), net::ERR_ABORTED);
  
    // ===========================================================================
    // Test: If main frame fails to load, page should enter ERROR state.
    // ===========================================================================
    run_loop_ = std::make_unique<base::RunLoop>();
    EXPECT_CALL(mock_cast_wc_observer_,
                PageStopped(PageState::ERROR, net::ERR_FAILED))
        .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    cast_web_contents_->DidFailLoad(
        web_contents_->GetMainFrame(),
        web_contents_->GetMainFrame()->GetLastCommittedURL(), net::ERR_FAILED);
    run_loop_->Run();
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, ErrorHttp4XX) {
    // ===========================================================================
    // Test: If a server responds with an HTTP 4XX error, page should enter ERROR
    // state.
    // ===========================================================================
    embedded_test_server()->RegisterRequestHandler(
        base::BindRepeating(&DefaultHandler, net::HTTP_NOT_FOUND));
    StartTestServer();
  
    run_loop_ = std::make_unique<base::RunLoop>();
    {
      InSequence seq;
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADING));
      EXPECT_CALL(
          mock_cast_wc_observer_,
          PageStopped(PageState::ERROR, net::ERR_HTTP_RESPONSE_CODE_FAILURE))
          .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    }
  
    cast_web_contents_->LoadUrl(embedded_test_server()->GetURL("/dummy.html"));
    run_loop_->Run();
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, ErrorLoadFailed) {
    // ===========================================================================
    // Test: When main frame load fails, enter ERROR state. This test simulates a
    // load error by intercepting the URL request and failing it with an arbitrary
    // error code.
    // ===========================================================================
    base::FilePath path = GetTestDataFilePath("dummy.html");
    GURL gurl = content::GetFileUrlWithQuery(path, "");
    content::URLLoaderInterceptor url_interceptor(base::BindRepeating(
        [](const GURL& url,
           content::URLLoaderInterceptor::RequestParams* params) {
          if (params->url_request.url != url)
            return false;
          network::URLLoaderCompletionStatus status;
          status.error_code = net::ERR_ADDRESS_UNREACHABLE;
          params->client->OnComplete(status);
          return true;
        },
        gurl));
  
    run_loop_ = std::make_unique<base::RunLoop>();
    {
      InSequence seq;
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADING));
      EXPECT_CALL(mock_cast_wc_observer_,
                  PageStopped(PageState::ERROR, net::ERR_ADDRESS_UNREACHABLE))
          .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    }
  
    cast_web_contents_->LoadUrl(gurl);
    run_loop_->Run();
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, LoadCanceledByApp) {
    // ===========================================================================
    // Test: When the app calls window.stop(), the page should not enter the ERROR
    // state. Instead, we treat it as LOADED. This is a historical behavior for
    // some apps which intentionally stop the page and reload content.
    // ===========================================================================
    embedded_test_server()->ServeFilesFromSourceDirectory(GetTestDataPath());
    StartTestServer();
  
    run_loop_ = std::make_unique<base::RunLoop>();
    {
      InSequence seq;
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADING));
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADED))
          .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    }
  
    cast_web_contents_->LoadUrl(
        embedded_test_server()->GetURL("/load_cancel.html"));
    run_loop_->Run();
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, LocationRedirectLifecycle) {
    // ===========================================================================
    // Test: When the app redirects to another url via window.location. Another
    // navigation will be committed. LOADING -> LOADED -> LOADING -> LOADED state
    // trasition is expected.
    // ===========================================================================
    embedded_test_server()->ServeFilesFromSourceDirectory(GetTestDataPath());
    StartTestServer();
  
    run_loop_ = std::make_unique<base::RunLoop>();
    {
      InSequence seq;
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADING));
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADED));
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADING));
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADED))
          .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    }
  
    cast_web_contents_->LoadUrl(
        embedded_test_server()->GetURL("/location_redirect.html"));
    run_loop_->Run();
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, NotifyMissingResource) {
    // ===========================================================================
    // Test: Loading a page with a missing resource should notify observers.
    // ===========================================================================
    run_loop_ = std::make_unique<base::RunLoop>();
    {
      InSequence seq;
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADING));
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADED))
          .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    }
    EXPECT_CALL(mock_cast_wc_observer_, ResourceLoadFailed());
  
    base::FilePath path = GetTestDataFilePath("missing_resource.html");
    cast_web_contents_->LoadUrl(content::GetFileUrlWithQuery(path, ""));
    run_loop_->Run();
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, ExecuteJavaScriptOnLoad) {
    // ===========================================================================
    // Test: Injecting script to change title should work.
    // ===========================================================================
    const std::string kExpectedTitle = "hello";
    const std::string kOriginalTitle =
        "Welcome to Stan the Offline Dino's Homepage";
  
    // The script should be able to run before HTML <script> tag starts running.
    // The original title will be loaded first and then the injected script. Other
    // scripts must run after the injected script.
    EXPECT_CALL(mock_cast_wc_observer_, UpdateTitle(kExpectedTitle));
    EXPECT_CALL(mock_cast_wc_observer_, UpdateTitle(kOriginalTitle));
    constexpr uint64_t kBindingsId = 1234;
  
    GURL gurl = content::GetFileUrlWithQuery(
        GetTestDataFilePath("dynamic_title.html"), "");
  
    cast_web_contents_->AddBeforeLoadJavaScript(kBindingsId,
                                                "stashed_title = 'hello';");
  
    cast_web_contents_->LoadUrl(gurl);
    title_change_observer_.RunUntilTitleEquals(kExpectedTitle);
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest,
                         ExecuteJavaScriptUpdatedOnLoad) {
    // ===========================================================================
    // Test: Verify that this script replaces the previous script with same
    // binding id, as opposed to being injected alongside it. (The latter would
    // result in the title being "helloclobber").
    // ===========================================================================
    const std::string kReplaceTitle = "clobber";
    const std::string kOriginalTitle =
        "Welcome to Stan the Offline Dino's Homepage";
  
    // The script should be able to run before HTML <script> tag starts running.
    EXPECT_CALL(mock_cast_wc_observer_, UpdateTitle(kReplaceTitle));
    EXPECT_CALL(mock_cast_wc_observer_, UpdateTitle(kOriginalTitle));
  
    constexpr uint64_t kBindingsId = 1234;
  
    GURL gurl = content::GetFileUrlWithQuery(
        GetTestDataFilePath("dynamic_title.html"), "");
  
    cast_web_contents_->AddBeforeLoadJavaScript(kBindingsId,
                                                "stashed_title = 'hello';");
  
    cast_web_contents_->AddBeforeLoadJavaScript(
        kBindingsId, "stashed_title = document.title + 'clobber';");
  
    cast_web_contents_->LoadUrl(gurl);
    title_change_observer_.RunUntilTitleEquals(kReplaceTitle);
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest,
                         ExecuteJavaScriptOnLoadOrdered) {
    // ===========================================================================
    // Test: Verifies that bindings are injected in order by producing a
    // cumulative, non-commutative result.
    // ===========================================================================
    const std::string kExpectedTitle = "hello there";
    const std::string kOriginalTitle =
        "Welcome to Stan the Offline Dino's Homepage";
    constexpr int64_t kBindingsId1 = 1234;
    constexpr int64_t kBindingsId2 = 5678;
  
    // The script should be able to run before HTML <script> tag starts running.
    // The original title will be loaded first and then the injected script. Other
    // scripts must run after the injected script.
    EXPECT_CALL(mock_cast_wc_observer_, UpdateTitle(kExpectedTitle));
    EXPECT_CALL(mock_cast_wc_observer_, UpdateTitle(kOriginalTitle));
  
    GURL gurl = content::GetFileUrlWithQuery(
        GetTestDataFilePath("dynamic_title.html"), "");
  
    cast_web_contents_->AddBeforeLoadJavaScript(kBindingsId1,
                                                "stashed_title = 'hello';");
  
    cast_web_contents_->AddBeforeLoadJavaScript(kBindingsId2,
                                                "stashed_title += ' there';");
  
    cast_web_contents_->LoadUrl(gurl);
    title_change_observer_.RunUntilTitleEquals(kExpectedTitle);
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest,
                         ExecuteJavaScriptOnLoadEarlyAndLateRegistrations) {
    // ===========================================================================
    // Test: Tests that we can inject scripts before and after RenderFrame
    // creation.
    // ===========================================================================
    const std::string kExpectedTitle1 = "foo";
    const std::string kExpectedTitle2 = "foo bar";
    const std::string kOriginalTitle =
        "Welcome to Stan the Offline Dino's Homepage";
    constexpr int64_t kBindingsId1 = 1234;
    constexpr int64_t kBindingsId2 = 5678;
  
    // The script should be able to run before HTML <script> tag starts running.
    // The original title will be loaded first and then the injected script. Other
    // scripts must run after the injected script.
    EXPECT_CALL(mock_cast_wc_observer_, UpdateTitle(kExpectedTitle2));
    EXPECT_CALL(mock_cast_wc_observer_, UpdateTitle(kExpectedTitle1));
    EXPECT_CALL(mock_cast_wc_observer_, UpdateTitle(kOriginalTitle)).Times(2);
  
    GURL gurl = content::GetFileUrlWithQuery(
        GetTestDataFilePath("dynamic_title.html"), "");
  
    cast_web_contents_->AddBeforeLoadJavaScript(kBindingsId1,
                                                "stashed_title = 'foo';");
    cast_web_contents_->LoadUrl(gurl);
    title_change_observer_.RunUntilTitleEquals(kExpectedTitle1);
  
    // Inject bindings after RenderFrameCreation
    cast_web_contents_->AddBeforeLoadJavaScript(kBindingsId2,
                                                "stashed_title += ' bar';");
  
    // Navigate away to clean the state.
    cast_web_contents_->LoadUrl(GURL(url::kAboutBlankURL));
  
    // Navigate back and see if both scripts are working.
    cast_web_contents_->LoadUrl(gurl);
    title_change_observer_.RunUntilTitleEquals(kExpectedTitle2);
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, PostMessageToMainFrame) {
    // ===========================================================================
    // Test: Tests that we can trigger onmessage event on a web page. This test
    // would post a message to the test page to redirect it to |title1.html|.
    // ===========================================================================
    constexpr char kOriginalTitle[] = "postmessage";
    constexpr char kPage1Path[] = "title1.html";
    constexpr char kPage1Title[] = "title 1";
  
    EXPECT_CALL(mock_cast_wc_observer_, UpdateTitle(kPage1Title));
    EXPECT_CALL(mock_cast_wc_observer_, UpdateTitle(kOriginalTitle));
  
    embedded_test_server()->ServeFilesFromSourceDirectory(GetTestDataPath());
    StartTestServer();
    GURL gurl = embedded_test_server()->GetURL("/window_post_message.html");
  
    cast_web_contents_->LoadUrl(gurl);
    title_change_observer_.RunUntilTitleEquals(kOriginalTitle);
  
    cast_web_contents_->PostMessageToMainFrame(
        gurl.DeprecatedGetOriginAsURL().spec(), kPage1Path,
        std::vector<blink::WebMessagePort>());
    title_change_observer_.RunUntilTitleEquals(kPage1Title);
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, PostMessagePassMessagePort) {
    // ===========================================================================
    // Test: Send a MessagePort to the page, then perform bidirectional messaging
    // through the port.
    // ===========================================================================
    constexpr char kOriginalTitle[] = "messageport";
    constexpr char kHelloMsg[] = "hi";
    constexpr char16_t kPingMsg[] = u"ping";
  
    EXPECT_CALL(mock_cast_wc_observer_, UpdateTitle(kOriginalTitle));
  
    // Load test page.
    embedded_test_server()->ServeFilesFromSourceDirectory(GetTestDataPath());
    StartTestServer();
    GURL gurl = embedded_test_server()->GetURL("/message_port.html");
    cast_web_contents_->LoadUrl(gurl);
    title_change_observer_.RunUntilTitleEquals(kOriginalTitle);
  
    auto message_pipe = blink::WebMessagePort::CreatePair();
    auto platform_port = std::move(message_pipe.first);
    auto page_port = std::move(message_pipe.second);
  
    TestMessageReceiver message_receiver;
    platform_port.SetReceiver(&message_receiver,
                              base::ThreadTaskRunnerHandle::Get());
  
    // Make sure we could send a MessagePort (ScopedMessagePipeHandle) to the
    // page.
    {
      base::RunLoop run_loop;
      auto quit_closure = run_loop.QuitClosure();
      auto received_message_callback = base::BindOnce(
          [](base::OnceClosure loop_quit_closure, std::string port_msg,
             absl::optional<blink::WebMessagePort> incoming_port) {
            EXPECT_EQ("got_port", port_msg);
            std::move(loop_quit_closure).Run();
          },
          std::move(quit_closure));
      message_receiver.WaitForNextIncomingMessage(
          std::move(received_message_callback));
      std::vector<blink::WebMessagePort> message_ports;
      message_ports.push_back(std::move(page_port));
      cast_web_contents_->PostMessageToMainFrame(
          gurl.DeprecatedGetOriginAsURL().spec(), kHelloMsg,
          std::move(message_ports));
      run_loop.Run();
    }
    // Test whether we could receive the right response from the page after we
    // send messages through |platform_port|.
    {
      base::RunLoop run_loop;
      auto quit_closure = run_loop.QuitClosure();
      auto received_message_callback = base::BindOnce(
          [](base::OnceClosure loop_quit_closure, std::string port_msg,
             absl::optional<blink::WebMessagePort> incoming_port) {
            EXPECT_EQ("ack ping", port_msg);
            std::move(loop_quit_closure).Run();
          },
          std::move(quit_closure));
      message_receiver.WaitForNextIncomingMessage(
          std::move(received_message_callback));
      platform_port.PostMessage(blink::WebMessagePort::Message(kPingMsg));
      run_loop.Run();
    }
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest,
                         PostMessageMessagePortDisconnected) {
    // ===========================================================================
    // Test: Send a MessagePort to the page, then perform bidirectional messaging
    // through the port. Make sure mojo counterpart pipe handle could receive the
    // MessagePort disconnection event.
    // ===========================================================================
    constexpr char kOriginalTitle[] = "messageport";
    constexpr char kHelloMsg[] = "hi";
  
    EXPECT_CALL(mock_cast_wc_observer_, UpdateTitle(kOriginalTitle));
    // Load test page.
    embedded_test_server()->ServeFilesFromSourceDirectory(GetTestDataPath());
    StartTestServer();
    GURL gurl = embedded_test_server()->GetURL("/message_port.html");
    cast_web_contents_->LoadUrl(gurl);
    title_change_observer_.RunUntilTitleEquals(kOriginalTitle);
  
    auto message_pipe = blink::WebMessagePort::CreatePair();
    auto platform_port = std::move(message_pipe.first);
    auto page_port = std::move(message_pipe.second);
  
    // Bind platform side port
    TestMessageReceiver message_receiver;
    platform_port.SetReceiver(&message_receiver,
                              base::ThreadTaskRunnerHandle::Get());
  
    // Make sure we could post a MessagePort (ScopedMessagePipeHandle) to
    // the page.
    {
      base::RunLoop run_loop;
      auto quit_closure = run_loop.QuitClosure();
      auto received_message_callback = base::BindOnce(
          [](base::OnceClosure loop_quit_closure, std::string port_msg,
             absl::optional<blink::WebMessagePort> incoming_port) {
            EXPECT_EQ("got_port", port_msg);
            std::move(loop_quit_closure).Run();
          },
          std::move(quit_closure));
      message_receiver.WaitForNextIncomingMessage(
          std::move(received_message_callback));
      std::vector<blink::WebMessagePort> message_ports;
      message_ports.push_back(std::move(page_port));
      cast_web_contents_->PostMessageToMainFrame(
          gurl.DeprecatedGetOriginAsURL().spec(), kHelloMsg,
          std::move(message_ports));
      run_loop.Run();
    }
    // Navigating off-page should tear down the MessageChannel, native side
    // should be able to receive disconnected event.
    {
      base::RunLoop run_loop;
      message_receiver.SetOnPipeErrorCallback(base::BindOnce(
          [](base::OnceClosure quit_closure) { std::move(quit_closure).Run(); },
          run_loop.QuitClosure()));
      cast_web_contents_->LoadUrl(GURL(url::kAboutBlankURL));
      run_loop.Run();
    }
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, ExecuteJavaScript) {
    // Start test server for hosting test HTML pages.
    embedded_test_server()->ServeFilesFromSourceDirectory(GetTestDataPath());
    StartTestServer();
  
    // ===========================================================================
    // Test: Set a value using ExecuteJavaScript with empty callback, and then use
    // ExecuteJavaScript with callback to retrieve that value.
    // ===========================================================================
    constexpr char kSoyMilkJsonStringLiteral[] = "\"SoyMilk\"";
    constexpr char16_t kSoyMilkJsonStringLiteral16[] = u"\"SoyMilk\"";
  
    // Load page with title "hello":
    GURL gurl{embedded_test_server()->GetURL("/title1.html")};
    run_loop_ = std::make_unique<base::RunLoop>();
    {
      InSequence seq;
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADING));
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADED))
          .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    }
    cast_web_contents_->LoadUrl(gurl);
    run_loop_->Run();
  
    // Execute with empty callback.
    cast_web_contents_->ExecuteJavaScript(
        base::StrCat({u"const the_var = ", kSoyMilkJsonStringLiteral16, u";"}),
        base::DoNothing());
  
    // Execute a script snippet to return the variable's value.
    base::RunLoop run_loop2;
    cast_web_contents_->ExecuteJavaScript(
        u"the_var;", base::BindLambdaForTesting([&](base::Value result_value) {
          std::string result_json;
          ASSERT_TRUE(base::JSONWriter::Write(result_value, &result_json));
          EXPECT_EQ(result_json, kSoyMilkJsonStringLiteral);
          run_loop2.Quit();
        }));
    run_loop2.Run();
  }
  
  // Mock class used by the following test case.
  class MockApiBindings : public mojom::ApiBindings {
   public:
    MockApiBindings() = default;
    ~MockApiBindings() override = default;
  
    mojo::PendingRemote<mojom::ApiBindings> CreateRemote() {
      DCHECK(!receiver_.is_bound());
  
      mojo::PendingRemote<mojom::ApiBindings> pending_remote =
          receiver_.BindNewPipeAndPassRemote();
  
      return pending_remote;
    }
  
    // mojom::ApiBindings implementation:
    MOCK_METHOD(void, GetAll, (GetAllCallback), (override));
    MOCK_METHOD(void,
                Connect,
                (const std::string&, blink::MessagePortDescriptor),
                (override));
  
   private:
    mojo::Receiver<mojom::ApiBindings> receiver_{this};
  };
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest,
                         InjectBindingsFromApiBindingsRemote) {
    // Start test server for hosting test HTML pages.
    embedded_test_server()->ServeFilesFromSourceDirectory(GetTestDataPath());
    StartTestServer();
  
    // ===========================================================================
    // Test: Inject a set of scripts to eval an result. Retrieve that value and
    // match against the right answer.
    // ===========================================================================
    MockApiBindings mock_api_bindings;
    EXPECT_CALL(mock_api_bindings, GetAll(_))
        .Times(1)
        .WillOnce(
            WithArgs<0>(Invoke([](MockApiBindings::GetAllCallback callback) {
              std::vector<chromecast::mojom::ApiBindingPtr> bindings_vector;
              bindings_vector.emplace_back(
                  chromecast::mojom::ApiBinding::New("let res = 0;"));
              bindings_vector.emplace_back(
                  chromecast::mojom::ApiBinding::New("res += 1;"));
              bindings_vector.emplace_back(
                  chromecast::mojom::ApiBinding::New("res += 2;"));
              bindings_vector.emplace_back(
                  chromecast::mojom::ApiBinding::New("res += 3;"));
              std::move(callback).Run(std::move(bindings_vector));
            })));
  
    // Binds mocked |mojom::ApiBindings|.
    cast_web_contents_->ConnectToBindingsService(
        mock_api_bindings.CreateRemote());
  
    run_loop_ = std::make_unique<base::RunLoop>();
    {
      InSequence seq;
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADING));
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADED))
          .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    }
  
    // Loads a blank page.
    cast_web_contents_->LoadUrl(GURL(url::kAboutBlankURL));
    run_loop_->Run();
  
    // Evaluates the value of |res|.
    EXPECT_EQ(6, content::EvalJs(cast_web_contents_->web_contents(), "res;"));
  }
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest,
                         StopPageInCaseOfEmptyBindingsReceived) {
    // Start test server for hosting test HTML pages.
    embedded_test_server()->ServeFilesFromSourceDirectory(GetTestDataPath());
    StartTestServer();
  
    // ===========================================================================
    // Test: Sending empty set of bindings should result in error page state.
    // ===========================================================================
    MockApiBindings mock_api_bindings;
    EXPECT_CALL(mock_api_bindings, GetAll(_))
        .Times(1)
        .WillOnce(
            WithArgs<0>(Invoke([](MockApiBindings::GetAllCallback callback) {
              std::vector<chromecast::mojom::ApiBindingPtr> bindings_vector;
              std::move(callback).Run(std::move(bindings_vector));
            })));
  
    // Binds mocked |mojom::ApiBindings|.
    cast_web_contents_->ConnectToBindingsService(
        mock_api_bindings.CreateRemote());
  
    run_loop_ = std::make_unique<base::RunLoop>();
    {
      InSequence seq;
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADING));
      EXPECT_CALL(mock_cast_wc_observer_, PageStateChanged(PageState::LOADED))
          .WillOnce(InvokeWithoutArgs([&]() { QuitRunLoop(); }));
    }
  
    EXPECT_CALL(mock_cast_wc_observer_,
                PageStopped(PageState::ERROR, net::ERR_UNEXPECTED));
  
    // Loads a blank page.
    cast_web_contents_->LoadUrl(GURL(url::kAboutBlankURL));
    run_loop_->Run();
  }
  
  // Helper for the test below. This exposes two interfaces, TestAdder and
  // TestDoubler.
  class TestInterfaceProvider : public mojom::TestAdder,
                                public mojom::TestDoubler {
   public:
    TestInterfaceProvider() = default;
    ~TestInterfaceProvider() override = default;
  
    size_t num_adders() const { return adders_.size(); }
    size_t num_doublers() const { return doublers_.size(); }
  
    base::RepeatingCallback<void(mojo::PendingReceiver<mojom::TestAdder>)>
    MakeAdderBinder() {
      return base::BindLambdaForTesting(
          [this](mojo::PendingReceiver<mojom::TestAdder> receiver) {
            adders_.Add(this, std::move(receiver));
            OnRequestHandled();
          });
    }
  
    base::RepeatingCallback<void(mojo::PendingReceiver<mojom::TestDoubler>)>
    MakeDoublerBinder() {
      return base::BindLambdaForTesting(
          [this](mojo::PendingReceiver<mojom::TestDoubler> receiver) {
            doublers_.Add(this, std::move(receiver));
            OnRequestHandled();
          });
    }
  
    // Waits for some number of new interface binding requests to be dispatched
    // and then invokes `callback`.
    void WaitForRequests(size_t n, base::OnceClosure callback) {
      wait_callback_ = std::move(callback);
      num_requests_to_wait_for_ = n;
    }
  
    // mojom::TestAdder:
    void Add(int32_t a, int32_t b, AddCallback callback) override {
      std::move(callback).Run(a + b);
    }
  
    // mojom::TestDouble:
    void Double(int32_t x, DoubleCallback callback) override {
      std::move(callback).Run(x * 2);
    }
  
   private:
    void OnRequestHandled() {
      if (num_requests_to_wait_for_ == 0)
        return;
      DCHECK(wait_callback_);
      if (--num_requests_to_wait_for_ == 0)
        std::move(wait_callback_).Run();
    }
  
    mojo::ReceiverSet<mojom::TestAdder> adders_;
    mojo::ReceiverSet<mojom::TestDoubler> doublers_;
    size_t num_requests_to_wait_for_ = 0;
    base::OnceClosure wait_callback_;
  };
  
  IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, InterfaceBinding) {
    // This test verifies that interfaces registered with the CastWebContents --
    // either via its binder_registry() or its RegisterInterfaceProvider() API --
    // are reachable from render frames using either the deprecated
    // InterfaceProvider API (which results in an OnInterfaceRequestFromFrame call
    // on the WebContents) or the newer BrowserInterfaceBroker API which is used
    // in most other places (including from Mojo JS).
    TestInterfaceProvider provider;
    InterfaceBundle bundle_;
    bundle_.AddBinder(provider.MakeAdderBinder());
    bundle_.AddBinder(provider.MakeDoublerBinder());
    cast_web_contents_->SetInterfacesForRenderer(bundle_.CreateRemote());
  
    // First verify that both interfaces are reachable using the deprecated
    // WebContents path, which is triggered only by renderer-side use of
    // RenderFrame::GetRemoteInterfaces(). Since poking renderer state in browser
    // tests is challenging, we simply simulate the resulting WebContentsObbserver
    // calls here instead and verify end-to-end connection for each interface.
    mojo::Remote<mojom::TestAdder> adder;
    mojo::GenericPendingReceiver adder_receiver(
        adder.BindNewPipeAndPassReceiver());
    EXPECT_TRUE(cast_web_contents_->TryBindReceiver(adder_receiver));
  
    mojo::Remote<mojom::TestDoubler> doubler;
    mojo::GenericPendingReceiver doubler_receiver(
        doubler.BindNewPipeAndPassReceiver());
    EXPECT_TRUE(cast_web_contents_->TryBindReceiver(doubler_receiver));
  
    base::RunLoop add_loop;
    adder->Add(37, 5, base::BindLambdaForTesting([&](int32_t result) {
                 EXPECT_EQ(42, result);
                 add_loop.Quit();
               }));
    add_loop.Run();
  
    base::RunLoop double_loop;
    doubler->Double(21, base::BindLambdaForTesting([&](int32_t result) {
                      EXPECT_EQ(42, result);
                      double_loop.Quit();
                    }));
    double_loop.Run();
  
    EXPECT_EQ(1u, provider.num_adders());
    EXPECT_EQ(1u, provider.num_doublers());
  
    // Now verify that the same interfaces are also reachable at the same binders
    // when going through the newer BrowserInterfaceBroker path. For simplicity
    // the test JS here does not have access to bindings and so does not make
    // calls on the interfaces. It is however totally sufficient for us to verify
    // that the page's requests result in new receivers being bound inside
    // TestInterfaceProvider.
    base::RunLoop loop;
    provider.WaitForRequests(2, loop.QuitClosure());
    embedded_test_server()->ServeFilesFromSourceDirectory(GetTestDataPath());
    StartTestServer();
    const GURL kUrl{embedded_test_server()->GetURL("/interface_binding.html")};
    cast_web_contents_->LoadUrl(kUrl);
    loop.Run();
  
    EXPECT_EQ(2u, provider.num_adders());
    EXPECT_EQ(2u, provider.num_doublers());
  }
  
  }  // namespace chromecast
  
  #endif  // CHROMECAST_BROWSER_CAST_WEB_CONTENTS_BROWSERTEST_H_
  