blob: 3a3960bd5aabfb648d298ca7eb4d35983be14bf1 [file] [log] [blame]
// 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 "base/command_line.h"
#include "base/containers/flat_set.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/string16.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind_test_util.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 "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/connector.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 "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/messaging/string_message_codec.h"
#include "third_party/blink/public/common/messaging/transferable_message.h"
#include "third_party/blink/public/common/messaging/transferable_message_mojom_traits.h"
#include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h"
#include "url/gurl.h"
using ::testing::_;
using ::testing::AllOf;
using ::testing::AtLeast;
using ::testing::Eq;
using ::testing::Expectation;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::Property;
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;
}
// TODO(lijiawei): Move mojo_message_port_util upstream to remove duplicate
// helper functions. (b/138150191)
mojo::Message MojoMessageFromUtf8(base::StringPiece message_utf8) {
blink::TransferableMessage transfer_message;
transfer_message.owned_encoded_message =
blink::EncodeStringMessage(base::UTF8ToUTF16(message_utf8));
transfer_message.encoded_message = transfer_message.owned_encoded_message;
return blink::mojom::TransferableMessage::SerializeAsMessage(
&transfer_message);
}
// =============================================================================
// Mocks
// =============================================================================
class MockCastWebContentsDelegate : public CastWebContents::Delegate {
public:
MockCastWebContentsDelegate() {}
~MockCastWebContentsDelegate() override = default;
MOCK_METHOD2(InnerContentsCreated,
void(CastWebContents* inner_contents,
CastWebContents* outer_contents));
private:
DISALLOW_COPY_AND_ASSIGN(MockCastWebContentsDelegate);
};
class MockCastWebContentsObserver : public CastWebContents::Observer {
public:
MockCastWebContentsObserver() {}
~MockCastWebContentsObserver() override = default;
MOCK_METHOD1(OnPageStateChanged, void(CastWebContents* cast_web_contents));
MOCK_METHOD2(OnPageStopped,
void(CastWebContents* cast_web_contents, int error_code));
MOCK_METHOD4(
RenderFrameCreated,
void(int render_process_id,
int render_frame_id,
service_manager::InterfaceProvider* frame_interfaces,
blink::AssociatedInterfaceProvider* frame_associated_interfaces));
MOCK_METHOD1(ResourceLoadFailed, void(CastWebContents* cast_web_contents));
MOCK_METHOD1(UpdateTitle, void(const base::string16& title));
private:
DISALLOW_COPY_AND_ASSIGN(MockCastWebContentsObserver);
};
class MockWebContentsDelegate : public content::WebContentsDelegate {
public:
MockWebContentsDelegate() = default;
~MockWebContentsDelegate() override = default;
MOCK_METHOD1(CloseContents, void(content::WebContents* source));
};
class TitleChangeObserver : public CastWebContents::Observer {
public:
TitleChangeObserver() = default;
~TitleChangeObserver() override = default;
// Spins a Runloop until the title of the page matches the |expected_title|
// that have been set.
void RunUntilTitleEquals(base::StringPiece expected_title) {
expected_title_ = expected_title.as_string();
// Spin the runloop until the expected conditions are met.
if (current_title_ != expected_title_) {
expected_title_ = expected_title.as_string();
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
}
// CastWebContents::Observer implementation:
void UpdateTitle(const base::string16& title) override {
// Resumes execution of RunUntilTitleEquals() if |title| matches
// expectations.
std::string title_utf8 = base::UTF16ToUTF8(title);
current_title_ = title_utf8;
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_;
DISALLOW_COPY_AND_ASSIGN(TitleChangeObserver);
};
class TestMessageReceiver : public mojo::MessageReceiver {
public:
TestMessageReceiver() = default;
~TestMessageReceiver() override = default;
void WaitForNextIncomingMessage(
base::OnceCallback<void(std::string,
base::Optional<mojo::ScopedMessagePipeHandle>)>
callback) {
DCHECK(message_received_callback_.is_null())
<< "Only one waiting event is allowed.";
message_received_callback_ = std::move(callback);
}
private:
bool Accept(mojo::Message* message) override {
blink::TransferableMessage transferable_message;
if (!blink::mojom::TransferableMessage::DeserializeFromMessage(
std::move(*message), &transferable_message)) {
return false;
}
base::string16 data_utf16;
if (!blink::DecodeStringMessage(transferable_message.encoded_message,
&data_utf16)) {
return false;
}
std::string message_text;
if (!base::UTF16ToUTF8(data_utf16.data(), data_utf16.size(),
&message_text)) {
return false;
}
base::Optional<mojo::ScopedMessagePipeHandle> incoming_port = base::nullopt;
// Only one MessagePort should be sent to here.
if (!transferable_message.ports.empty()) {
DCHECK(transferable_message.ports.size() == 1)
<< "Only one control port can be provided";
blink::MessagePortChannel message_port_channel =
std::move(transferable_message.ports[0]);
incoming_port = base::make_optional<mojo::ScopedMessagePipeHandle>(
message_port_channel.ReleaseHandle());
}
if (message_received_callback_) {
std::move(message_received_callback_)
.Run(message_text, std::move(incoming_port));
}
return true;
}
base::OnceCallback<void(
std::string,
base::Optional<mojo::ScopedMessagePipeHandle> incoming_port)>
message_received_callback_;
DISALLOW_COPY_AND_ASSIGN(TestMessageReceiver);
};
} // namespace
// =============================================================================
// Test class
// =============================================================================
class CastWebContentsBrowserTest : public content::BrowserTestBase,
public content::WebContentsObserver {
protected:
CastWebContentsBrowserTest() = default;
~CastWebContentsBrowserTest() override = default;
void SetUp() final {
SetUpCommandLine(base::CommandLine::ForCurrentProcess());
BrowserTestBase::SetUp();
}
void SetUpCommandLine(base::CommandLine* command_line) final {
command_line->AppendSwitch(switches::kNoWifi);
command_line->AppendSwitchASCII(switches::kTestType, "browser");
}
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_);
CastWebContents::InitParams init_params = {
&mock_cast_wc_delegate_, false /* enabled_for_dev */,
false /* use_cma_renderer */, true /* is_root_window */};
cast_web_contents_ =
std::make_unique<CastWebContentsImpl>(web_contents_.get(), init_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());
}
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();
}
MockWebContentsDelegate mock_wc_delegate_;
MockCastWebContentsDelegate mock_cast_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_;
base::flat_set<content::RenderFrameHost*> render_frames_;
private:
DISALLOW_COPY_AND_ASSIGN(CastWebContentsBrowserTest);
};
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) {
auto run_loop = std::make_unique<base::RunLoop>();
auto quit_closure = [&run_loop]() {
if (run_loop->running()) {
run_loop->QuitWhenIdle();
}
};
// ===========================================================================
// Test: Load a blank page successfully, verify LOADED state.
// ===========================================================================
{
InSequence seq;
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(
cast_web_contents_.get(), CastWebContents::PageState::LOADING)));
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(cast_web_contents_.get(),
CastWebContents::PageState::LOADED)))
.WillOnce(InvokeWithoutArgs(quit_closure));
}
cast_web_contents_->LoadUrl(GURL(url::kAboutBlankURL));
run_loop->Run();
// ===========================================================================
// Test: Load a blank page via WebContents API, verify LOADED state.
// ===========================================================================
{
InSequence seq;
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(
cast_web_contents_.get(), CastWebContents::PageState::LOADING)));
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(cast_web_contents_.get(),
CastWebContents::PageState::LOADED)))
.WillOnce(InvokeWithoutArgs(quit_closure));
}
run_loop = std::make_unique<base::RunLoop>();
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_, OnPageStateChanged(_)).Times(0);
EXPECT_CALL(mock_cast_wc_observer_, OnPageStopped(_, _)).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_, OnPageStateChanged(_)).Times(0);
EXPECT_CALL(mock_cast_wc_observer_, OnPageStopped(_, _)).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.
// ===========================================================================
EXPECT_CALL(mock_wc_delegate_, CloseContents(web_contents_.get()))
.Times(AtLeast(1));
EXPECT_CALL(mock_cast_wc_observer_,
OnPageStopped(CheckPageState(cast_web_contents_.get(),
CastWebContents::PageState::CLOSED),
net::OK))
.WillOnce(InvokeWithoutArgs(quit_closure));
run_loop = std::make_unique<base::RunLoop>();
cast_web_contents_->ClosePage();
run_loop->Run();
// ===========================================================================
// Test: Destroy the underlying WebContents. Verify DESTROYED state.
// ===========================================================================
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(
cast_web_contents_.get(), CastWebContents::PageState::DESTROYED)));
web_contents_.reset();
cast_web_contents_.reset();
}
IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, WebContentsDestroyed) {
auto run_loop = std::make_unique<base::RunLoop>();
auto quit_closure = [&run_loop]() {
if (run_loop->running()) {
run_loop->QuitWhenIdle();
}
};
{
InSequence seq;
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(
cast_web_contents_.get(), CastWebContents::PageState::LOADING)));
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(cast_web_contents_.get(),
CastWebContents::PageState::LOADED)))
.WillOnce(InvokeWithoutArgs(quit_closure));
}
cast_web_contents_->LoadUrl(GURL(url::kAboutBlankURL));
run_loop->Run();
// ===========================================================================
// Test: Destroy the WebContents. Verify OnPageStopped(DESTROYED, net::OK).
// ===========================================================================
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStopped(CheckPageState(cast_web_contents_.get(),
CastWebContents::PageState::DESTROYED),
net::OK));
web_contents_.reset();
}
IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, ErrorPageCrash) {
auto run_loop = std::make_unique<base::RunLoop>();
auto quit_closure = [&run_loop]() {
if (run_loop->running()) {
run_loop->QuitWhenIdle();
}
};
// ===========================================================================
// Test: If the page's main render process crashes, enter ERROR state.
// ===========================================================================
{
InSequence seq;
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(
cast_web_contents_.get(), CastWebContents::PageState::LOADING)));
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(cast_web_contents_.get(),
CastWebContents::PageState::LOADED)))
.WillOnce(InvokeWithoutArgs(quit_closure));
}
cast_web_contents_->LoadUrl(GURL(url::kAboutBlankURL));
run_loop->Run();
EXPECT_CALL(mock_cast_wc_observer_,
OnPageStopped(CheckPageState(cast_web_contents_.get(),
CastWebContents::PageState::ERROR),
net::ERR_UNEXPECTED));
CrashTab(web_contents_.get());
}
IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, ErrorLocalFileMissing) {
auto run_loop = std::make_unique<base::RunLoop>();
auto quit_closure = [&run_loop]() {
if (run_loop->running()) {
run_loop->QuitWhenIdle();
}
};
// ===========================================================================
// Test: Loading a page with an HTTP error should enter ERROR state.
// ===========================================================================
{
InSequence seq;
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(
cast_web_contents_.get(), CastWebContents::PageState::LOADING)));
EXPECT_CALL(mock_cast_wc_observer_,
OnPageStopped(CheckPageState(cast_web_contents_.get(),
CastWebContents::PageState::ERROR),
_))
.WillOnce(InvokeWithoutArgs(quit_closure));
}
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) {
auto run_loop = std::make_unique<base::RunLoop>();
auto quit_closure = [&run_loop]() {
if (run_loop->running()) {
run_loop->QuitWhenIdle();
}
};
// ===========================================================================
// Test: Ignore load errors in sub-frames.
// ===========================================================================
{
InSequence seq;
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(
cast_web_contents_.get(), CastWebContents::PageState::LOADING)));
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(cast_web_contents_.get(),
CastWebContents::PageState::LOADED)))
.WillOnce(InvokeWithoutArgs(quit_closure));
}
cast_web_contents_->LoadUrl(GURL(url::kAboutBlankURL));
run_loop->Run();
// Create a sub-frame.
EXPECT_CALL(mock_cast_wc_observer_, OnPageStateChanged(_)).Times(0);
EXPECT_CALL(mock_cast_wc_observer_, OnPageStopped(_, _)).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, base::string16());
// ===========================================================================
// Test: Ignore main frame load failures with net::ERR_ABORTED.
// ===========================================================================
EXPECT_CALL(mock_cast_wc_observer_, OnPageStateChanged(_)).Times(0);
EXPECT_CALL(mock_cast_wc_observer_, OnPageStopped(_, _)).Times(0);
cast_web_contents_->DidFailLoad(
web_contents_->GetMainFrame(),
web_contents_->GetMainFrame()->GetLastCommittedURL(), net::ERR_ABORTED,
base::string16());
// ===========================================================================
// Test: If main frame fails to load, page should enter ERROR state.
// ===========================================================================
EXPECT_CALL(mock_cast_wc_observer_,
OnPageStopped(CheckPageState(cast_web_contents_.get(),
CastWebContents::PageState::ERROR),
net::ERR_FAILED));
cast_web_contents_->DidFailLoad(
web_contents_->GetMainFrame(),
web_contents_->GetMainFrame()->GetLastCommittedURL(), net::ERR_FAILED,
base::string16());
}
IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, ErrorHttp4XX) {
auto run_loop = std::make_unique<base::RunLoop>();
auto quit_closure = [&run_loop]() {
if (run_loop->running()) {
run_loop->QuitWhenIdle();
}
};
// ===========================================================================
// 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();
{
InSequence seq;
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(
cast_web_contents_.get(), CastWebContents::PageState::LOADING)));
EXPECT_CALL(mock_cast_wc_observer_,
OnPageStopped(CheckPageState(cast_web_contents_.get(),
CastWebContents::PageState::ERROR),
net::ERR_HTTP_RESPONSE_CODE_FAILURE))
.WillOnce(InvokeWithoutArgs(quit_closure));
}
cast_web_contents_->LoadUrl(embedded_test_server()->GetURL("/dummy.html"));
run_loop->Run();
}
IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, ErrorLoadFailed) {
auto run_loop = std::make_unique<base::RunLoop>();
auto quit_closure = [&run_loop]() {
if (run_loop->running()) {
run_loop->QuitWhenIdle();
}
};
// ===========================================================================
// 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));
{
InSequence seq;
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(
cast_web_contents_.get(), CastWebContents::PageState::LOADING)));
EXPECT_CALL(mock_cast_wc_observer_,
OnPageStopped(CheckPageState(cast_web_contents_.get(),
CastWebContents::PageState::ERROR),
net::ERR_ADDRESS_UNREACHABLE))
.WillOnce(InvokeWithoutArgs(quit_closure));
}
cast_web_contents_->LoadUrl(gurl);
run_loop->Run();
}
IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, LoadCanceledByApp) {
auto run_loop = std::make_unique<base::RunLoop>();
auto quit_closure = [&run_loop]() {
if (run_loop->running()) {
run_loop->QuitWhenIdle();
}
};
// ===========================================================================
// 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();
{
InSequence seq;
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(
cast_web_contents_.get(), CastWebContents::PageState::LOADING)));
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(cast_web_contents_.get(),
CastWebContents::PageState::LOADED)))
.WillOnce(InvokeWithoutArgs(quit_closure));
}
cast_web_contents_->LoadUrl(
embedded_test_server()->GetURL("/load_cancel.html"));
run_loop->Run();
}
IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, NotifyMissingResource) {
auto run_loop = std::make_unique<base::RunLoop>();
auto quit_closure = [&run_loop]() {
if (run_loop->running()) {
run_loop->QuitWhenIdle();
}
};
// ===========================================================================
// Test: Loading a page with a missing resource should notify observers.
// ===========================================================================
{
InSequence seq;
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(
cast_web_contents_.get(), CastWebContents::PageState::LOADING)));
EXPECT_CALL(
mock_cast_wc_observer_,
OnPageStateChanged(CheckPageState(cast_web_contents_.get(),
CastWebContents::PageState::LOADED)))
.WillOnce(InvokeWithoutArgs(quit_closure));
}
EXPECT_CALL(mock_cast_wc_observer_,
ResourceLoadFailed(cast_web_contents_.get()));
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.
// ===========================================================================
constexpr char kExpectedTitle[] = "hello";
constexpr char 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(base::ASCIIToUTF16(kExpectedTitle)));
EXPECT_CALL(mock_cast_wc_observer_,
UpdateTitle(base::ASCIIToUTF16(kOriginalTitle)));
constexpr char kBindingsId[] = "1234";
GURL gurl = content::GetFileUrlWithQuery(
GetTestDataFilePath("dynamic_title.html"), "");
cast_web_contents_->AddBeforeLoadJavaScript(
kBindingsId, {gurl.GetOrigin().spec()}, "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").
// ===========================================================================
constexpr char kReplaceTitle[] = "clobber";
constexpr char 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(base::ASCIIToUTF16(kReplaceTitle)));
EXPECT_CALL(mock_cast_wc_observer_,
UpdateTitle(base::ASCIIToUTF16(kOriginalTitle)));
constexpr char kBindingsId[] = "1234";
GURL gurl = content::GetFileUrlWithQuery(
GetTestDataFilePath("dynamic_title.html"), "");
cast_web_contents_->AddBeforeLoadJavaScript(
kBindingsId, {gurl.GetOrigin().spec()}, "stashed_title = 'hello';");
cast_web_contents_->AddBeforeLoadJavaScript(
kBindingsId, {gurl.GetOrigin().spec()},
"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.
// ===========================================================================
constexpr char kExpectedTitle[] = "hello there";
constexpr char kOriginalTitle[] =
"Welcome to Stan the Offline Dino's Homepage";
constexpr char kBindingsId1[] = "1234";
constexpr char 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(base::ASCIIToUTF16(kExpectedTitle)));
EXPECT_CALL(mock_cast_wc_observer_,
UpdateTitle(base::ASCIIToUTF16(kOriginalTitle)));
GURL gurl = content::GetFileUrlWithQuery(
GetTestDataFilePath("dynamic_title.html"), "");
cast_web_contents_->AddBeforeLoadJavaScript(
kBindingsId1, {gurl.GetOrigin().spec()}, "stashed_title = 'hello';");
cast_web_contents_->AddBeforeLoadJavaScript(
kBindingsId2, {gurl.GetOrigin().spec()}, "stashed_title += ' there';");
cast_web_contents_->LoadUrl(gurl);
title_change_observer_.RunUntilTitleEquals(kExpectedTitle);
}
IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest,
ExecuteJavaScriptOnLoadRemoved) {
// ===========================================================================
// Test: Verifies that bindings could be removed successfully before page
// starts loading.
// ===========================================================================
constexpr char kExpectedTitle[] = "foo";
constexpr char kOriginalTitle[] =
"Welcome to Stan the Offline Dino's Homepage";
constexpr char kBindingsId1[] = "1234";
constexpr char 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(base::ASCIIToUTF16(kExpectedTitle)));
EXPECT_CALL(mock_cast_wc_observer_,
UpdateTitle(base::ASCIIToUTF16(kOriginalTitle)));
GURL gurl = content::GetFileUrlWithQuery(
GetTestDataFilePath("dynamic_title.html"), "");
cast_web_contents_->AddBeforeLoadJavaScript(
kBindingsId1, {gurl.GetOrigin().spec()}, "stashed_title = 'foo';");
// Add a script which clobbers "foo".
cast_web_contents_->AddBeforeLoadJavaScript(
kBindingsId2, {gurl.GetOrigin().spec()}, "stashed_title = 'bar';");
// Deletes the clobbering script.
cast_web_contents_->RemoveBeforeLoadJavaScript(kBindingsId2);
cast_web_contents_->LoadUrl(gurl);
title_change_observer_.RunUntilTitleEquals(kExpectedTitle);
}
IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest,
ExecuteJavaScriptOnLoadWrongOrigin) {
// ===========================================================================
// Test: Injecting script should not happen if the to-be-loaded page's origin
// is not whitelisted for the injection script.
// ===========================================================================
constexpr char kOriginalTitle[] =
"Welcome to Stan the Offline Dino's Homepage";
constexpr char kBindingsId[] = "1234";
EXPECT_CALL(mock_cast_wc_observer_,
UpdateTitle(base::ASCIIToUTF16(kOriginalTitle)));
GURL gurl = content::GetFileUrlWithQuery(
GetTestDataFilePath("dynamic_title.html"), "");
cast_web_contents_->AddBeforeLoadJavaScript(
kBindingsId, {"http://example.com"}, "stashed_title = 'hello';");
cast_web_contents_->LoadUrl(gurl);
title_change_observer_.RunUntilTitleEquals(kOriginalTitle);
}
IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest,
ExecuteJavaScriptOnLoadWildcardOrigin) {
// Start test server for hosting test HTML pages.
embedded_test_server()->ServeFilesFromSourceDirectory(GetTestDataPath());
StartTestServer();
// ===========================================================================
// Test: Injecting script should be able to load on arbitrary origins with
// wildcard origin restriction set.
// ===========================================================================
constexpr char kInjectedTitle1[] = "hello";
constexpr char kInjectedTitle2[] = "world";
constexpr char kOriginalTitle[] =
"Welcome to Stan the Offline Dino's Homepage";
constexpr char kBindingsId1[] = "1234";
constexpr char kBindingsId2[] = "5678";
// The script should be able to run before HTML <script> tag starts running.
EXPECT_CALL(mock_cast_wc_observer_,
UpdateTitle(base::ASCIIToUTF16(kOriginalTitle)))
.Times(2);
EXPECT_CALL(mock_cast_wc_observer_,
UpdateTitle(base::ASCIIToUTF16(kInjectedTitle1)));
EXPECT_CALL(mock_cast_wc_observer_,
UpdateTitle(base::ASCIIToUTF16(kInjectedTitle2)));
GURL gurl{embedded_test_server()->GetURL("/dynamic_title.html")};
cast_web_contents_->AddBeforeLoadJavaScript(kBindingsId1, {"*"},
"stashed_title = 'hello';");
// Test script injection for the origin 127.0.0.1.
// Load title "hello":
cast_web_contents_->LoadUrl(gurl);
title_change_observer_.RunUntilTitleEquals(kInjectedTitle1);
// Load AboutBlank page:
cast_web_contents_->LoadUrl(GURL(url::kAboutBlankURL));
cast_web_contents_->AddBeforeLoadJavaScript(kBindingsId2, {"*"},
"stashed_title = 'world';");
// Test script injection using a different origin ("localhost"), which should
// still be picked up by the wildcard. And the title should be initialized as
// origianl title first, then as 'world'.
GURL alt_url =
embedded_test_server()->GetURL("localhost", "/dynamic_title.html");
cast_web_contents_->LoadUrl(alt_url);
title_change_observer_.RunUntilTitleEquals(kInjectedTitle2);
}
IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest,
ExecuteJavaScriptOnLoadEarlyAndLateRegistrations) {
// ===========================================================================
// Test: Tests that we can inject scripts before and after RenderFrame
// creation.
// ===========================================================================
constexpr char kExpectedTitle1[] = "foo";
constexpr char kExpectedTitle2[] = "foo bar";
constexpr char kOriginalTitle[] =
"Welcome to Stan the Offline Dino's Homepage";
constexpr char kBindingsId1[] = "1234";
constexpr char 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(base::ASCIIToUTF16(kExpectedTitle2)));
EXPECT_CALL(mock_cast_wc_observer_,
UpdateTitle(base::ASCIIToUTF16(kExpectedTitle1)));
EXPECT_CALL(mock_cast_wc_observer_,
UpdateTitle(base::ASCIIToUTF16(kOriginalTitle)))
.Times(2);
GURL gurl = content::GetFileUrlWithQuery(
GetTestDataFilePath("dynamic_title.html"), "");
cast_web_contents_->AddBeforeLoadJavaScript(
kBindingsId1, {gurl.GetOrigin().spec()}, "stashed_title = 'foo';");
cast_web_contents_->LoadUrl(gurl);
title_change_observer_.RunUntilTitleEquals(kExpectedTitle1);
// Inject bindings after RenderFrameCreation
cast_web_contents_->AddBeforeLoadJavaScript(
kBindingsId2, {gurl.GetOrigin().spec()}, "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(base::ASCIIToUTF16(kPage1Title)));
EXPECT_CALL(mock_cast_wc_observer_,
UpdateTitle(base::ASCIIToUTF16(kOriginalTitle)));
GURL gurl = content::GetFileUrlWithQuery(
GetTestDataFilePath("window_post_message.html"), "");
cast_web_contents_->LoadUrl(gurl);
title_change_observer_.RunUntilTitleEquals(kOriginalTitle);
cast_web_contents_->PostMessageToMainFrame(
gurl.GetOrigin().spec(), std::string(kPage1Path),
std::vector<mojo::ScopedMessagePipeHandle>());
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 char kPingMsg[] = "ping";
EXPECT_CALL(mock_cast_wc_observer_,
UpdateTitle(base::ASCIIToUTF16(kOriginalTitle)));
// Load test page.
GURL gurl = content::GetFileUrlWithQuery(
GetTestDataFilePath("message_port.html"), "");
cast_web_contents_->LoadUrl(gurl);
title_change_observer_.RunUntilTitleEquals(kOriginalTitle);
mojo::MessagePipe message_pipe;
auto platform_port = std::move(message_pipe.handle0);
auto page_port = std::move(message_pipe.handle1);
TestMessageReceiver message_receiver;
auto connector = std::make_unique<mojo::Connector>(
std::move(platform_port), mojo::Connector::SINGLE_THREADED_SEND,
base::ThreadTaskRunnerHandle::Get());
connector->set_incoming_receiver(&message_receiver);
// 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,
base::Optional<mojo::ScopedMessagePipeHandle> 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<mojo::ScopedMessagePipeHandle> message_ports;
message_ports.push_back(std::move(page_port));
cast_web_contents_->PostMessageToMainFrame(
gurl.GetOrigin().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 mojo::Connector which has binded to |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,
base::Optional<mojo::ScopedMessagePipeHandle> 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));
mojo::Message mojo_message = MojoMessageFromUtf8(kPingMsg);
connector->Accept(&mojo_message);
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(base::ASCIIToUTF16(kOriginalTitle)));
// Load test page.
GURL gurl = content::GetFileUrlWithQuery(
GetTestDataFilePath("message_port.html"), "");
cast_web_contents_->LoadUrl(gurl);
title_change_observer_.RunUntilTitleEquals(kOriginalTitle);
mojo::MessagePipe message_pipe;
auto platform_port = std::move(message_pipe.handle0);
auto page_port = std::move(message_pipe.handle1);
// Bind platform side port
TestMessageReceiver message_receiver;
auto connector = std::make_unique<mojo::Connector>(
std::move(platform_port), mojo::Connector::SINGLE_THREADED_SEND,
base::ThreadTaskRunnerHandle::Get());
connector->set_incoming_receiver(&message_receiver);
// 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,
base::Optional<mojo::ScopedMessagePipeHandle> 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<mojo::ScopedMessagePipeHandle> message_ports;
message_ports.push_back(std::move(page_port));
cast_web_contents_->PostMessageToMainFrame(
gurl.GetOrigin().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;
connector->set_connection_error_handler(base::BindOnce(
[](base::OnceClosure quit_closure) { std::move(quit_closure).Run(); },
run_loop.QuitClosure()));
cast_web_contents_->LoadUrl(GURL(url::kAboutBlankURL));
run_loop.Run();
}
}
} // namespace chromecast
#endif // CHROMECAST_BROWSER_CAST_WEB_CONTENTS_BROWSERTEST_H_