blob: 28f64e13357895e58f29cf56d9e3da9d6e067970 [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 <lib/ui/scenic/cpp/view_token_pair.h>
#include "base/bind.h"
#include "base/containers/span.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/macros.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind_test_util.h"
#include "base/test/test_timeouts.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "fuchsia/base/fit_adapter.h"
#include "fuchsia/base/frame_test_util.h"
#include "fuchsia/base/mem_buffer_util.h"
#include "fuchsia/base/result_receiver.h"
#include "fuchsia/base/string_util.h"
#include "fuchsia/base/test_navigation_listener.h"
#include "fuchsia/base/url_request_rewrite_test_util.h"
#include "fuchsia/engine/browser/frame_impl.h"
#include "fuchsia/engine/test/web_engine_browser_test.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 "net/test/embedded_test_server/request_handler_util.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"
using testing::_;
using testing::AllOf;
using testing::Field;
using testing::InvokeWithoutArgs;
using testing::Mock;
// Use a shorter name for NavigationState, because it is referenced frequently
// in this file.
using NavigationDetails = fuchsia::web::NavigationState;
using OnNavigationStateChangedCallback =
fuchsia::web::NavigationEventListener::OnNavigationStateChangedCallback;
namespace {
const char kPage1Path[] = "/title1.html";
const char kPage2Path[] = "/title2.html";
const char kPage3Path[] = "/websql.html";
const char kPage4Path[] = "/image.html";
const char kPage4ImgPath[] = "/img.png";
const char kDynamicTitlePath[] = "/dynamic_title.html";
const char kPopupPath[] = "/popup_parent.html";
const char kPopupRedirectPath[] = "/popup_child.html";
const char kPopupMultiplePath[] = "/popup_multiple.html";
const char kPage1Title[] = "title 1";
const char kPage2Title[] = "title 2";
const char kPage3Title[] = "websql not available";
const char kDataUrl[] =
"data:text/html;base64,PGI+SGVsbG8sIHdvcmxkLi4uPC9iPg==";
const char kTestServerRoot[] = FILE_PATH_LITERAL("fuchsia/engine/test/data");
const int64_t kOnLoadScriptId = 0;
MATCHER_P(NavigationHandleUrlEquals,
url,
"Checks equality with a NavigationHandle's URL.") {
return arg->GetURL() == url;
}
class MockWebContentsObserver : public content::WebContentsObserver {
public:
explicit MockWebContentsObserver(content::WebContents* web_contents) {
Observe(web_contents);
}
~MockWebContentsObserver() override = default;
MOCK_METHOD1(DidStartNavigation, void(content::NavigationHandle*));
MOCK_METHOD1(RenderViewDeleted,
void(content::RenderViewHost* render_view_host));
};
std::string StringFromMemBufferOrDie(const fuchsia::mem::Buffer& buffer) {
std::string output;
CHECK(cr_fuchsia::StringFromMemBuffer(buffer, &output));
return output;
}
} // namespace
// Defines a suite of tests that exercise Frame-level functionality, such as
// navigation commands and page events.
class FrameImplTest : public cr_fuchsia::WebEngineBrowserTest {
public:
FrameImplTest()
: run_timeout_(TestTimeouts::action_timeout(),
base::MakeExpectedNotRunClosure(FROM_HERE)) {
set_test_server_root(base::FilePath(kTestServerRoot));
}
~FrameImplTest() = default;
MOCK_METHOD1(OnServeHttpRequest,
void(const net::test_server::HttpRequest& request));
protected:
// Creates a Frame with |navigation_listener_| attached.
fuchsia::web::FramePtr CreateFrame() {
return WebEngineBrowserTest::CreateFrame(&navigation_listener_);
}
cr_fuchsia::TestNavigationListener navigation_listener_;
private:
const base::RunLoop::ScopedRunTimeoutForTest run_timeout_;
DISALLOW_COPY_AND_ASSIGN(FrameImplTest);
};
void VerifyCanGoBackAndForward(fuchsia::web::NavigationController* controller,
bool can_go_back_expected,
bool can_go_forward_expected) {
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::NavigationState> visible_entry(
run_loop.QuitClosure());
controller->GetVisibleEntry(
cr_fuchsia::CallbackToFitFunction(visible_entry.GetReceiveCallback()));
run_loop.Run();
EXPECT_TRUE(visible_entry->has_can_go_back());
EXPECT_EQ(visible_entry->can_go_back(), can_go_back_expected);
EXPECT_TRUE(visible_entry->has_can_go_forward());
EXPECT_EQ(visible_entry->can_go_forward(), can_go_forward_expected);
}
// Verifies that the browser will navigate and generate a navigation listener
// event when LoadUrl() is called.
IN_PROC_BROWSER_TEST_F(FrameImplTest, NavigateFrame) {
fuchsia::web::FramePtr frame = CreateFrame();
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url::kAboutBlankURL));
navigation_listener_.RunUntilUrlAndTitleEquals(GURL(url::kAboutBlankURL),
url::kAboutBlankURL);
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, NavigateDataFrame) {
fuchsia::web::FramePtr frame = CreateFrame();
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), kDataUrl));
navigation_listener_.RunUntilUrlAndTitleEquals(GURL(kDataUrl), kDataUrl);
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, FrameDeletedBeforeContext) {
fuchsia::web::FramePtr frame = CreateFrame();
// Process the frame creation message.
base::RunLoop().RunUntilIdle();
FrameImpl* frame_impl = context_impl()->GetFrameImplForTest(&frame);
MockWebContentsObserver deletion_observer(frame_impl->web_contents());
base::RunLoop run_loop;
EXPECT_CALL(deletion_observer, RenderViewDeleted(_))
.WillOnce(InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url::kAboutBlankURL));
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) {
fuchsia::web::FramePtr frame = CreateFrame();
EXPECT_TRUE(frame);
base::RunLoop run_loop;
frame.set_error_handler([&run_loop](zx_status_t status) {
EXPECT_EQ(status, ZX_ERR_PEER_CLOSED);
run_loop.Quit();
});
context().Unbind();
run_loop.Run();
EXPECT_FALSE(frame);
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, ContextDeletedBeforeFrameWithView) {
fuchsia::web::FramePtr frame = CreateFrame();
EXPECT_TRUE(frame);
auto view_tokens = scenic::NewViewTokenPair();
frame->CreateView(std::move(view_tokens.first));
base::RunLoop().RunUntilIdle();
base::RunLoop run_loop;
frame.set_error_handler([&run_loop](zx_status_t status) {
EXPECT_EQ(status, ZX_ERR_PEER_CLOSED);
run_loop.Quit();
});
context().Unbind();
run_loop.Run();
EXPECT_FALSE(frame);
}
// TODO(https://crbug.com/695592): Remove this test when WebSQL is removed from
// Chrome.
IN_PROC_BROWSER_TEST_F(FrameImplTest, EnsureWebSqlDisabled) {
fuchsia::web::FramePtr frame = CreateFrame();
EXPECT_TRUE(frame);
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
ASSERT_TRUE(embedded_test_server()->Start());
GURL title3(embedded_test_server()->GetURL(kPage3Path));
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), title3.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(title3, kPage3Title);
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, GoBackAndForward) {
fuchsia::web::FramePtr frame = CreateFrame();
fuchsia::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_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), title1.spec()));
navigation_listener_.RunUntilUrlTitleBackForwardEquals(title1, kPage1Title,
false, false);
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), title2.spec()));
navigation_listener_.RunUntilUrlTitleBackForwardEquals(title2, kPage2Title,
true, false);
VerifyCanGoBackAndForward(controller.get(), true, false);
controller->GoBack();
navigation_listener_.RunUntilUrlTitleBackForwardEquals(title1, kPage1Title,
false, true);
// At the top of the navigation entry list; this should be a no-op.
VerifyCanGoBackAndForward(controller.get(), false, true);
controller->GoBack();
// Process the navigation request message.
base::RunLoop().RunUntilIdle();
VerifyCanGoBackAndForward(controller.get(), false, true);
controller->GoForward();
navigation_listener_.RunUntilUrlTitleBackForwardEquals(title2, kPage2Title,
true, false);
// At the end of the navigation entry list; this should be a no-op.
VerifyCanGoBackAndForward(controller.get(), true, false);
controller->GoForward();
// Process the navigation request message.
base::RunLoop().RunUntilIdle();
}
// An HTTP response stream whose response payload can be sent as "chunks"
// with indeterminate-length pauses in between.
class ChunkedHttpTransaction {
public:
ChunkedHttpTransaction(const net::test_server::SendBytesCallback& send,
const net::test_server::SendCompleteCallback& done)
: io_task_runner_(base::ThreadTaskRunnerHandle::Get()),
send_callback_(send),
done_callback_(done) {
DCHECK(!current_instance_);
DCHECK(send_callback_);
DCHECK(done_callback_);
current_instance_ = this;
}
static ChunkedHttpTransaction* current() {
DCHECK(current_instance_);
return current_instance_;
}
void Close() {
EnsureSendCompleted();
io_task_runner_->PostTask(FROM_HERE, done_callback_);
delete this;
}
void EnsureSendCompleted() {
if (send_callback_)
return;
base::RunLoop run_loop;
send_chunk_complete_callback_ = run_loop.QuitClosure();
run_loop.Run();
DCHECK(send_callback_);
}
void SendChunk(std::string chunk) {
EnsureSendCompleted();
// Temporarily nullify |send_callback_| while the operation is inflight, to
// guard against concurrent sends. The callback will be restored by
// SendChunkComplete().
net::test_server::SendBytesCallback inflight_send_callback = send_callback_;
send_callback_ = {};
io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(inflight_send_callback, chunk,
base::BindRepeating(
&ChunkedHttpTransaction::SendChunkCompleteOnIoThread,
base::Unretained(this), inflight_send_callback,
base::ThreadTaskRunnerHandle::Get())));
}
private:
static ChunkedHttpTransaction* current_instance_;
~ChunkedHttpTransaction() { current_instance_ = nullptr; }
void SendChunkCompleteOnIoThread(
net::test_server::SendBytesCallback send_callback,
scoped_refptr<base::TaskRunner> ui_thread_task_runner) {
ui_thread_task_runner->PostTask(
FROM_HERE,
base::BindOnce(&ChunkedHttpTransaction::SendChunkCompleteOnUiThread,
base::Unretained(this), send_callback));
}
void SendChunkCompleteOnUiThread(
net::test_server::SendBytesCallback send_callback) {
send_callback_ = send_callback;
if (send_chunk_complete_callback_)
std::move(send_chunk_complete_callback_).Run();
}
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
// Set by callers to SendChunk() waiting for the previous chunk to complete.
base::OnceClosure send_chunk_complete_callback_;
// Callbacks are affine with |io_task_runner_|.
net::test_server::SendBytesCallback send_callback_;
net::test_server::SendCompleteCallback done_callback_;
DISALLOW_COPY_AND_ASSIGN(ChunkedHttpTransaction);
};
ChunkedHttpTransaction* ChunkedHttpTransaction::current_instance_ = nullptr;
class ChunkedHttpTransactionFactory : public net::test_server::HttpResponse {
public:
ChunkedHttpTransactionFactory() = default;
~ChunkedHttpTransactionFactory() override = default;
void SetOnResponseCreatedCallback(base::OnceClosure on_response_created) {
on_response_created_ = std::move(on_response_created);
}
// net::test_server::HttpResponse implementation.
void SendResponse(
const net::test_server::SendBytesCallback& send,
const net::test_server::SendCompleteCallback& done) override {
// The ChunkedHttpTransaction manages its own lifetime.
new ChunkedHttpTransaction(send, done);
if (on_response_created_)
std::move(on_response_created_).Run();
}
private:
base::OnceClosure on_response_created_;
DISALLOW_COPY_AND_ASSIGN(ChunkedHttpTransactionFactory);
};
IN_PROC_BROWSER_TEST_F(FrameImplTest, NavigationEventDuringPendingLoad) {
fuchsia::web::FramePtr frame = CreateFrame();
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
ChunkedHttpTransactionFactory* factory = new ChunkedHttpTransactionFactory;
base::RunLoop transaction_created_run_loop;
factory->SetOnResponseCreatedCallback(
transaction_created_run_loop.QuitClosure());
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&net::test_server::HandlePrefixedRequest, "/pausable",
base::BindRepeating(
[](std::unique_ptr<ChunkedHttpTransactionFactory> out_factory,
const net::test_server::HttpRequest&)
-> std::unique_ptr<net::test_server::HttpResponse> {
return out_factory;
},
base::Passed(base::WrapUnique(factory)))));
ASSERT_TRUE(embedded_test_server()->Start());
GURL hung_url(embedded_test_server()->GetURL("/pausable"));
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), hung_url.spec()));
fuchsia::web::NavigationState state_change;
state_change.set_url(hung_url.spec());
state_change.set_is_main_document_loaded(false);
transaction_created_run_loop.Run();
ChunkedHttpTransaction* transaction = ChunkedHttpTransaction::current();
transaction->SendChunk(
"HTTP/1.0 200 OK\r\n"
"Host: localhost\r\n"
"Content-Type: text/html\r\n\r\n"
"<html><head><title>initial load</title>");
state_change.set_title("initial load");
state_change.set_is_main_document_loaded(false);
navigation_listener_.RunUntilNavigationStateMatches(state_change);
transaction->SendChunk(
"<script>document.title='final load';</script><body></body>");
transaction->Close();
state_change.set_title("final load");
state_change.set_is_main_document_loaded(true);
navigation_listener_.RunUntilNavigationStateMatches(state_change);
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, ReloadFrame) {
fuchsia::web::FramePtr frame = CreateFrame();
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(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(_)).Times(testing::AtLeast(1));
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(url, kPage1Title);
// Reload with NO_CACHE.
{
MockWebContentsObserver web_contents_observer(
context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
base::RunLoop run_loop;
EXPECT_CALL(web_contents_observer,
DidStartNavigation(NavigationHandleUrlEquals(url)))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
controller->Reload(fuchsia::web::ReloadType::NO_CACHE);
run_loop.Run();
}
// Reload with PARTIAL_CACHE.
{
MockWebContentsObserver web_contents_observer(
context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
base::RunLoop run_loop;
EXPECT_CALL(web_contents_observer,
DidStartNavigation(NavigationHandleUrlEquals(url)))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
controller->Reload(fuchsia::web::ReloadType::PARTIAL_CACHE);
run_loop.Run();
}
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, GetVisibleEntry) {
fuchsia::web::FramePtr frame = CreateFrame();
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
// Verify that a Frame returns an empty NavigationState prior to receiving any
// LoadUrl() calls.
{
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::NavigationState> result(
run_loop.QuitClosure());
controller->GetVisibleEntry(
cr_fuchsia::CallbackToFitFunction(result.GetReceiveCallback()));
run_loop.Run();
EXPECT_FALSE(result->has_title());
EXPECT_FALSE(result->has_url());
EXPECT_FALSE(result->has_page_type());
}
ASSERT_TRUE(embedded_test_server()->Start());
GURL title1(embedded_test_server()->GetURL(kPage1Path));
GURL title2(embedded_test_server()->GetURL(kPage2Path));
// Navigate to a page.
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), title1.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(title1, kPage1Title);
// Verify that GetVisibleEntry() reflects the new Frame navigation state.
{
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::NavigationState> result(
run_loop.QuitClosure());
controller->GetVisibleEntry(
cr_fuchsia::CallbackToFitFunction(result.GetReceiveCallback()));
run_loop.Run();
ASSERT_TRUE(result->has_url());
EXPECT_EQ(result->url(), title1.spec());
ASSERT_TRUE(result->has_title());
EXPECT_EQ(result->title(), kPage1Title);
ASSERT_TRUE(result->has_page_type());
EXPECT_EQ(result->page_type(), fuchsia::web::PageType::NORMAL);
}
// Navigate to another page.
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), title2.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(title2, kPage2Title);
// Verify the navigation with GetVisibleEntry().
{
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::NavigationState> result(
run_loop.QuitClosure());
controller->GetVisibleEntry(
cr_fuchsia::CallbackToFitFunction(result.GetReceiveCallback()));
run_loop.Run();
ASSERT_TRUE(result->has_url());
EXPECT_EQ(result->url(), title2.spec());
ASSERT_TRUE(result->has_title());
EXPECT_EQ(result->title(), kPage2Title);
ASSERT_TRUE(result->has_page_type());
EXPECT_EQ(result->page_type(), fuchsia::web::PageType::NORMAL);
}
// Navigate back to the first page.
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), title1.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(title1, kPage1Title);
// Verify the navigation with GetVisibleEntry().
{
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::NavigationState> result(
run_loop.QuitClosure());
controller->GetVisibleEntry(
cr_fuchsia::CallbackToFitFunction(result.GetReceiveCallback()));
run_loop.Run();
ASSERT_TRUE(result->has_url());
EXPECT_EQ(result->url(), title1.spec());
ASSERT_TRUE(result->has_title());
EXPECT_EQ(result->title(), kPage1Title);
ASSERT_TRUE(result->has_page_type());
EXPECT_EQ(result->page_type(), fuchsia::web::PageType::NORMAL);
}
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, NoNavigationObserverAttached) {
fuchsia::web::FramePtr frame = WebEngineBrowserTest::CreateFrame(nullptr);
base::RunLoop().RunUntilIdle();
fuchsia::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));
MockWebContentsObserver observer(
context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
{
base::RunLoop run_loop;
EXPECT_CALL(observer, DidStartNavigation(NavigationHandleUrlEquals(title1)))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), title1.spec()));
run_loop.Run();
}
{
base::RunLoop run_loop;
EXPECT_CALL(observer, DidStartNavigation(NavigationHandleUrlEquals(title2)))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), title2.spec()));
run_loop.Run();
}
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, BeforeLoadScript) {
constexpr int64_t kBindingsId = 1234;
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
fuchsia::web::FramePtr frame = CreateFrame();
frame->AddBeforeLoadJavaScript(
kBindingsId, {url.GetOrigin().spec()},
cr_fuchsia::MemBufferFromString("stashed_title = 'hello';", "test"),
[](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
EXPECT_TRUE(result.is_response());
});
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url.spec()));
navigation_listener_.RunUntilUrlEquals(url);
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, BeforeLoadScriptUpdated) {
constexpr int64_t kBindingsId = 1234;
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
fuchsia::web::FramePtr frame = CreateFrame();
frame->AddBeforeLoadJavaScript(
kBindingsId, {url.GetOrigin().spec()},
cr_fuchsia::MemBufferFromString("stashed_title = 'hello';", "test"),
[](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
EXPECT_TRUE(result.is_response());
});
// Verify that this script clobbers the previous script, as opposed to being
// injected alongside it. (The latter would result in the title being
// "helloclobber").
frame->AddBeforeLoadJavaScript(
kBindingsId, {url.GetOrigin().spec()},
cr_fuchsia::MemBufferFromString(
"stashed_title = document.title + 'clobber';", "test"),
[](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
EXPECT_TRUE(result.is_response());
});
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(url, "clobber");
}
// Verifies that bindings are injected in order by producing a cumulative,
// non-commutative result.
IN_PROC_BROWSER_TEST_F(FrameImplTest, BeforeLoadScriptOrdered) {
constexpr int64_t kBindingsId1 = 1234;
constexpr int64_t kBindingsId2 = 5678;
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
fuchsia::web::FramePtr frame = CreateFrame();
frame->AddBeforeLoadJavaScript(
kBindingsId1, {url.GetOrigin().spec()},
cr_fuchsia::MemBufferFromString("stashed_title = 'hello';", "test"),
[](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
EXPECT_TRUE(result.is_response());
});
frame->AddBeforeLoadJavaScript(
kBindingsId2, {url.GetOrigin().spec()},
cr_fuchsia::MemBufferFromString("stashed_title += ' there';", "test"),
[](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
EXPECT_TRUE(result.is_response());
});
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(url, "hello there");
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, BeforeLoadScriptRemoved) {
constexpr int64_t kBindingsId1 = 1234;
constexpr int64_t kBindingsId2 = 5678;
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
fuchsia::web::FramePtr frame = CreateFrame();
frame->AddBeforeLoadJavaScript(
kBindingsId1, {url.GetOrigin().spec()},
cr_fuchsia::MemBufferFromString("stashed_title = 'foo';", "test"),
[](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
EXPECT_TRUE(result.is_response());
});
// Add a script which clobbers "foo".
frame->AddBeforeLoadJavaScript(
kBindingsId2, {url.GetOrigin().spec()},
cr_fuchsia::MemBufferFromString("stashed_title = 'bar';", "test"),
[](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
EXPECT_TRUE(result.is_response());
});
// Deletes the clobbering script.
frame->RemoveBeforeLoadJavaScript(kBindingsId2);
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(url, "foo");
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, BeforeLoadScriptRemoveInvalidId) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kPage1Path));
fuchsia::web::FramePtr frame = CreateFrame();
frame->RemoveBeforeLoadJavaScript(kOnLoadScriptId);
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(url, kPage1Title);
}
// Test JS injection using ExecuteJavaScriptNoResult() to set a value, and
// ExecuteJavaScript() to retrieve that value.
IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavaScript) {
constexpr char kJsonStringLiteral[] = "\"I am a literal, literally\"";
fuchsia::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kUrl(embedded_test_server()->GetURL(kPage1Path));
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
// Navigate to a page and wait for it to finish loading.
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), kUrl.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(kUrl, kPage1Title);
// Execute with no result to set the variable.
frame->ExecuteJavaScriptNoResult(
{kUrl.GetOrigin().spec()},
cr_fuchsia::MemBufferFromString(
base::StringPrintf("my_variable = %s;", kJsonStringLiteral), "test"),
[](fuchsia::web::Frame_ExecuteJavaScriptNoResult_Result result) {
EXPECT_TRUE(result.is_response());
});
// Execute a script snippet to return the variable's value.
base::RunLoop loop;
frame->ExecuteJavaScript(
{kUrl.GetOrigin().spec()},
cr_fuchsia::MemBufferFromString("my_variable;", "test"),
[&](fuchsia::web::Frame_ExecuteJavaScript_Result result) {
ASSERT_TRUE(result.is_response());
std::string result_json =
StringFromMemBufferOrDie(result.response().result);
EXPECT_EQ(result_json, kJsonStringLiteral);
loop.Quit();
});
loop.Run();
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, BeforeLoadScriptVmoDestroyed) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
fuchsia::web::FramePtr frame = CreateFrame();
frame->AddBeforeLoadJavaScript(
kOnLoadScriptId, {url.GetOrigin().spec()},
cr_fuchsia::MemBufferFromString("stashed_title = 'hello';", "test"),
[](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
EXPECT_TRUE(result.is_response());
});
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(url, "hello");
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, BeforeLoadScriptWrongOrigin) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
fuchsia::web::FramePtr frame = CreateFrame();
frame->AddBeforeLoadJavaScript(
kOnLoadScriptId, {"http://example.com"},
cr_fuchsia::MemBufferFromString("stashed_title = 'hello';", "test"),
[](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
EXPECT_TRUE(result.is_response());
});
fuchsia::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.
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(
url, "Welcome to Stan the Offline Dino's Homepage");
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, BeforeLoadScriptWildcardOrigin) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
fuchsia::web::FramePtr frame = CreateFrame();
frame->AddBeforeLoadJavaScript(
kOnLoadScriptId, {"*"},
cr_fuchsia::MemBufferFromString("stashed_title = 'hello';", "test"),
[](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
EXPECT_TRUE(result.is_response());
});
// Test script injection for the origin 127.0.0.1.
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(url, "hello");
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url::kAboutBlankURL));
navigation_listener_.RunUntilUrlEquals(GURL(url::kAboutBlankURL));
// 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);
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), alt_url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(alt_url, "hello");
}
// Test that we can inject scripts before and after RenderFrame creation.
IN_PROC_BROWSER_TEST_F(FrameImplTest,
BeforeLoadScriptEarlyAndLateRegistrations) {
constexpr int64_t kOnLoadScriptId2 = kOnLoadScriptId + 1;
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
fuchsia::web::FramePtr frame = CreateFrame();
frame->AddBeforeLoadJavaScript(
kOnLoadScriptId, {url.GetOrigin().spec()},
cr_fuchsia::MemBufferFromString("stashed_title = 'hello';", "test"),
[](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
EXPECT_TRUE(result.is_response());
});
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(url, "hello");
frame->AddBeforeLoadJavaScript(
kOnLoadScriptId2, {url.GetOrigin().spec()},
cr_fuchsia::MemBufferFromString("stashed_title += ' there';", "test"),
[](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
EXPECT_TRUE(result.is_response());
});
// Navigate away to clean the slate.
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url::kAboutBlankURL));
navigation_listener_.RunUntilUrlEquals(GURL(url::kAboutBlankURL));
// Navigate back and see if both scripts are working.
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(url, "hello there");
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavaScriptBadEncoding) {
fuchsia::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kPage1Path));
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(url, kPage1Title);
base::RunLoop run_loop;
// 0xFE is an illegal UTF-8 byte; it should cause UTF-8 conversion to fail.
frame->ExecuteJavaScriptNoResult(
{url.GetOrigin().spec()},
cr_fuchsia::MemBufferFromString("true;\xfe", "test"),
[&run_loop](fuchsia::web::Frame_ExecuteJavaScriptNoResult_Result result) {
EXPECT_TRUE(result.is_err());
EXPECT_EQ(result.err(), fuchsia::web::FrameError::BUFFER_NOT_UTF8);
run_loop.Quit();
});
run_loop.Run();
}
// Verifies that a Frame will handle navigation listener disconnection events
// gracefully.
IN_PROC_BROWSER_TEST_F(FrameImplTest, NavigationObserverDisconnected) {
fuchsia::web::FramePtr frame = CreateFrame();
fuchsia::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));
MockWebContentsObserver web_contents_observer(
context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
EXPECT_CALL(web_contents_observer,
DidStartNavigation(NavigationHandleUrlEquals(title1)));
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), title1.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(title1, kPage1Title);
// Disconnect the listener & spin the runloop to propagate the disconnection
// event over IPC.
navigation_listener_bindings().CloseAll();
base::RunLoop().RunUntilIdle();
base::RunLoop run_loop;
EXPECT_CALL(web_contents_observer,
DidStartNavigation(NavigationHandleUrlEquals(title2)))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), title2.spec()));
run_loop.Run();
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, DelayedNavigationEventAck) {
fuchsia::web::FramePtr frame = CreateFrame();
fuchsia::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.
OnNavigationStateChangedCallback captured_ack_cb;
navigation_listener_.SetBeforeAckHook(base::BindRepeating(
[](OnNavigationStateChangedCallback* dest_cb,
const fuchsia::web::NavigationState& state,
OnNavigationStateChangedCallback cb) { *dest_cb = std::move(cb); },
base::Unretained(&captured_ack_cb)));
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), title1.spec()));
fuchsia::web::NavigationState expected_state;
expected_state.set_url(title1.spec());
navigation_listener_.RunUntilNavigationStateMatches(expected_state);
EXPECT_TRUE(captured_ack_cb);
navigation_listener_.SetBeforeAckHook({});
// Navigate to a second page.
{
// Since we have blocked NavigationEventObserver's flow, we must observe the
// lower level browser navigation events directly from the WebContents.
MockWebContentsObserver web_contents_observer(
context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
base::RunLoop run_loop;
EXPECT_CALL(web_contents_observer,
DidStartNavigation(NavigationHandleUrlEquals(title2)))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), title2.spec()));
run_loop.Run();
}
// Navigate to the first page.
{
MockWebContentsObserver web_contents_observer(
context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
base::RunLoop run_loop;
EXPECT_CALL(web_contents_observer,
DidStartNavigation(NavigationHandleUrlEquals(title1)))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), title1.spec()));
run_loop.Run();
}
// Since there was no observable change in navigation state since the last
// ack, there should be no more NavigationEvents generated.
captured_ack_cb();
navigation_listener_.RunUntilUrlAndTitleEquals(title1, kPage1Title);
}
// 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) {
fuchsia::web::FramePtr frame = CreateFrame();
fuchsia::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"));
{
base::RunLoop run_loop;
WebContentsObserverForStop observer;
observer.Observe(
context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
EXPECT_CALL(observer, DidStartNavigation(_))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
controller->LoadUrl(
hung_url.spec(), fuchsia::web::LoadUrlParams(),
[](fuchsia::web::NavigationController_LoadUrl_Result) {});
run_loop.Run();
}
EXPECT_TRUE(
context_impl()->GetFrameImplForTest(&frame)->web_contents_->IsLoading());
{
base::RunLoop run_loop;
WebContentsObserverForStop observer;
observer.Observe(
context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
EXPECT_CALL(observer, NavigationStopped())
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
controller->Stop();
run_loop.Run();
}
EXPECT_FALSE(
context_impl()->GetFrameImplForTest(&frame)->web_contents_->IsLoading());
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessage) {
fuchsia::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL post_message_url(
embedded_test_server()->GetURL("/window_post_message.html"));
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(),
post_message_url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(post_message_url,
"postmessage");
fuchsia::web::WebMessage message;
message.set_data(cr_fuchsia::MemBufferFromString(kPage1Path, "test"));
cr_fuchsia::ResultReceiver<fuchsia::web::Frame_PostMessage_Result>
post_result;
frame->PostMessage(
post_message_url.GetOrigin().spec(), std::move(message),
cr_fuchsia::CallbackToFitFunction(post_result.GetReceiveCallback()));
navigation_listener_.RunUntilUrlAndTitleEquals(
embedded_test_server()->GetURL(kPage1Path), kPage1Title);
EXPECT_TRUE(post_result->is_response());
}
// Send a MessagePort to the content, then perform bidirectional messaging
// through the port.
IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessagePassMessagePort) {
fuchsia::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(),
post_message_url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(post_message_url,
"messageport");
fuchsia::web::MessagePortPtr message_port;
fuchsia::web::WebMessage msg;
{
fuchsia::web::OutgoingTransferable outgoing;
outgoing.set_message_port(message_port.NewRequest());
std::vector<fuchsia::web::OutgoingTransferable> outgoing_vector;
outgoing_vector.push_back(std::move(outgoing));
msg.set_outgoing_transfer(std::move(outgoing_vector));
msg.set_data(cr_fuchsia::MemBufferFromString("hi", "test"));
cr_fuchsia::ResultReceiver<fuchsia::web::Frame_PostMessage_Result>
post_result;
frame->PostMessage(
post_message_url.GetOrigin().spec(), std::move(msg),
cr_fuchsia::CallbackToFitFunction(post_result.GetReceiveCallback()));
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::WebMessage> receiver(
run_loop.QuitClosure());
message_port->ReceiveMessage(
cr_fuchsia::CallbackToFitFunction(receiver.GetReceiveCallback()));
run_loop.Run();
ASSERT_TRUE(receiver->has_data());
EXPECT_EQ("got_port", StringFromMemBufferOrDie(receiver->data()));
}
{
msg.set_data(cr_fuchsia::MemBufferFromString("ping", "test"));
cr_fuchsia::ResultReceiver<fuchsia::web::MessagePort_PostMessage_Result>
post_result;
message_port->PostMessage(
std::move(msg),
cr_fuchsia::CallbackToFitFunction(post_result.GetReceiveCallback()));
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::WebMessage> receiver(
run_loop.QuitClosure());
message_port->ReceiveMessage(
cr_fuchsia::CallbackToFitFunction(receiver.GetReceiveCallback()));
run_loop.Run();
ASSERT_TRUE(receiver->has_data());
EXPECT_EQ("ack ping", StringFromMemBufferOrDie(receiver->data()));
EXPECT_TRUE(post_result->is_response());
}
}
// Send a MessagePort to the content, then perform bidirectional messaging
// over its channel.
IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessageMessagePortDisconnected) {
fuchsia::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(),
post_message_url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(post_message_url,
"messageport");
fuchsia::web::MessagePortPtr message_port;
fuchsia::web::WebMessage msg;
{
fuchsia::web::OutgoingTransferable outgoing;
outgoing.set_message_port(message_port.NewRequest());
std::vector<fuchsia::web::OutgoingTransferable> outgoing_vector;
outgoing_vector.push_back(std::move(outgoing));
msg.set_outgoing_transfer(std::move(outgoing_vector));
msg.set_data(cr_fuchsia::MemBufferFromString("hi", "test"));
cr_fuchsia::ResultReceiver<fuchsia::web::Frame_PostMessage_Result>
post_result;
frame->PostMessage(
post_message_url.GetOrigin().spec(), std::move(msg),
cr_fuchsia::CallbackToFitFunction(post_result.GetReceiveCallback()));
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::WebMessage> receiver(
run_loop.QuitClosure());
message_port->ReceiveMessage(
cr_fuchsia::CallbackToFitFunction(receiver.GetReceiveCallback()));
run_loop.Run();
ASSERT_TRUE(receiver->has_data());
EXPECT_EQ("got_port", StringFromMemBufferOrDie(receiver->data()));
EXPECT_TRUE(post_result->is_response());
}
// Navigating off-page should tear down the Mojo channel, thereby causing the
// MessagePortImpl to self-destruct and tear down its FIDL channel.
{
base::RunLoop run_loop;
message_port.set_error_handler(
[&run_loop](zx_status_t) { run_loop.Quit(); });
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), url::kAboutBlankURL));
run_loop.Run();
}
}
// Send a MessagePort to the content, and through that channel, receive a
// different MessagePort that was created by the content. Verify the second
// channel's liveness by sending a ping to it.
IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessageUseContentProvidedPort) {
fuchsia::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(),
post_message_url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(post_message_url,
"messageport");
fuchsia::web::MessagePortPtr incoming_message_port;
fuchsia::web::WebMessage msg;
{
fuchsia::web::MessagePortPtr message_port;
fuchsia::web::OutgoingTransferable outgoing;
outgoing.set_message_port(message_port.NewRequest());
std::vector<fuchsia::web::OutgoingTransferable> outgoing_vector;
outgoing_vector.push_back(std::move(outgoing));
msg.set_outgoing_transfer(std::move(outgoing_vector));
msg.set_data(cr_fuchsia::MemBufferFromString("hi", "test"));
cr_fuchsia::ResultReceiver<fuchsia::web::Frame_PostMessage_Result>
post_result;
frame->PostMessage(
"*", std::move(msg),
cr_fuchsia::CallbackToFitFunction(post_result.GetReceiveCallback()));
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::WebMessage> receiver(
run_loop.QuitClosure());
message_port->ReceiveMessage(
cr_fuchsia::CallbackToFitFunction(receiver.GetReceiveCallback()));
run_loop.Run();
ASSERT_TRUE(receiver->has_data());
EXPECT_EQ("got_port", StringFromMemBufferOrDie(receiver->data()));
ASSERT_TRUE(receiver->has_incoming_transfer());
ASSERT_EQ(receiver->incoming_transfer().size(), 1u);
incoming_message_port =
receiver->mutable_incoming_transfer()->at(0).message_port().Bind();
EXPECT_TRUE(post_result->is_response());
}
// Get the content to send three 'ack ping' messages, which will accumulate in
// the MessagePortImpl buffer.
for (int i = 0; i < 3; ++i) {
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::MessagePort_PostMessage_Result>
post_result(run_loop.QuitClosure());
msg.set_data(cr_fuchsia::MemBufferFromString("ping", "test"));
incoming_message_port->PostMessage(
std::move(msg),
cr_fuchsia::CallbackToFitFunction(post_result.GetReceiveCallback()));
run_loop.Run();
EXPECT_TRUE(post_result->is_response());
}
// Receive another acknowledgement from content on a side channel to ensure
// that all the "ack pings" are ready to be consumed.
{
fuchsia::web::MessagePortPtr ack_message_port;
fuchsia::web::WebMessage msg;
fuchsia::web::OutgoingTransferable outgoing;
outgoing.set_message_port(ack_message_port.NewRequest());
std::vector<fuchsia::web::OutgoingTransferable> outgoing_vector;
outgoing_vector.push_back(std::move(outgoing));
msg.set_outgoing_transfer(std::move(outgoing_vector));
msg.set_data(cr_fuchsia::MemBufferFromString("hi", "test"));
// Quit the runloop only after we've received a WebMessage AND a PostMessage
// result.
cr_fuchsia::ResultReceiver<fuchsia::web::Frame_PostMessage_Result>
post_result;
frame->PostMessage(
"*", std::move(msg),
cr_fuchsia::CallbackToFitFunction(post_result.GetReceiveCallback()));
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::WebMessage> receiver(
run_loop.QuitClosure());
ack_message_port->ReceiveMessage(
cr_fuchsia::CallbackToFitFunction(receiver.GetReceiveCallback()));
run_loop.Run();
ASSERT_TRUE(receiver->has_data());
EXPECT_EQ("got_port", StringFromMemBufferOrDie(receiver->data()));
EXPECT_TRUE(post_result->is_response());
}
// Pull the three 'ack ping's from the buffer.
for (int i = 0; i < 3; ++i) {
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::WebMessage> receiver(
run_loop.QuitClosure());
incoming_message_port->ReceiveMessage(
cr_fuchsia::CallbackToFitFunction(receiver.GetReceiveCallback()));
run_loop.Run();
ASSERT_TRUE(receiver->has_data());
EXPECT_EQ("ack ping", StringFromMemBufferOrDie(receiver->data()));
}
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessageBadOriginDropped) {
fuchsia::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(),
post_message_url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(post_message_url,
"messageport");
fuchsia::web::MessagePortPtr bad_origin_incoming_message_port;
fuchsia::web::WebMessage msg;
// PostMessage() to invalid origins should be ignored. We pass in a
// MessagePort but nothing should happen to it.
fuchsia::web::MessagePortPtr unused_message_port;
fuchsia::web::OutgoingTransferable unused_outgoing;
unused_outgoing.set_message_port(unused_message_port.NewRequest());
std::vector<fuchsia::web::OutgoingTransferable> unused_outgoing_vector;
unused_outgoing_vector.push_back(std::move(unused_outgoing));
msg.set_outgoing_transfer(std::move(unused_outgoing_vector));
msg.set_data(cr_fuchsia::MemBufferFromString("bad origin, bad!", "test"));
cr_fuchsia::ResultReceiver<fuchsia::web::Frame_PostMessage_Result>
unused_post_result;
frame->PostMessage("https://example.com", std::move(msg),
cr_fuchsia::CallbackToFitFunction(
unused_post_result.GetReceiveCallback()));
cr_fuchsia::ResultReceiver<fuchsia::web::WebMessage> unused_message_read;
bad_origin_incoming_message_port->ReceiveMessage(
cr_fuchsia::CallbackToFitFunction(
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.
fuchsia::web::MessagePortPtr incoming_message_port;
fuchsia::web::MessagePortPtr message_port;
fuchsia::web::OutgoingTransferable outgoing;
outgoing.set_message_port(message_port.NewRequest());
std::vector<fuchsia::web::OutgoingTransferable> outgoing_vector;
outgoing_vector.push_back(std::move(outgoing));
msg.set_outgoing_transfer(std::move(outgoing_vector));
msg.set_data(cr_fuchsia::MemBufferFromString("good origin", "test"));
cr_fuchsia::ResultReceiver<fuchsia::web::Frame_PostMessage_Result>
post_result;
frame->PostMessage(
"*", std::move(msg),
cr_fuchsia::CallbackToFitFunction(post_result.GetReceiveCallback()));
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::WebMessage> receiver(
run_loop.QuitClosure());
message_port->ReceiveMessage(
cr_fuchsia::CallbackToFitFunction(receiver.GetReceiveCallback()));
run_loop.Run();
ASSERT_TRUE(receiver->has_data());
EXPECT_EQ("got_port", StringFromMemBufferOrDie(receiver->data()));
ASSERT_TRUE(receiver->has_incoming_transfer());
ASSERT_EQ(receiver->incoming_transfer().size(), 1u);
incoming_message_port =
receiver->mutable_incoming_transfer()->at(0).message_port().Bind();
EXPECT_TRUE(post_result->is_response());
// Verify that the first PostMessage() call wasn't handled.
EXPECT_FALSE(unused_message_read.has_value());
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, RecreateView) {
fuchsia::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
// Process the Frame creation request, and verify we can get the FrameImpl.
base::RunLoop().RunUntilIdle();
FrameImpl* frame_impl = context_impl()->GetFrameImplForTest(&frame);
ASSERT_TRUE(frame_impl);
EXPECT_FALSE(frame_impl->has_view_for_test());
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
// Verify that the Frame can navigate, prior to the View being created.
const GURL page1_url(embedded_test_server()->GetURL(kPage1Path));
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), page1_url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(page1_url, kPage1Title);
// Request a View from the Frame, and pump the loop to process the request.
zx::eventpair owner_token, frame_token;
ASSERT_EQ(zx::eventpair::create(0, &owner_token, &frame_token), ZX_OK);
fuchsia::ui::views::ViewToken view_token;
view_token.value = std::move(frame_token);
frame->CreateView(std::move(view_token));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(frame_impl->has_view_for_test());
// Verify that the Frame still works, by navigating to Page #2.
const GURL page2_url(embedded_test_server()->GetURL(kPage2Path));
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), page2_url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(page2_url, kPage2Title);
// Create new View tokens and request a new view.
zx::eventpair owner_token2, frame_token2;
ASSERT_EQ(zx::eventpair::create(0, &owner_token2, &frame_token2), ZX_OK);
fuchsia::ui::views::ViewToken view_token2;
view_token2.value = std::move(frame_token2);
frame->CreateView(std::move(view_token2));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(frame_impl->has_view_for_test());
// Verify that the Frame still works, by navigating back to Page #1.
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), page1_url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(page1_url, kPage1Title);
}
// Tests SetNavigationEventListener() immediately returns a NavigationEvent,
// even in the absence of a new navigation.
IN_PROC_BROWSER_TEST_F(FrameImplTest, ImmediateNavigationEvent) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL page_url(embedded_test_server()->GetURL(kPage1Path));
// The first NavigationState received should be empty.
base::RunLoop run_loop;
navigation_listener_.SetBeforeAckHook(base::BindRepeating(
[](base::RunLoop* run_loop, const fuchsia::web::NavigationState& change,
OnNavigationStateChangedCallback callback) {
EXPECT_TRUE(change.IsEmpty());
run_loop->Quit();
callback();
},
base::Unretained(&run_loop)));
fuchsia::web::FramePtr frame = CreateFrame();
run_loop.Run();
navigation_listener_.SetBeforeAckHook({});
// Navigate to a page and wait for the navigation to complete.
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), page_url.spec()));
navigation_listener_.RunUntilUrlEquals(page_url);
// Attach a new navigation listener, we should get the new page state, even if
// no new navigation occurred.
cr_fuchsia::TestNavigationListener navigation_listener2;
fidl::Binding<fuchsia::web::NavigationEventListener>
navigation_listener_binding(&navigation_listener2);
frame->SetNavigationEventListener(navigation_listener_binding.NewBinding());
navigation_listener2.RunUntilUrlAndTitleEquals(page_url, kPage1Title);
}
// Check loading an invalid URL in NavigationController.LoadUrl() sets the right
// error.
IN_PROC_BROWSER_TEST_F(FrameImplTest, InvalidUrl) {
fuchsia::web::FramePtr frame = CreateFrame();
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<fuchsia::web::NavigationController_LoadUrl_Result>
result(run_loop.QuitClosure());
controller->LoadUrl(
"http:google.com:foo", fuchsia::web::LoadUrlParams(),
cr_fuchsia::CallbackToFitFunction(result.GetReceiveCallback()));
run_loop.Run();
ASSERT_TRUE(result->is_err());
EXPECT_EQ(result->err(),
fuchsia::web::NavigationControllerError::INVALID_URL);
}
// Check setting invalid headers in NavigationController.LoadUrl() sets the
// right error.
IN_PROC_BROWSER_TEST_F(FrameImplTest, InvalidHeader) {
fuchsia::web::FramePtr frame = CreateFrame();
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
{
// Set an invalid header name.
fuchsia::web::LoadUrlParams load_url_params;
fuchsia::net::http::Header header;
header.name = cr_fuchsia::StringToBytes("Invalid:Header");
header.value = cr_fuchsia::StringToBytes("1");
load_url_params.set_headers({header});
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<
fuchsia::web::NavigationController_LoadUrl_Result>
result(run_loop.QuitClosure());
controller->LoadUrl(
"http://site.ext/", std::move(load_url_params),
cr_fuchsia::CallbackToFitFunction(result.GetReceiveCallback()));
run_loop.Run();
ASSERT_TRUE(result->is_err());
EXPECT_EQ(result->err(),
fuchsia::web::NavigationControllerError::INVALID_HEADER);
}
{
// Set an invalid header value.
fuchsia::web::LoadUrlParams load_url_params;
fuchsia::net::http::Header header;
header.name = cr_fuchsia::StringToBytes("Header");
header.value = cr_fuchsia::StringToBytes("Invalid\rValue");
load_url_params.set_headers({header});
base::RunLoop run_loop;
cr_fuchsia::ResultReceiver<
fuchsia::web::NavigationController_LoadUrl_Result>
result(run_loop.QuitClosure());
controller->LoadUrl(
"http://site.ext/", std::move(load_url_params),
cr_fuchsia::CallbackToFitFunction(result.GetReceiveCallback()));
run_loop.Run();
ASSERT_TRUE(result->is_err());
EXPECT_EQ(result->err(),
fuchsia::web::NavigationControllerError::INVALID_HEADER);
}
}
class RequestMonitoringFrameImplBrowserTest : public FrameImplTest {
public:
RequestMonitoringFrameImplBrowserTest() = default;
protected:
void SetUpOnMainThread() override {
// Accumulate all http requests made to |embedded_test_server| into
// |accumulated_requests_| container.
embedded_test_server()->RegisterRequestMonitor(base::BindRepeating(
&RequestMonitoringFrameImplBrowserTest::MonitorRequestOnIoThread,
base::Unretained(this), base::SequencedTaskRunnerHandle::Get()));
ASSERT_TRUE(embedded_test_server()->Start());
}
void TearDown() override {
EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
}
std::map<GURL, net::test_server::HttpRequest> accumulated_requests_;
private:
void MonitorRequestOnIoThread(
const scoped_refptr<base::SequencedTaskRunner>& main_thread_task_runner,
const net::test_server::HttpRequest& request) {
main_thread_task_runner->PostTask(
FROM_HERE,
base::BindOnce(
&RequestMonitoringFrameImplBrowserTest::MonitorRequestOnMainThread,
base::Unretained(this), request));
}
void MonitorRequestOnMainThread(
const net::test_server::HttpRequest& request) {
accumulated_requests_[request.GetURL()] = request;
}
DISALLOW_COPY_AND_ASSIGN(RequestMonitoringFrameImplBrowserTest);
};
IN_PROC_BROWSER_TEST_F(RequestMonitoringFrameImplBrowserTest, ExtraHeaders) {
fuchsia::web::FramePtr frame = CreateFrame();
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
const GURL page_url(embedded_test_server()->GetURL(kPage1Path));
fuchsia::web::LoadUrlParams load_url_params;
fuchsia::net::http::Header header1;
header1.name = cr_fuchsia::StringToBytes("X-ExtraHeaders");
header1.value = cr_fuchsia::StringToBytes("1");
fuchsia::net::http::Header header2;
header2.name = cr_fuchsia::StringToBytes("X-2ExtraHeaders");
header2.value = cr_fuchsia::StringToBytes("2");
load_url_params.set_headers({header1, header2});
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), std::move(load_url_params), page_url.spec()));
navigation_listener_.RunUntilUrlAndTitleEquals(page_url, kPage1Title);
// At this point, the page should be loaded, the server should have received
// the request and the request should be in the map.
const auto iter = accumulated_requests_.find(page_url);
ASSERT_NE(iter, accumulated_requests_.end());
EXPECT_THAT(iter->second.headers,
testing::Contains(testing::Key("X-ExtraHeaders")));
EXPECT_THAT(iter->second.headers,
testing::Contains(testing::Key("X-2ExtraHeaders")));
}
// Tests the URLRequestRewrite API properly adds headers on every requests.
IN_PROC_BROWSER_TEST_F(RequestMonitoringFrameImplBrowserTest,
UrlRequestRewriteAddHeaders) {
fuchsia::web::FramePtr frame = CreateFrame();
std::vector<fuchsia::web::UrlRequestRewrite> rewrites;
rewrites.push_back(cr_fuchsia::CreateRewriteAddHeaders("Test", "Value"));
fuchsia::web::UrlRequestRewriteRule rule;
rule.set_rewrites(std::move(rewrites));
std::vector<fuchsia::web::UrlRequestRewriteRule> rules;
rules.push_back(std::move(rule));
frame->SetUrlRequestRewriteRules(std::move(rules), []() {});
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
// Navigate, we should get the additional header on the main request and the
// image request.
const GURL page_url(embedded_test_server()->GetURL(kPage4Path));
const GURL img_url(embedded_test_server()->GetURL(kPage4ImgPath));
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), page_url.spec()));
navigation_listener_.RunUntilUrlEquals(page_url);
{
const auto iter = accumulated_requests_.find(page_url);
ASSERT_NE(iter, accumulated_requests_.end());
EXPECT_THAT(iter->second.headers, testing::Contains(testing::Key("Test")));
}
{
const auto iter = accumulated_requests_.find(img_url);
ASSERT_NE(iter, accumulated_requests_.end());
EXPECT_THAT(iter->second.headers, testing::Contains(testing::Key("Test")));
}
}
// Tests the URLRequestRewrite API properly removes headers on every requests.
// Also tests that rewrites are applied properly in succession.
IN_PROC_BROWSER_TEST_F(RequestMonitoringFrameImplBrowserTest,
UrlRequestRewriteRemoveHeader) {
fuchsia::web::FramePtr frame = CreateFrame();
std::vector<fuchsia::web::UrlRequestRewrite> rewrites;
rewrites.push_back(cr_fuchsia::CreateRewriteAddHeaders("Test", "Value"));
rewrites.push_back(
cr_fuchsia::CreateRewriteRemoveHeader(base::nullopt, "Test"));
fuchsia::web::UrlRequestRewriteRule rule;
rule.set_rewrites(std::move(rewrites));
std::vector<fuchsia::web::UrlRequestRewriteRule> rules;
rules.push_back(std::move(rule));
frame->SetUrlRequestRewriteRules(std::move(rules), []() {});
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
// Navigate, we should get no "Test" header.
const GURL page_url(embedded_test_server()->GetURL(kPage4Path));
const GURL img_url(embedded_test_server()->GetURL(kPage4ImgPath));
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), page_url.spec()));
navigation_listener_.RunUntilUrlEquals(page_url);
{
const auto iter = accumulated_requests_.find(page_url);
ASSERT_NE(iter, accumulated_requests_.end());
EXPECT_THAT(iter->second.headers,
testing::Not(testing::Contains(testing::Key("Test"))));
}
{
const auto iter = accumulated_requests_.find(img_url);
ASSERT_NE(iter, accumulated_requests_.end());
EXPECT_THAT(iter->second.headers,
testing::Not(testing::Contains(testing::Key("Test"))));
}
}
// Tests the URLRequestRewrite API properly removes headers, based on the
// presence of a string in the query.
IN_PROC_BROWSER_TEST_F(RequestMonitoringFrameImplBrowserTest,
UrlRequestRewriteRemoveHeaderWithQuery) {
fuchsia::web::FramePtr frame = CreateFrame();
const GURL page_url(embedded_test_server()->GetURL("/page?stuff=[pattern]"));
std::vector<fuchsia::web::UrlRequestRewrite> rewrites;
rewrites.push_back(cr_fuchsia::CreateRewriteAddHeaders("Test", "Value"));
rewrites.push_back(cr_fuchsia::CreateRewriteRemoveHeader(
base::make_optional("[pattern]"), "Test"));
fuchsia::web::UrlRequestRewriteRule rule;
rule.set_rewrites(std::move(rewrites));
std::vector<fuchsia::web::UrlRequestRewriteRule> rules;
rules.push_back(std::move(rule));
frame->SetUrlRequestRewriteRules(std::move(rules), []() {});
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
// Navigate, we should get no "Test" header.
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), page_url.spec()));
navigation_listener_.RunUntilUrlEquals(page_url);
const auto iter = accumulated_requests_.find(page_url);
ASSERT_NE(iter, accumulated_requests_.end());
EXPECT_THAT(iter->second.headers,
testing::Not(testing::Contains(testing::Key("Test"))));
}
// Tests the URLRequestRewrite API properly handles query substitution.
IN_PROC_BROWSER_TEST_F(RequestMonitoringFrameImplBrowserTest,
UrlRequestRewriteSubstituteQueryPattern) {
fuchsia::web::FramePtr frame = CreateFrame();
std::vector<fuchsia::web::UrlRequestRewrite> rewrites;
rewrites.push_back(cr_fuchsia::CreateRewriteSubstituteQueryPattern(
"[pattern]", "substitution"));
fuchsia::web::UrlRequestRewriteRule rule;
rule.set_rewrites(std::move(rewrites));
std::vector<fuchsia::web::UrlRequestRewriteRule> rules;
rules.push_back(std::move(rule));
frame->SetUrlRequestRewriteRules(std::move(rules), []() {});
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
// Navigate, we should get to the URL with the modified request.
const GURL page_url(embedded_test_server()->GetURL("/page?[pattern]"));
const GURL final_url(embedded_test_server()->GetURL("/page?substitution"));
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), page_url.spec()));
navigation_listener_.RunUntilUrlEquals(final_url);
EXPECT_THAT(accumulated_requests_,
testing::Contains(testing::Key(final_url)));
}
// Tests the URLRequestRewrite API properly handles URL replacement.
IN_PROC_BROWSER_TEST_F(RequestMonitoringFrameImplBrowserTest,
UrlRequestRewriteReplaceUrl) {
fuchsia::web::FramePtr frame = CreateFrame();
const GURL page_url(embedded_test_server()->GetURL(kPage1Path));
const GURL final_url(embedded_test_server()->GetURL(kPage2Path));
std::vector<fuchsia::web::UrlRequestRewrite> rewrites;
rewrites.push_back(
cr_fuchsia::CreateRewriteReplaceUrl(kPage1Path, final_url.spec()));
fuchsia::web::UrlRequestRewriteRule rule;
rule.set_rewrites(std::move(rewrites));
std::vector<fuchsia::web::UrlRequestRewriteRule> rules;
rules.push_back(std::move(rule));
frame->SetUrlRequestRewriteRules(std::move(rules), []() {});
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
// Navigate, we should get to the replaced URL.
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), page_url.spec()));
navigation_listener_.RunUntilUrlEquals(final_url);
EXPECT_THAT(accumulated_requests_,
testing::Contains(testing::Key(final_url)));
}
// Tests the URLRequestRewrite API properly handles URL replacement when the
// original request URL contains a query and a fragment string.
IN_PROC_BROWSER_TEST_F(RequestMonitoringFrameImplBrowserTest,
UrlRequestRewriteReplaceUrlQueryRef) {
fuchsia::web::FramePtr frame = CreateFrame();
const GURL page_url(
embedded_test_server()->GetURL(std::string(kPage1Path) + "?query#ref"));
const GURL replacement_url(embedded_test_server()->GetURL(kPage2Path));
const GURL final_url_with_ref(
embedded_test_server()->GetURL(std::string(kPage2Path) + "?query#ref"));
const GURL final_url(
embedded_test_server()->GetURL(std::string(kPage2Path) + "?query"));
std::vector<fuchsia::web::UrlRequestRewrite> rewrites;
rewrites.push_back(
cr_fuchsia::CreateRewriteReplaceUrl(kPage1Path, replacement_url.spec()));
fuchsia::web::UrlRequestRewriteRule rule;
rule.set_rewrites(std::move(rewrites));
std::vector<fuchsia::web::UrlRequestRewriteRule> rules;
rules.push_back(std::move(rule));
frame->SetUrlRequestRewriteRules(std::move(rules), []() {});
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
// Navigate, we should get to the replaced URL.
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), page_url.spec()));
navigation_listener_.RunUntilUrlEquals(final_url_with_ref);
EXPECT_THAT(accumulated_requests_,
testing::Contains(testing::Key(final_url)));
}
// Tests the URLRequestRewrite API properly handles scheme and host filtering in
// rules.
IN_PROC_BROWSER_TEST_F(RequestMonitoringFrameImplBrowserTest,
UrlRequestRewriteSchemeHostFilter) {
fuchsia::web::FramePtr frame = CreateFrame();
std::vector<fuchsia::web::UrlRequestRewrite> rewrites1;
rewrites1.push_back(cr_fuchsia::CreateRewriteAddHeaders("Test1", "Value"));
fuchsia::web::UrlRequestRewriteRule rule1;
rule1.set_rewrites(std::move(rewrites1));
rule1.set_hosts_filter({"127.0.0.1"});
std::vector<fuchsia::web::UrlRequestRewrite> rewrites2;
rewrites2.push_back(cr_fuchsia::CreateRewriteAddHeaders("Test2", "Value"));
fuchsia::web::UrlRequestRewriteRule rule2;
rule2.set_rewrites(std::move(rewrites2));
rule2.set_hosts_filter({"test.xyz"});
std::vector<fuchsia::web::UrlRequestRewrite> rewrites3;
rewrites3.push_back(cr_fuchsia::CreateRewriteAddHeaders("Test3", "Value"));
fuchsia::web::UrlRequestRewriteRule rule3;
rule3.set_rewrites(std::move(rewrites3));
rule3.set_schemes_filter({"http"});
std::vector<fuchsia::web::UrlRequestRewrite> rewrites4;
rewrites4.push_back(cr_fuchsia::CreateRewriteAddHeaders("Test4", "Value"));
fuchsia::web::UrlRequestRewriteRule rule4;
rule4.set_rewrites(std::move(rewrites4));
rule4.set_schemes_filter({"https"});
std::vector<fuchsia::web::UrlRequestRewriteRule> rules;
rules.push_back(std::move(rule1));
rules.push_back(std::move(rule2));
rules.push_back(std::move(rule3));
rules.push_back(std::move(rule4));
frame->SetUrlRequestRewriteRules(std::move(rules), []() {});
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
// Navigate, we should get the "Test1" and "Test3" headers, but not "Test2"
// and "Test4".
const GURL page_url(embedded_test_server()->GetURL("/default"));
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), page_url.spec()));
navigation_listener_.RunUntilUrlEquals(page_url);
const auto iter = accumulated_requests_.find(page_url);
ASSERT_NE(iter, accumulated_requests_.end());
EXPECT_THAT(iter->second.headers, testing::Contains(testing::Key("Test1")));
EXPECT_THAT(iter->second.headers, testing::Contains(testing::Key("Test3")));
EXPECT_THAT(iter->second.headers,
testing::Not(testing::Contains(testing::Key("Test2"))));
EXPECT_THAT(iter->second.headers,
testing::Not(testing::Contains(testing::Key("Test4"))));
}
// Tests the URLRequestRewrite API properly closes the Frame channel if the
// rules are invalid.
IN_PROC_BROWSER_TEST_F(RequestMonitoringFrameImplBrowserTest,
UrlRequestRewriteInvalidRules) {
fuchsia::web::FramePtr frame = CreateFrame();
base::RunLoop run_loop;
frame.set_error_handler([&run_loop](zx_status_t status) {
EXPECT_EQ(status, ZX_ERR_INVALID_ARGS);
run_loop.Quit();
});
std::vector<fuchsia::web::UrlRequestRewrite> rewrites;
rewrites.push_back(cr_fuchsia::CreateRewriteAddHeaders("Te\nst1", "Value"));
fuchsia::web::UrlRequestRewriteRule rule;
rule.set_rewrites(std::move(rewrites));
std::vector<fuchsia::web::UrlRequestRewriteRule> rules;
rules.push_back(std::move(rule));
frame->SetUrlRequestRewriteRules(std::move(rules), []() {});
run_loop.Run();
}
class TestPopupListener : public fuchsia::web::PopupFrameCreationListener {
public:
TestPopupListener() = default;
~TestPopupListener() override = default;
void GetAndAckNextPopup(fuchsia::web::FramePtr* frame,
fuchsia::web::PopupFrameCreationInfo* creation_info) {
if (!frame_) {
base::RunLoop run_loop;
received_popup_callback_ = run_loop.QuitClosure();
run_loop.Run();
}
*frame = frame_.Bind();
*creation_info = std::move(creation_info_);
popup_ack_callback_();
popup_ack_callback_ = {};
}
private:
void OnPopupFrameCreated(fidl::InterfaceHandle<fuchsia::web::Frame> frame,
fuchsia::web::PopupFrameCreationInfo creation_info,
OnPopupFrameCreatedCallback callback) override {
creation_info_ = std::move(creation_info);
frame_ = std::move(frame);
popup_ack_callback_ = std::move(callback);
if (received_popup_callback_)
std::move(received_popup_callback_).Run();
}
fidl::InterfaceHandle<fuchsia::web::Frame> frame_;
fuchsia::web::PopupFrameCreationInfo creation_info_;
base::OnceClosure received_popup_callback_;
OnPopupFrameCreatedCallback popup_ack_callback_;
};
IN_PROC_BROWSER_TEST_F(FrameImplTest, PopupWindow) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL popup_url(embedded_test_server()->GetURL(kPopupPath));
GURL popup_child_url(embedded_test_server()->GetURL(kPopupRedirectPath));
GURL title1_url(embedded_test_server()->GetURL(kPage1Path));
fuchsia::web::FramePtr frame = CreateFrame();
TestPopupListener popup_listener;
fidl::Binding<fuchsia::web::PopupFrameCreationListener>
popup_listener_binding(&popup_listener);
frame->SetPopupFrameCreationListener(popup_listener_binding.NewBinding());
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(controller.get(), {},
popup_url.spec()));
// Verify the popup's initial URL, "popup_child.html".
fuchsia::web::FramePtr popup_frame;
fuchsia::web::PopupFrameCreationInfo popup_info;
popup_listener.GetAndAckNextPopup(&popup_frame, &popup_info);
EXPECT_EQ(popup_info.initial_url(), popup_child_url);
// Verify that the popup eventually redirects to "title1.html".
cr_fuchsia::TestNavigationListener popup_nav_listener;
fidl::Binding<fuchsia::web::NavigationEventListener>
popup_nav_listener_binding(&popup_nav_listener);
popup_frame->SetNavigationEventListener(
popup_nav_listener_binding.NewBinding());
popup_nav_listener.RunUntilUrlAndTitleEquals(title1_url, kPage1Title);
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, MultiplePopups) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL popup_url(embedded_test_server()->GetURL(kPopupMultiplePath));
GURL title1_url(embedded_test_server()->GetURL(kPage1Path));
GURL title2_url(embedded_test_server()->GetURL(kPage2Path));
fuchsia::web::FramePtr frame = CreateFrame();
TestPopupListener popup_listener;
fidl::Binding<fuchsia::web::PopupFrameCreationListener>
popup_listener_binding(&popup_listener);
frame->SetPopupFrameCreationListener(popup_listener_binding.NewBinding());
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(controller.get(), {},
popup_url.spec()));
fuchsia::web::FramePtr popup_frame;
fuchsia::web::PopupFrameCreationInfo popup_info;
popup_listener.GetAndAckNextPopup(&popup_frame, &popup_info);
EXPECT_EQ(popup_info.initial_url(), title1_url);
popup_listener.GetAndAckNextPopup(&popup_frame, &popup_info);
EXPECT_EQ(popup_info.initial_url(), title2_url);
}