blob: 01e64929808e3b2c1fafa27fb419891eac46de7e [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 "content/browser/frame_host/navigation_throttle_runner.h"
#include "base/bind.h"
#include "base/macros.h"
#include "base/optional.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/mock_navigation_handle.h"
#include "content/public/test/test_navigation_throttle.h"
#include "content/public/test/test_renderer_host.h"
namespace content {
// Test version of a NavigationThrottle that will execute a callback when
// called.
class DeletingNavigationThrottle : public NavigationThrottle {
public:
DeletingNavigationThrottle(NavigationHandle* handle,
const base::RepeatingClosure& deletion_callback)
: NavigationThrottle(handle), deletion_callback_(deletion_callback) {}
~DeletingNavigationThrottle() override {}
NavigationThrottle::ThrottleCheckResult WillStartRequest() override {
deletion_callback_.Run();
return NavigationThrottle::PROCEED;
}
NavigationThrottle::ThrottleCheckResult WillRedirectRequest() override {
deletion_callback_.Run();
return NavigationThrottle::PROCEED;
}
NavigationThrottle::ThrottleCheckResult WillFailRequest() override {
deletion_callback_.Run();
return NavigationThrottle::PROCEED;
}
NavigationThrottle::ThrottleCheckResult WillProcessResponse() override {
deletion_callback_.Run();
return NavigationThrottle::PROCEED;
}
const char* GetNameForLogging() override {
return "DeletingNavigationThrottle";
}
private:
base::RepeatingClosure deletion_callback_;
};
class NavigationThrottleRunnerTest : public RenderViewHostTestHarness,
public NavigationThrottleRunner::Delegate {
public:
NavigationThrottleRunnerTest()
: delegate_result_(NavigationThrottle::DEFER) {}
void SetUp() override {
RenderViewHostTestHarness::SetUp();
runner_ = std::make_unique<NavigationThrottleRunner>(this, &handle_);
}
void Resume() { runner_->CallResumeForTesting(); }
void SimulateEvent(NavigationThrottleRunner::Event event) {
was_delegate_notified_ = false;
delegate_result_ = NavigationThrottle::DEFER;
observer_last_event_ = NavigationThrottleRunner::Event::NoEvent;
runner_->ProcessNavigationEvent(event);
}
// Whether the callback was called.
bool was_delegate_notified() const { return was_delegate_notified_; }
// Returns the delegate_result.
NavigationThrottle::ThrottleCheckResult delegate_result() const {
return delegate_result_;
}
NavigationThrottleRunner::Event observer_last_event() const {
return observer_last_event_;
}
bool is_deferring() { return runner_->GetDeferringThrottle() != nullptr; }
NavigationThrottleRunner* runner() { return runner_.get(); }
void CheckNotNotified(TestNavigationThrottle* throttle) {
CHECK_EQ(
0, throttle->GetCallCount(TestNavigationThrottle::WILL_START_REQUEST));
CHECK_EQ(0, throttle->GetCallCount(
TestNavigationThrottle::WILL_REDIRECT_REQUEST));
CHECK_EQ(0,
throttle->GetCallCount(TestNavigationThrottle::WILL_FAIL_REQUEST));
CHECK_EQ(0, throttle->GetCallCount(
TestNavigationThrottle::WILL_PROCESS_RESPONSE));
}
void CheckNotifiedOfEvent(TestNavigationThrottle* throttle,
NavigationThrottleRunner::Event event) {
if (event == NavigationThrottleRunner::Event::WillStartRequest) {
CHECK_EQ(1, throttle->GetCallCount(
TestNavigationThrottle::WILL_START_REQUEST));
} else {
CHECK_EQ(0, throttle->GetCallCount(
TestNavigationThrottle::WILL_START_REQUEST));
}
if (event == NavigationThrottleRunner::Event::WillRedirectRequest) {
CHECK_EQ(1, throttle->GetCallCount(
TestNavigationThrottle::WILL_REDIRECT_REQUEST));
} else {
CHECK_EQ(0, throttle->GetCallCount(
TestNavigationThrottle::WILL_REDIRECT_REQUEST));
}
if (event == NavigationThrottleRunner::Event::WillFailRequest) {
CHECK_EQ(
1, throttle->GetCallCount(TestNavigationThrottle::WILL_FAIL_REQUEST));
} else {
CHECK_EQ(
0, throttle->GetCallCount(TestNavigationThrottle::WILL_FAIL_REQUEST));
}
if (event == NavigationThrottleRunner::Event::WillProcessResponse) {
CHECK_EQ(1, throttle->GetCallCount(
TestNavigationThrottle::WILL_PROCESS_RESPONSE));
} else {
CHECK_EQ(0, throttle->GetCallCount(
TestNavigationThrottle::WILL_PROCESS_RESPONSE));
}
}
// Creates, register and returns a TestNavigationThrottle that will
// synchronously return |result| on checks by default.
TestNavigationThrottle* CreateTestNavigationThrottle(
NavigationThrottle::ThrottleCheckResult result) {
TestNavigationThrottle* test_throttle =
new TestNavigationThrottle(&handle_);
test_throttle->SetResponseForAllMethods(TestNavigationThrottle::SYNCHRONOUS,
result);
runner_->AddThrottle(
std::unique_ptr<TestNavigationThrottle>(test_throttle));
return test_throttle;
}
// Creates, register and returns a TestNavigationThrottle that will
// synchronously return |result| on check for the given |method|, and
// NavigationThrottle::PROCEED otherwise.
TestNavigationThrottle* CreateTestNavigationThrottle(
TestNavigationThrottle::ThrottleMethod method,
NavigationThrottle::ThrottleCheckResult result) {
TestNavigationThrottle* test_throttle =
CreateTestNavigationThrottle(NavigationThrottle::PROCEED);
test_throttle->SetResponse(method, TestNavigationThrottle::SYNCHRONOUS,
result);
return test_throttle;
}
// Creates and register a NavigationThrottle that will delete the
// NavigationHandle in checks.
void AddDeletingNavigationThrottle() {
runner_->AddThrottle(std::make_unique<DeletingNavigationThrottle>(
&handle_,
base::BindRepeating(
&NavigationThrottleRunnerTest::ResetNavigationThrottleRunner,
base::Unretained(this))));
}
private:
// NavigationThrottleRunner::Delegate:
void OnNavigationEventProcessed(
NavigationThrottleRunner::Event event,
NavigationThrottle::ThrottleCheckResult result) override {
DCHECK(!was_delegate_notified_);
delegate_result_ = result;
was_delegate_notified_ = true;
observer_last_event_ = event;
}
void ResetNavigationThrottleRunner() { runner_.reset(); }
std::unique_ptr<NavigationThrottleRunner> runner_;
MockNavigationHandle handle_;
NavigationThrottleRunner::Event observer_last_event_ =
NavigationThrottleRunner::Event::NoEvent;
bool was_delegate_notified_ = false;
NavigationThrottle::ThrottleCheckResult delegate_result_;
};
class NavigationThrottleRunnerTestWithEvent
: public NavigationThrottleRunnerTest,
public testing::WithParamInterface<NavigationThrottleRunner::Event> {
public:
NavigationThrottleRunnerTestWithEvent() : NavigationThrottleRunnerTest() {}
~NavigationThrottleRunnerTestWithEvent() override {}
void SetUp() override {
NavigationThrottleRunnerTest::SetUp();
event_ = GetParam();
}
NavigationThrottleRunner::Event event() const { return event_; }
void CheckNotified(TestNavigationThrottle* throttle) {
CheckNotifiedOfEvent(throttle, event());
}
private:
NavigationThrottleRunner::Event event_;
};
// Checks that a navigation deferred by a NavigationThrottle can be properly
// resumed.
TEST_P(NavigationThrottleRunnerTestWithEvent, ResumeDeferred) {
TestNavigationThrottle* test_throttle =
CreateTestNavigationThrottle(NavigationThrottle::DEFER);
CheckNotNotified(test_throttle);
// Simulate the event. The request should be deferred. The observer
// should not have been notified.
SimulateEvent(event());
CheckNotified(test_throttle);
EXPECT_TRUE(is_deferring());
EXPECT_FALSE(was_delegate_notified());
// Resume the request. It should no longer be deferred and the observer
// should have been notified.
Resume();
CheckNotified(test_throttle);
EXPECT_FALSE(is_deferring());
EXPECT_TRUE(was_delegate_notified());
EXPECT_EQ(NavigationThrottle::PROCEED, delegate_result());
EXPECT_EQ(event(), observer_last_event());
}
// Checks that a NavigationThrottleRunner can be safely deleted by the execution
// of one of its NavigationThrottle.
TEST_P(NavigationThrottleRunnerTestWithEvent, DeletionByNavigationThrottle) {
AddDeletingNavigationThrottle();
SimulateEvent(event());
EXPECT_EQ(nullptr, runner());
EXPECT_FALSE(was_delegate_notified());
}
// Checks that a NavigationThrottleRunner can be safely deleted by the execution
// of one of its NavigationThrottle following a call to
// ResumeProcessingNavigationEvent.
TEST_P(NavigationThrottleRunnerTestWithEvent,
DeletionByNavigationThrottleAfterResume) {
CreateTestNavigationThrottle(NavigationThrottle::DEFER);
AddDeletingNavigationThrottle();
SimulateEvent(event());
EXPECT_NE(nullptr, runner());
Resume();
EXPECT_EQ(nullptr, runner());
EXPECT_FALSE(was_delegate_notified());
}
INSTANTIATE_TEST_SUITE_P(
AllEvents,
NavigationThrottleRunnerTestWithEvent,
::testing::Values(NavigationThrottleRunner::Event::WillStartRequest,
NavigationThrottleRunner::Event::WillRedirectRequest,
NavigationThrottleRunner::Event::WillFailRequest,
NavigationThrottleRunner::Event::WillProcessResponse));
class NavigationThrottleRunnerTestWithEventAndAction
: public NavigationThrottleRunnerTest,
public testing::WithParamInterface<
std::tuple<NavigationThrottleRunner::Event,
NavigationThrottle::ThrottleAction>> {
public:
NavigationThrottleRunnerTestWithEventAndAction()
: NavigationThrottleRunnerTest() {}
~NavigationThrottleRunnerTestWithEventAndAction() override {}
void SetUp() override {
NavigationThrottleRunnerTest::SetUp();
std::tie(event_, action_) = GetParam();
}
NavigationThrottleRunner::Event event() const { return event_; }
NavigationThrottle::ThrottleAction action() const { return action_; }
void CheckNotified(TestNavigationThrottle* throttle) {
CheckNotifiedOfEvent(throttle, event());
}
private:
NavigationThrottleRunner::Event event_;
NavigationThrottle::ThrottleAction action_;
};
// Checks that a NavigationThrottle asking during to defer
// followed by another NavigationThrottle behave correctly.
TEST_P(NavigationThrottleRunnerTestWithEventAndAction, DeferThenAction) {
TestNavigationThrottle* defer_throttle =
CreateTestNavigationThrottle(NavigationThrottle::DEFER);
TestNavigationThrottle* test_throttle =
CreateTestNavigationThrottle(action());
CheckNotNotified(defer_throttle);
CheckNotNotified(test_throttle);
// Simulate the event. The request should be deferred. The observer
// should not have been notified. The second throttle should not have been
// notified.
SimulateEvent(event());
CheckNotified(defer_throttle);
CheckNotNotified(test_throttle);
EXPECT_TRUE(is_deferring());
EXPECT_FALSE(was_delegate_notified());
// Resume the request. It should no longer be deferred and the observer
// should have been notified. The second throttle should have been notified.
Resume();
CheckNotified(defer_throttle);
CheckNotified(test_throttle);
EXPECT_FALSE(is_deferring());
EXPECT_TRUE(was_delegate_notified());
EXPECT_EQ(action(), delegate_result().action());
EXPECT_EQ(event(), observer_last_event());
}
// Checks that a NavigationThrottle asking to cancel followed by a
// NavigationThrottle asking to proceed behave correctly. The navigation will
// be stopped directly, and the second throttle will not be called.
TEST_P(NavigationThrottleRunnerTestWithEventAndAction, CancelThenProceed) {
if (action() == NavigationThrottle::PROCEED)
return;
TestNavigationThrottle* test_throttle =
CreateTestNavigationThrottle(action());
TestNavigationThrottle* proceed_throttle =
CreateTestNavigationThrottle(NavigationThrottle::PROCEED);
CheckNotNotified(test_throttle);
CheckNotNotified(proceed_throttle);
// Simulate the event. The request should be not be deferred. The observer
// should have been notified. The second throttle should not have been
// notified.
SimulateEvent(event());
CheckNotified(test_throttle);
CheckNotNotified(proceed_throttle);
EXPECT_FALSE(is_deferring());
EXPECT_TRUE(was_delegate_notified());
EXPECT_EQ(action(), delegate_result().action());
EXPECT_EQ(event(), observer_last_event());
}
// Checks that a NavigationThrottle asking to proceed followed by a
// NavigationThrottle asking to cancel behave correctly.
// Both throttles will be called, and the request will be cancelled.
TEST_P(NavigationThrottleRunnerTestWithEventAndAction, ProceedThenCancel) {
if (action() == NavigationThrottle::PROCEED)
return;
TestNavigationThrottle* proceed_throttle =
CreateTestNavigationThrottle(NavigationThrottle::PROCEED);
TestNavigationThrottle* test_throttle =
CreateTestNavigationThrottle(action());
CheckNotNotified(test_throttle);
CheckNotNotified(proceed_throttle);
// Simulate the event. The request should be not be deferred. The observer
// should have been notified.
SimulateEvent(event());
CheckNotified(proceed_throttle);
CheckNotified(test_throttle);
EXPECT_FALSE(is_deferring());
EXPECT_TRUE(was_delegate_notified());
EXPECT_EQ(action(), delegate_result().action());
EXPECT_EQ(event(), observer_last_event());
}
INSTANTIATE_TEST_SUITE_P(
AllEvents,
NavigationThrottleRunnerTestWithEventAndAction,
::testing::Combine(
::testing::Values(NavigationThrottleRunner::Event::WillStartRequest,
NavigationThrottleRunner::Event::WillRedirectRequest,
NavigationThrottleRunner::Event::WillFailRequest,
NavigationThrottleRunner::Event::WillProcessResponse),
::testing::Values(NavigationThrottle::PROCEED,
NavigationThrottle::CANCEL,
NavigationThrottle::CANCEL_AND_IGNORE,
NavigationThrottle::BLOCK_REQUEST,
NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE,
NavigationThrottle::BLOCK_RESPONSE)));
class NavigationThrottleRunnerTestWithEventAndError
: public NavigationThrottleRunnerTest,
public testing::WithParamInterface<
std::tuple<NavigationThrottleRunner::Event,
net::Error,
base::Optional<std::string>>> {
public:
NavigationThrottleRunnerTestWithEventAndError()
: NavigationThrottleRunnerTest() {}
~NavigationThrottleRunnerTestWithEventAndError() override {}
void SetUp() override {
NavigationThrottleRunnerTest::SetUp();
std::tie(event_, error_, custom_error_page_) = GetParam();
}
NavigationThrottleRunner::Event event() const { return event_; }
net::Error error() const { return error_; }
const base::Optional<std::string>& custom_error_page() const {
return custom_error_page_;
}
void CheckNotified(TestNavigationThrottle* throttle) {
CheckNotifiedOfEvent(throttle, event());
}
private:
NavigationThrottleRunner::Event event_;
net::Error error_;
base::Optional<std::string> custom_error_page_ = base::nullopt;
};
// Checks that the NavigationThrottleRunner correctly propagates a
// ThrottleCheckResult with a custom error page and/or error code to its
// delegate.
TEST_P(NavigationThrottleRunnerTestWithEventAndError, CustomNetError) {
SCOPED_TRACE(::testing::Message()
<< "Event: " << static_cast<int>(event())
<< " Error: " << error() << " Custom error page: "
<< (custom_error_page().has_value() ? custom_error_page().value()
: ""));
NavigationThrottle::ThrottleCheckResult result(NavigationThrottle::CANCEL,
error());
if (custom_error_page().has_value()) {
result = NavigationThrottle::ThrottleCheckResult(
NavigationThrottle::CANCEL, error(), custom_error_page().value());
}
TestNavigationThrottle* test_throttle = CreateTestNavigationThrottle(result);
CheckNotNotified(test_throttle);
// Simulate the event. The request should not be deferred. The
// callback should have been called.
SimulateEvent(event());
EXPECT_FALSE(is_deferring());
EXPECT_TRUE(was_delegate_notified());
EXPECT_EQ(NavigationThrottle::CANCEL, delegate_result().action());
EXPECT_EQ(error(), delegate_result().net_error_code());
EXPECT_EQ(custom_error_page().has_value(),
delegate_result().error_page_content().has_value());
if (custom_error_page().has_value()) {
EXPECT_EQ(custom_error_page().value(),
delegate_result().error_page_content().value());
}
CheckNotified(test_throttle);
}
INSTANTIATE_TEST_SUITE_P(
AllEvents,
NavigationThrottleRunnerTestWithEventAndError,
::testing::Combine(
::testing::Values(NavigationThrottleRunner::Event::WillStartRequest,
NavigationThrottleRunner::Event::WillRedirectRequest,
NavigationThrottleRunner::Event::WillFailRequest,
NavigationThrottleRunner::Event::WillProcessResponse),
::testing::Values(net::ERR_BLOCKED_BY_ADMINISTRATOR, net::ERR_ABORTED),
::testing::Values(base::nullopt, "<html><body>test</body></html>")));
} // namespace content