blob: 109aae054801cf5f7a6aaa99a43b0168361bd1d5 [file] [log] [blame]
// Copyright 2018 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.
#include <lib/fidl/cpp/binding.h>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/macros.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/url_request/url_request_context.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/url_constants.h"
#include "webrunner/browser/frame_impl.h"
#include "webrunner/browser/run_with_timeout.h"
#include "webrunner/browser/test_common.h"
#include "webrunner/browser/webrunner_browser_test.h"
#include "webrunner/service/common.h"
namespace webrunner {
using testing::_;
using testing::AllOf;
using testing::Field;
using testing::InvokeWithoutArgs;
using testing::Mock;
// Use a shorter name for NavigationEvent, because it is
// referenced frequently in this file.
using NavigationDetails = chromium::web::NavigationEvent;
const char kPage1Path[] = "/title1.html";
const char kPage2Path[] = "/title2.html";
const char kDynamicTitlePath[] = "/dynamic_title.html";
const char kPage1Title[] = "title 1";
const char kPage2Title[] = "title 2";
const char kDataUrl[] =
"data:text/html;base64,PGI+SGVsbG8sIHdvcmxkLi4uPC9iPg==";
MATCHER(IsSet, "Checks if an optional field is set.") {
return !arg.is_null();
}
// Defines a suite of tests that exercise Frame-level functionality, such as
// navigation commands and page events.
class FrameImplTest : public WebRunnerBrowserTest {
public:
FrameImplTest() = default;
~FrameImplTest() = default;
MOCK_METHOD1(OnServeHttpRequest,
void(const net::test_server::HttpRequest& request));
protected:
// Creates a Frame with |navigation_observer_| attached.
chromium::web::FramePtr CreateFrame() {
return WebRunnerBrowserTest::CreateFrame(&navigation_observer_);
}
// Navigates a |controller| to |url|, blocking until navigation is complete.
void CheckLoadUrl(const std::string& url,
const std::string& expected_title,
chromium::web::NavigationController* controller) {
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_,
MockableOnNavigationStateChanged(testing::AllOf(
Field(&NavigationDetails::title, expected_title),
Field(&NavigationDetails::url, url))))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
controller->LoadUrl(url, nullptr);
CheckRunWithTimeout(&run_loop);
Mock::VerifyAndClearExpectations(this);
navigation_observer_.Acknowledge();
}
testing::StrictMock<MockNavigationObserver> navigation_observer_;
private:
DISALLOW_COPY_AND_ASSIGN(FrameImplTest);
};
class WebContentsDeletionObserver : public content::WebContentsObserver {
public:
explicit WebContentsDeletionObserver(content::WebContents* web_contents)
: content::WebContentsObserver(web_contents) {}
MOCK_METHOD1(RenderViewDeleted,
void(content::RenderViewHost* render_view_host));
};
// Verifies that the browser will navigate and generate a navigation observer
// event when LoadUrl() is called.
IN_PROC_BROWSER_TEST_F(FrameImplTest, NavigateFrame) {
chromium::web::FramePtr frame = CreateFrame();
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(url::kAboutBlankURL, url::kAboutBlankURL, controller.get());
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, NavigateDataFrame) {
chromium::web::FramePtr frame = CreateFrame();
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(kDataUrl, kDataUrl, controller.get());
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, FrameDeletedBeforeContext) {
chromium::web::FramePtr frame = CreateFrame();
// Process the frame creation message.
base::RunLoop().RunUntilIdle();
FrameImpl* frame_impl = context_impl()->GetFrameImplForTest(&frame);
WebContentsDeletionObserver deletion_observer(
frame_impl->web_contents_for_test());
base::RunLoop run_loop;
EXPECT_CALL(deletion_observer, RenderViewDeleted(_))
.WillOnce(InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
controller->LoadUrl(url::kAboutBlankURL, nullptr);
frame.Unbind();
run_loop.Run();
// Check that |context| remains bound after the frame is closed.
EXPECT_TRUE(context());
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, ContextDeletedBeforeFrame) {
chromium::web::FramePtr frame = CreateFrame();
EXPECT_TRUE(frame);
base::RunLoop run_loop;
frame.set_error_handler([&run_loop](zx_status_t status) { run_loop.Quit(); });
context().Unbind();
run_loop.Run();
EXPECT_FALSE(frame);
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, GoBackAndForward) {
chromium::web::FramePtr frame = CreateFrame();
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
ASSERT_TRUE(embedded_test_server()->Start());
GURL title1(embedded_test_server()->GetURL(kPage1Path));
GURL title2(embedded_test_server()->GetURL(kPage2Path));
CheckLoadUrl(title1.spec(), kPage1Title, controller.get());
CheckLoadUrl(title2.spec(), kPage2Title, controller.get());
{
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_,
MockableOnNavigationStateChanged(testing::AllOf(
Field(&NavigationDetails::title, kPage1Title),
Field(&NavigationDetails::url, IsSet()))))
.WillOnce(InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
controller->GoBack();
run_loop.Run();
navigation_observer_.Acknowledge();
}
// At the top of the navigation entry list; this should be a no-op.
controller->GoBack();
// Process the navigation request message.
base::RunLoop().RunUntilIdle();
{
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_,
MockableOnNavigationStateChanged(testing::AllOf(
Field(&NavigationDetails::title, kPage2Title),
Field(&NavigationDetails::url, IsSet()))))
.WillOnce(InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
controller->GoForward();
run_loop.Run();
navigation_observer_.Acknowledge();
}
// At the end of the navigation entry list; this should be a no-op.
controller->GoForward();
// Process the navigation request message.
base::RunLoop().RunUntilIdle();
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, ReloadFrame) {
chromium::web::FramePtr frame = CreateFrame();
chromium::web::NavigationControllerPtr navigation_controller;
frame->GetNavigationController(navigation_controller.NewRequest());
embedded_test_server()->RegisterRequestMonitor(base::BindRepeating(
&FrameImplTest::OnServeHttpRequest, base::Unretained(this)));
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kPage1Path));
EXPECT_CALL(*this, OnServeHttpRequest(_));
CheckLoadUrl(url.spec(), kPage1Title, navigation_controller.get());
navigation_observer_.Observe(
context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
// Reload with NO_CACHE.
{
base::RunLoop run_loop;
EXPECT_CALL(*this, OnServeHttpRequest(_));
EXPECT_CALL(navigation_observer_, DidFinishLoad(_, url))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
navigation_controller->Reload(chromium::web::ReloadType::NO_CACHE);
run_loop.Run();
Mock::VerifyAndClearExpectations(this);
navigation_observer_.Acknowledge();
}
// Reload with PARTIAL_CACHE.
{
base::RunLoop run_loop;
EXPECT_CALL(*this, OnServeHttpRequest(_));
EXPECT_CALL(navigation_observer_, DidFinishLoad(_, url))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
navigation_controller->Reload(chromium::web::ReloadType::PARTIAL_CACHE);
run_loop.Run();
}
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, GetVisibleEntry) {
chromium::web::FramePtr frame = CreateFrame();
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
// Verify that a Frame returns a null NavigationEntry prior to receiving any
// LoadUrl() calls.
{
base::RunLoop run_loop;
controller->GetVisibleEntry(
[&run_loop](std::unique_ptr<chromium::web::NavigationEntry> details) {
EXPECT_EQ(nullptr, details.get());
run_loop.Quit();
});
run_loop.Run();
}
ASSERT_TRUE(embedded_test_server()->Start());
GURL title1(embedded_test_server()->GetURL(kPage1Path));
GURL title2(embedded_test_server()->GetURL(kPage2Path));
// Navigate to a page.
{
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_,
MockableOnNavigationStateChanged(testing::AllOf(
Field(&NavigationDetails::title, kPage1Title),
Field(&NavigationDetails::url, IsSet()))))
.WillOnce(testing::InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
controller->LoadUrl(title1.spec(), nullptr);
run_loop.Run();
navigation_observer_.Acknowledge();
}
// Verify that GetVisibleEntry() reflects the new Frame navigation state.
{
base::RunLoop run_loop;
controller->GetVisibleEntry(
[&run_loop,
&title1](std::unique_ptr<chromium::web::NavigationEntry> details) {
EXPECT_TRUE(details);
EXPECT_EQ(details->url, title1.spec());
EXPECT_EQ(details->title, kPage1Title);
run_loop.Quit();
});
run_loop.Run();
}
// Navigate to another page.
{
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_,
MockableOnNavigationStateChanged(testing::AllOf(
Field(&NavigationDetails::title, kPage2Title),
Field(&NavigationDetails::url, IsSet()))))
.WillOnce(testing::InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
controller->LoadUrl(title2.spec(), nullptr);
run_loop.Run();
navigation_observer_.Acknowledge();
}
// Verify the navigation with GetVisibleEntry().
{
base::RunLoop run_loop;
controller->GetVisibleEntry(
[&run_loop,
&title2](std::unique_ptr<chromium::web::NavigationEntry> details) {
EXPECT_TRUE(details);
EXPECT_EQ(details->url, title2.spec());
EXPECT_EQ(details->title, kPage2Title);
run_loop.Quit();
});
run_loop.Run();
}
// Navigate back to the first page.
{
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_,
MockableOnNavigationStateChanged(testing::AllOf(
Field(&NavigationDetails::title, kPage1Title),
Field(&NavigationDetails::url, IsSet()))))
.WillOnce(testing::InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
controller->GoBack();
run_loop.Run();
navigation_observer_.Acknowledge();
}
// Verify the navigation with GetVisibleEntry().
{
base::RunLoop run_loop;
controller->GetVisibleEntry(
[&run_loop,
&title1](std::unique_ptr<chromium::web::NavigationEntry> details) {
EXPECT_TRUE(details);
EXPECT_EQ(details->url, title1.spec());
EXPECT_EQ(details->title, kPage1Title);
run_loop.Quit();
});
run_loop.Run();
}
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, NoNavigationObserverAttached) {
chromium::web::FramePtr frame;
context()->CreateFrame(frame.NewRequest());
base::RunLoop().RunUntilIdle();
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
ASSERT_TRUE(embedded_test_server()->Start());
GURL title1(embedded_test_server()->GetURL(kPage1Path));
GURL title2(embedded_test_server()->GetURL(kPage2Path));
navigation_observer_.Observe(
context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
{
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title1))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
controller->LoadUrl(title1.spec(), nullptr);
run_loop.Run();
}
{
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title2))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
controller->LoadUrl(title2.spec(), nullptr);
run_loop.Run();
}
}
fuchsia::mem::Buffer CreateBuffer(base::StringPiece data) {
fuchsia::mem::Buffer output;
output.size = data.size();
zx_status_t status = zx::vmo::create(data.size(), 0, &output.vmo);
ZX_CHECK(status == ZX_OK, ZX_OK) << "zx_vmo_create";
status = output.vmo.write(data.data(), 0, data.size());
ZX_CHECK(status == ZX_OK, status) << "zx_vmo_write";
return output;
}
// Test JS injection by using Javascript to trigger document navigation.
IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavaScriptImmediate) {
chromium::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL title1(embedded_test_server()->GetURL(kPage1Path));
GURL title2(embedded_test_server()->GetURL(kPage2Path));
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(title1.spec(), kPage1Title, controller.get());
fidl::VectorPtr<fidl::StringPtr> origins =
fidl::VectorPtr<fidl::StringPtr>::New(0);
origins.push_back(title1.GetOrigin().spec());
frame->ExecuteJavaScript(
std::move(origins),
CreateBuffer("window.location.href = \"" + title2.spec() + "\";"),
chromium::web::ExecuteMode::IMMEDIATE_ONCE,
[](bool success) { EXPECT_TRUE(success); });
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_,
MockableOnNavigationStateChanged(
testing::AllOf(Field(&NavigationDetails::title, kPage2Title),
Field(&NavigationDetails::url, IsSet()))))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
CheckRunWithTimeout(&run_loop);
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavaScriptOnLoad) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
chromium::web::FramePtr frame = CreateFrame();
fidl::VectorPtr<fidl::StringPtr> origins =
fidl::VectorPtr<fidl::StringPtr>::New(0);
origins.push_back(url.GetOrigin().spec());
frame->ExecuteJavaScript(std::move(origins),
CreateBuffer("stashed_title = 'hello';"),
chromium::web::ExecuteMode::ON_PAGE_LOAD,
[](bool success) { EXPECT_TRUE(success); });
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(url.spec(), "hello", controller.get());
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavascriptOnLoadWrongOrigin) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
chromium::web::FramePtr frame = CreateFrame();
fidl::VectorPtr<fidl::StringPtr> origins =
fidl::VectorPtr<fidl::StringPtr>::New(0);
origins.push_back("http://example.com");
frame->ExecuteJavaScript(std::move(origins),
CreateBuffer("stashed_title = 'hello';"),
chromium::web::ExecuteMode::ON_PAGE_LOAD,
[](bool success) { EXPECT_TRUE(success); });
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
// Expect that the original HTML title is used, because we didn't inject a
// script with a replacement title.
CheckLoadUrl(url.spec(), "Welcome to Stan the Offline Dino's Homepage",
controller.get());
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavaScriptOnLoadWildcardOrigin) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
chromium::web::FramePtr frame = CreateFrame();
fidl::VectorPtr<fidl::StringPtr> origins =
fidl::VectorPtr<fidl::StringPtr>::New(0);
origins.push_back("*");
frame->ExecuteJavaScript(std::move(origins),
CreateBuffer("stashed_title = 'hello';"),
chromium::web::ExecuteMode::ON_PAGE_LOAD,
[](bool success) { EXPECT_TRUE(success); });
// Test script injection for the origin 127.0.0.1.
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(url.spec(), "hello", controller.get());
CheckLoadUrl(url::kAboutBlankURL, url::kAboutBlankURL, controller.get());
// Test script injection using a different origin ("localhost"), which should
// still be picked up by the wildcard.
GURL alt_url = embedded_test_server()->GetURL("localhost", kDynamicTitlePath);
CheckLoadUrl(alt_url.spec(), "hello", controller.get());
}
// Test that consecutive scripts are executed in order by computing a cumulative
// result.
IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteMultipleJavaScriptsOnLoad) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
chromium::web::FramePtr frame = CreateFrame();
fidl::VectorPtr<fidl::StringPtr> origins =
fidl::VectorPtr<fidl::StringPtr>::New(0);
origins.push_back(url.GetOrigin().spec());
frame->ExecuteJavaScript(origins.Clone(),
CreateBuffer("stashed_title = 'hello';"),
chromium::web::ExecuteMode::ON_PAGE_LOAD,
[](bool success) { EXPECT_TRUE(success); });
frame->ExecuteJavaScript(std::move(origins),
CreateBuffer("stashed_title += ' there';"),
chromium::web::ExecuteMode::ON_PAGE_LOAD,
[](bool success) { EXPECT_TRUE(success); });
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(url.spec(), "hello there", controller.get());
}
// Test that we can inject scripts before and after RenderFrame creation.
IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteOnLoadEarlyAndLateRegistrations) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
chromium::web::FramePtr frame = CreateFrame();
fidl::VectorPtr<fidl::StringPtr> origins =
fidl::VectorPtr<fidl::StringPtr>::New(0);
origins.push_back(url.GetOrigin().spec());
frame->ExecuteJavaScript(origins.Clone(),
CreateBuffer("stashed_title = 'hello';"),
chromium::web::ExecuteMode::ON_PAGE_LOAD,
[](bool success) { EXPECT_TRUE(success); });
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(url.spec(), "hello", controller.get());
frame->ExecuteJavaScript(std::move(origins),
CreateBuffer("stashed_title += ' there';"),
chromium::web::ExecuteMode::ON_PAGE_LOAD,
[](bool success) { EXPECT_TRUE(success); });
// Navigate away to clean the slate.
CheckLoadUrl(url::kAboutBlankURL, url::kAboutBlankURL, controller.get());
// Navigate back and see if both scripts are working.
CheckLoadUrl(url.spec(), "hello there", controller.get());
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavaScriptBadEncoding) {
chromium::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kPage1Path));
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(url.spec(), kPage1Title, controller.get());
base::RunLoop run_loop;
// 0xFE is an illegal UTF-8 byte; it should cause UTF-8 conversion to fail.
fidl::VectorPtr<fidl::StringPtr> origins =
fidl::VectorPtr<fidl::StringPtr>::New(0);
origins.push_back(url.host());
frame->ExecuteJavaScript(std::move(origins), CreateBuffer("true;\xfe"),
chromium::web::ExecuteMode::IMMEDIATE_ONCE,
[&run_loop](bool success) {
EXPECT_FALSE(success);
run_loop.Quit();
});
CheckRunWithTimeout(&run_loop);
}
// Verifies that a Frame will handle navigation observer disconnection events
// gracefully.
IN_PROC_BROWSER_TEST_F(FrameImplTest, NavigationObserverDisconnected) {
chromium::web::FramePtr frame = CreateFrame();
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
ASSERT_TRUE(embedded_test_server()->Start());
GURL title1(embedded_test_server()->GetURL(kPage1Path));
GURL title2(embedded_test_server()->GetURL(kPage2Path));
navigation_observer_.Observe(
context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
{
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title1));
EXPECT_CALL(navigation_observer_,
MockableOnNavigationStateChanged(testing::AllOf(
Field(&NavigationDetails::title, kPage1Title),
Field(&NavigationDetails::url, IsSet()))))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
controller->LoadUrl(title1.spec(), nullptr);
run_loop.Run();
}
// Disconnect the observer & spin the runloop to propagate the disconnection
// event over IPC.
navigation_observer_bindings().CloseAll();
base::RunLoop().RunUntilIdle();
{
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title2))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
controller->LoadUrl(title2.spec(), nullptr);
run_loop.Run();
}
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, DelayedNavigationEventAck) {
chromium::web::FramePtr frame = CreateFrame();
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
ASSERT_TRUE(embedded_test_server()->Start());
GURL title1(embedded_test_server()->GetURL(kPage1Path));
GURL title2(embedded_test_server()->GetURL(kPage2Path));
// Expect an navigation event here, but deliberately postpone acknowledgement
// until the end of the test.
{
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_,
MockableOnNavigationStateChanged(testing::AllOf(
Field(&NavigationDetails::title, kPage1Title),
Field(&NavigationDetails::url, IsSet()))))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
controller->LoadUrl(title1.spec(), nullptr);
run_loop.Run();
Mock::VerifyAndClearExpectations(this);
}
// Since we have blocked NavigationEventObserver's flow, we must observe the
// WebContents events directly via a test-only seam.
navigation_observer_.Observe(
context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
// Navigate to a second page.
{
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title2))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
controller->LoadUrl(title2.spec(), nullptr);
run_loop.Run();
Mock::VerifyAndClearExpectations(this);
}
// Navigate to the first page.
{
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title1))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
controller->LoadUrl(title1.spec(), nullptr);
run_loop.Run();
Mock::VerifyAndClearExpectations(this);
}
// Since there was no observable change in navigation state since the last
// ack, there should be no more NavigationEvents generated.
{
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_,
MockableOnNavigationStateChanged(testing::AllOf(
Field(&NavigationDetails::title, kPage1Title),
Field(&NavigationDetails::url, IsSet()))))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
navigation_observer_.Acknowledge();
run_loop.Run();
}
}
// Observes events specific to the Stop() test case.
struct WebContentsObserverForStop : public content::WebContentsObserver {
using content::WebContentsObserver::Observe;
MOCK_METHOD1(DidStartNavigation, void(content::NavigationHandle*));
MOCK_METHOD0(NavigationStopped, void());
};
IN_PROC_BROWSER_TEST_F(FrameImplTest, Stop) {
chromium::web::FramePtr frame = CreateFrame();
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
ASSERT_TRUE(embedded_test_server()->Start());
// Use a request handler that will accept the connection and stall
// indefinitely.
GURL hung_url(embedded_test_server()->GetURL("/hung"));
WebContentsObserverForStop observer;
observer.Observe(
context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
{
base::RunLoop run_loop;
EXPECT_CALL(observer, DidStartNavigation(_))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
controller->LoadUrl(hung_url.spec(), nullptr);
run_loop.Run();
Mock::VerifyAndClearExpectations(this);
}
EXPECT_TRUE(
context_impl()->GetFrameImplForTest(&frame)->web_contents_->IsLoading());
{
base::RunLoop run_loop;
EXPECT_CALL(observer, NavigationStopped())
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
controller->Stop();
run_loop.Run();
Mock::VerifyAndClearExpectations(this);
}
EXPECT_FALSE(
context_impl()->GetFrameImplForTest(&frame)->web_contents_->IsLoading());
}
fuchsia::mem::Buffer MemBufferFromString(const std::string& data) {
fuchsia::mem::Buffer buffer;
zx_status_t status =
zx::vmo::create(data.size(), ZX_VMO_NON_RESIZABLE, &buffer.vmo);
ZX_CHECK(status == ZX_OK, status) << "zx_vmo_create";
status = buffer.vmo.write(data.data(), 0, data.size());
ZX_CHECK(status == ZX_OK, status) << "zx_vmo_write";
buffer.size = data.size();
return buffer;
}
// Reads a UTF-8 string from |buffer|.
std::string ReadFromBufferOrDie(const fuchsia::mem::Buffer& buffer) {
std::string output;
output.resize(buffer.size);
zx_status_t status = buffer.vmo.read(&output[0], 0, buffer.size);
ZX_CHECK(status == ZX_OK, status) << "zx_vmo_read";
return output;
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessage) {
chromium::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL post_message_url(
embedded_test_server()->GetURL("/window_post_message.html"));
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(post_message_url.spec(), "postmessage", controller.get());
chromium::web::WebMessage message;
message.data = MemBufferFromString(kPage1Path);
Promise<bool> post_result;
frame->PostMessage(std::move(message), post_message_url.GetOrigin().spec(),
post_result.GetReceiveCallback());
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_,
MockableOnNavigationStateChanged(
testing::AllOf(Field(&NavigationDetails::title, kPage1Title),
Field(&NavigationDetails::url, IsSet()))))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
CheckRunWithTimeout(&run_loop);
EXPECT_TRUE(*post_result);
}
// Send a MessagePort to the content, then perform bidirectional messaging
// through the port.
IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessagePassMessagePort) {
chromium::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(post_message_url.spec(), "messageport", controller.get());
chromium::web::MessagePortPtr message_port;
chromium::web::WebMessage msg;
{
msg.outgoing_transfer =
std::make_unique<chromium::web::OutgoingTransferable>();
msg.outgoing_transfer->set_message_port(message_port.NewRequest());
msg.data = MemBufferFromString("hi");
Promise<bool> post_result;
frame->PostMessage(std::move(msg), post_message_url.GetOrigin().spec(),
post_result.GetReceiveCallback());
base::RunLoop run_loop;
Promise<chromium::web::WebMessage> receiver(run_loop.QuitClosure());
message_port->ReceiveMessage(receiver.GetReceiveCallback());
CheckRunWithTimeout(&run_loop);
EXPECT_EQ("got_port", ReadFromBufferOrDie(receiver->data));
}
{
msg.data = MemBufferFromString("ping");
Promise<bool> post_result;
message_port->PostMessage(std::move(msg), post_result.GetReceiveCallback());
base::RunLoop run_loop;
Promise<chromium::web::WebMessage> receiver(run_loop.QuitClosure());
message_port->ReceiveMessage(receiver.GetReceiveCallback());
CheckRunWithTimeout(&run_loop);
EXPECT_EQ("ack ping", ReadFromBufferOrDie(receiver->data));
EXPECT_TRUE(*post_result);
}
}
// Send a MessagePort to the content, then perform bidirectional messaging
// over its channel.
IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessageMessagePortDisconnected) {
chromium::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(post_message_url.spec(), "messageport", controller.get());
chromium::web::MessagePortPtr message_port;
chromium::web::WebMessage msg;
{
msg.outgoing_transfer =
std::make_unique<chromium::web::OutgoingTransferable>();
msg.outgoing_transfer->set_message_port(message_port.NewRequest());
msg.data = MemBufferFromString("hi");
Promise<bool> post_result;
frame->PostMessage(std::move(msg), post_message_url.GetOrigin().spec(),
post_result.GetReceiveCallback());
base::RunLoop run_loop;
Promise<chromium::web::WebMessage> receiver(run_loop.QuitClosure());
message_port->ReceiveMessage(receiver.GetReceiveCallback());
CheckRunWithTimeout(&run_loop);
EXPECT_EQ("got_port", ReadFromBufferOrDie(receiver->data));
EXPECT_TRUE(*post_result);
}
// Navigating off-page should tear down the Mojo channel, thereby causing the
// MessagePortImpl to self-destruct and tear down its FIDL channel.
{
base::RunLoop run_loop;
message_port.set_error_handler(
[&run_loop](zx_status_t) { run_loop.Quit(); });
controller->LoadUrl(url::kAboutBlankURL, nullptr);
CheckRunWithTimeout(&run_loop);
}
}
// Send a MessagePort to the content, and through that channel, receive a
// different MessagePort that was created by the content. Verify the second
// channel's liveness by sending a ping to it.
IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessageUseContentProvidedPort) {
chromium::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(post_message_url.spec(), "messageport", controller.get());
chromium::web::MessagePortPtr incoming_message_port;
chromium::web::WebMessage msg;
{
chromium::web::MessagePortPtr message_port;
msg.outgoing_transfer =
std::make_unique<chromium::web::OutgoingTransferable>();
msg.outgoing_transfer->set_message_port(message_port.NewRequest());
msg.data = MemBufferFromString("hi");
Promise<bool> post_result;
frame->PostMessage(std::move(msg), "*", post_result.GetReceiveCallback());
base::RunLoop run_loop;
Promise<chromium::web::WebMessage> receiver(run_loop.QuitClosure());
message_port->ReceiveMessage(receiver.GetReceiveCallback());
CheckRunWithTimeout(&run_loop);
EXPECT_EQ("got_port", ReadFromBufferOrDie(receiver->data));
incoming_message_port = receiver->incoming_transfer->message_port().Bind();
EXPECT_TRUE(*post_result);
}
// Get the content to send three 'ack ping' messages, which will accumulate in
// the MessagePortImpl buffer.
for (int i = 0; i < 3; ++i) {
base::RunLoop run_loop;
Promise<bool> post_result(run_loop.QuitClosure());
msg.data = MemBufferFromString("ping");
incoming_message_port->PostMessage(std::move(msg),
post_result.GetReceiveCallback());
run_loop.Run();
EXPECT_TRUE(*post_result);
}
// Receive another acknowledgement from content on a side channel to ensure
// that all the "ack pings" are ready to be consumed.
{
chromium::web::MessagePortPtr ack_message_port;
chromium::web::WebMessage msg;
msg.outgoing_transfer =
std::make_unique<chromium::web::OutgoingTransferable>();
msg.outgoing_transfer->set_message_port(ack_message_port.NewRequest());
msg.data = MemBufferFromString("hi");
// Quit the runloop only after we've received a WebMessage AND a PostMessage
// result.
Promise<bool> post_result;
frame->PostMessage(std::move(msg), "*", post_result.GetReceiveCallback());
base::RunLoop run_loop;
Promise<chromium::web::WebMessage> receiver(run_loop.QuitClosure());
ack_message_port->ReceiveMessage(receiver.GetReceiveCallback());
CheckRunWithTimeout(&run_loop);
EXPECT_EQ("got_port", ReadFromBufferOrDie(receiver->data));
EXPECT_TRUE(*post_result);
}
// Pull the three 'ack ping's from the buffer.
for (int i = 0; i < 3; ++i) {
base::RunLoop run_loop;
Promise<chromium::web::WebMessage> receiver(run_loop.QuitClosure());
incoming_message_port->ReceiveMessage(receiver.GetReceiveCallback());
CheckRunWithTimeout(&run_loop);
EXPECT_EQ("ack ping", ReadFromBufferOrDie(receiver->data));
}
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessageBadOriginDropped) {
chromium::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(post_message_url.spec(), "messageport", controller.get());
chromium::web::MessagePortPtr bad_origin_incoming_message_port;
chromium::web::WebMessage msg;
// PostMessage() to invalid origins should be ignored. We pass in a
// MessagePort but nothing should happen to it.
chromium::web::MessagePortPtr unused_message_port;
msg.outgoing_transfer =
std::make_unique<chromium::web::OutgoingTransferable>();
msg.outgoing_transfer->set_message_port(unused_message_port.NewRequest());
msg.data = MemBufferFromString("bad origin, bad!");
Promise<bool> unused_post_result;
frame->PostMessage(std::move(msg), "https://example.com",
unused_post_result.GetReceiveCallback());
Promise<chromium::web::WebMessage> unused_message_read;
bad_origin_incoming_message_port->ReceiveMessage(
unused_message_read.GetReceiveCallback());
// PostMessage() with a valid origin should succeed.
// Verify it by looking for an ack message on the MessagePort we passed in.
// Since message events are handled in order, observing the result of this
// operation will verify whether the previous PostMessage() was received but
// discarded.
chromium::web::MessagePortPtr incoming_message_port;
chromium::web::MessagePortPtr message_port;
msg.outgoing_transfer =
std::make_unique<chromium::web::OutgoingTransferable>();
msg.outgoing_transfer->set_message_port(message_port.NewRequest());
msg.data = MemBufferFromString("good origin");
Promise<bool> post_result;
frame->PostMessage(std::move(msg), "*", post_result.GetReceiveCallback());
base::RunLoop run_loop;
Promise<chromium::web::WebMessage> receiver(run_loop.QuitClosure());
message_port->ReceiveMessage(receiver.GetReceiveCallback());
CheckRunWithTimeout(&run_loop);
EXPECT_EQ("got_port", ReadFromBufferOrDie(receiver->data));
incoming_message_port = receiver->incoming_transfer->message_port().Bind();
EXPECT_TRUE(*post_result);
// Verify that the first PostMessage() call wasn't handled.
EXPECT_FALSE(unused_message_read.has_value());
}
} // namespace webrunner