blob: d25f720f995266525eeb9335294407810642daa4 [file] [log] [blame]
// Copyright 2017 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 "chrome/browser/predictors/preconnect_manager.h"
#include <utility>
#include "base/format_macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/predictors/resource_prefetch_predictor.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::DoAll;
using testing::Mock;
using testing::Return;
using testing::SaveArg;
using testing::StrictMock;
namespace predictors {
class MockPreconnectManagerDelegate
: public PreconnectManager::Delegate,
public base::SupportsWeakPtr<MockPreconnectManagerDelegate> {
public:
// Gmock doesn't support mocking methods with move-only argument types.
virtual void PreconnectFinished(std::unique_ptr<PreconnectStats> stats) {
PreconnectFinishedProxy(stats->url);
}
MOCK_METHOD1(PreconnectFinishedProxy, void(const GURL& url));
};
class MockPreconnectManager : public PreconnectManager {
public:
MockPreconnectManager(
base::WeakPtr<Delegate> delegate,
scoped_refptr<net::URLRequestContextGetter> context_getter);
MOCK_CONST_METHOD4(PreconnectUrl,
void(const GURL& url,
const GURL& site_for_cookies,
int num_sockets,
bool allow_credentials));
MOCK_CONST_METHOD2(PreresolveUrl,
int(const GURL& url,
const net::CompletionCallback& callback));
};
MockPreconnectManager::MockPreconnectManager(
base::WeakPtr<Delegate> delegate,
scoped_refptr<net::URLRequestContextGetter> context_getter)
: PreconnectManager(delegate, context_getter) {}
class PreconnectManagerTest : public testing::Test {
public:
PreconnectManagerTest();
~PreconnectManagerTest() override;
protected:
content::TestBrowserThreadBundle thread_bundle_;
std::unique_ptr<StrictMock<MockPreconnectManagerDelegate>> mock_delegate_;
scoped_refptr<net::URLRequestContextGetter> context_getter_;
std::unique_ptr<StrictMock<MockPreconnectManager>> preconnect_manager_;
private:
DISALLOW_COPY_AND_ASSIGN(PreconnectManagerTest);
};
PreconnectManagerTest::PreconnectManagerTest()
: mock_delegate_(
std::make_unique<StrictMock<MockPreconnectManagerDelegate>>()),
context_getter_(base::MakeRefCounted<net::TestURLRequestContextGetter>(
base::ThreadTaskRunnerHandle::Get())),
preconnect_manager_(std::make_unique<StrictMock<MockPreconnectManager>>(
mock_delegate_->AsWeakPtr(),
context_getter_)) {}
PreconnectManagerTest::~PreconnectManagerTest() = default;
TEST_F(PreconnectManagerTest, TestStartOneUrlPreresolve) {
GURL main_frame_url("http://google.com");
GURL url_to_preresolve("http://cdn.google.com");
EXPECT_CALL(*preconnect_manager_, PreresolveUrl(url_to_preresolve, _))
.WillOnce(Return(net::OK));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
preconnect_manager_->Start(main_frame_url,
{PreconnectRequest(url_to_preresolve, 0)});
// Wait for PreconnectFinished task posted to the UI thread.
base::RunLoop().RunUntilIdle();
}
TEST_F(PreconnectManagerTest, TestStartOneUrlPreconnect) {
GURL main_frame_url("http://google.com");
GURL url_to_preconnect("http://cdn.google.com");
EXPECT_CALL(*preconnect_manager_, PreresolveUrl(url_to_preconnect, _))
.WillOnce(Return(net::OK));
EXPECT_CALL(*preconnect_manager_,
PreconnectUrl(url_to_preconnect, main_frame_url, 1, true));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
preconnect_manager_->Start(main_frame_url,
{PreconnectRequest(url_to_preconnect, 1)});
base::RunLoop().RunUntilIdle();
}
TEST_F(PreconnectManagerTest, TestStopOneUrlBeforePreconnect) {
GURL main_frame_url("http://google.com");
GURL url_to_preconnect("http://cdn.google.com");
net::CompletionCallback callback;
// Preconnect job isn't started before preresolve is completed asynchronously.
EXPECT_CALL(*preconnect_manager_, PreresolveUrl(url_to_preconnect, _))
.WillOnce(DoAll(SaveArg<1>(&callback), Return(net::ERR_IO_PENDING)));
preconnect_manager_->Start(main_frame_url,
{PreconnectRequest(url_to_preconnect, 1)});
// Stop all jobs for |main_frame_url| before we get the callback.
preconnect_manager_->Stop(main_frame_url);
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
callback.Run(net::OK);
base::RunLoop().RunUntilIdle();
}
TEST_F(PreconnectManagerTest, TestGetCallbackAfterDestruction) {
GURL main_frame_url("http://google.com");
GURL url_to_preconnect("http://cdn.google.com");
net::CompletionCallback callback;
EXPECT_CALL(*preconnect_manager_, PreresolveUrl(url_to_preconnect, _))
.WillOnce(DoAll(SaveArg<1>(&callback), Return(net::ERR_IO_PENDING)));
preconnect_manager_->Start(main_frame_url,
{PreconnectRequest(url_to_preconnect, 1)});
// Callback may outlive PreconnectManager but it shouldn't cause a crash.
preconnect_manager_ = nullptr;
callback.Run(net::OK);
base::RunLoop().RunUntilIdle();
}
TEST_F(PreconnectManagerTest, TestUnqueuedPreresolvesCanceled) {
GURL main_frame_url("http://google.com");
size_t count = PreconnectManager::kMaxInflightPreresolves;
std::vector<PreconnectRequest> requests;
// Allocate the space for callbacks at once because we need stable pointers.
std::vector<net::CompletionCallback> callbacks(count);
for (size_t i = 0; i < count; ++i) {
// Exactly PreconnectManager::kMaxInflightPreresolves should be preresolved.
requests.emplace_back(
GURL(base::StringPrintf("http://cdn%" PRIuS ".google.com", i)), 1);
EXPECT_CALL(*preconnect_manager_, PreresolveUrl(requests.back().origin, _))
.WillOnce(
DoAll(SaveArg<1>(&callbacks[i]), Return(net::ERR_IO_PENDING)));
}
// This url shouldn't be preresolved.
requests.emplace_back(GURL("http://no.preresolve.com"), 1);
preconnect_manager_->Start(main_frame_url, std::move(requests));
preconnect_manager_->Stop(main_frame_url);
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
for (auto& callback : callbacks)
callback.Run(net::OK);
base::RunLoop().RunUntilIdle();
}
TEST_F(PreconnectManagerTest, TestTwoConcurrentMainFrameUrls) {
GURL main_frame_url1("http://google.com");
GURL url_to_preconnect1("http://cdn.google.com");
net::CompletionCallback callback1;
GURL main_frame_url2("http://facebook.com");
GURL url_to_preconnect2("http://cdn.facebook.com");
net::CompletionCallback callback2;
EXPECT_CALL(*preconnect_manager_, PreresolveUrl(url_to_preconnect1, _))
.WillOnce(DoAll(SaveArg<1>(&callback1), Return(net::ERR_IO_PENDING)));
EXPECT_CALL(*preconnect_manager_, PreresolveUrl(url_to_preconnect2, _))
.WillOnce(DoAll(SaveArg<1>(&callback2), Return(net::ERR_IO_PENDING)));
preconnect_manager_->Start(main_frame_url1,
{PreconnectRequest(url_to_preconnect1, 1)});
preconnect_manager_->Start(main_frame_url2,
{PreconnectRequest(url_to_preconnect2, 1)});
// Check that the first url didn't block the second one.
Mock::VerifyAndClearExpectations(preconnect_manager_.get());
preconnect_manager_->Stop(main_frame_url2);
// Stopping the second url shouldn't stop the first one.
EXPECT_CALL(*preconnect_manager_,
PreconnectUrl(url_to_preconnect1, main_frame_url1, 1, true));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url1));
callback1.Run(net::OK);
// No preconnect for the second url.
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url2));
callback2.Run(net::OK);
base::RunLoop().RunUntilIdle();
}
// Checks that the PreconnectManager handles no more than one URL per host
// simultaneously.
TEST_F(PreconnectManagerTest, TestTwoConcurrentSameHostMainFrameUrls) {
GURL main_frame_url1("http://google.com/search?query=cats");
GURL url_to_preconnect1("http://cats.google.com");
net::CompletionCallback callback1;
GURL main_frame_url2("http://google.com/search?query=dogs");
GURL url_to_preconnect2("http://dogs.google.com");
EXPECT_CALL(*preconnect_manager_, PreresolveUrl(url_to_preconnect1, _))
.WillOnce(DoAll(SaveArg<1>(&callback1), Return(net::ERR_IO_PENDING)));
preconnect_manager_->Start(main_frame_url1,
{PreconnectRequest(url_to_preconnect1, 1)});
// This suggestion should be dropped because the PreconnectManager already has
// a job for the "google.com" host.
preconnect_manager_->Start(main_frame_url2,
{PreconnectRequest(url_to_preconnect2, 1)});
EXPECT_CALL(*preconnect_manager_,
PreconnectUrl(url_to_preconnect1, main_frame_url1, 1, true));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url1));
callback1.Run(net::OK);
base::RunLoop().RunUntilIdle();
}
TEST_F(PreconnectManagerTest, TestStartPreresolveHost) {
GURL url("http://cdn.google.com/script.js");
GURL origin("http://cdn.google.com");
EXPECT_CALL(*preconnect_manager_, PreresolveUrl(origin, _))
.WillOnce(Return(net::OK));
preconnect_manager_->StartPreresolveHost(url);
// PreconnectFinished shouldn't be called.
base::RunLoop().RunUntilIdle();
// Non http url shouldn't be preresovled.
GURL non_http_url("file:///tmp/index.html");
preconnect_manager_->StartPreresolveHost(non_http_url);
base::RunLoop().RunUntilIdle();
}
TEST_F(PreconnectManagerTest, TestStartPreresolveHosts) {
GURL cdn("http://cdn.google.com");
GURL fonts("http://fonts.google.com");
EXPECT_CALL(*preconnect_manager_, PreresolveUrl(cdn, _))
.WillOnce(Return(net::OK));
EXPECT_CALL(*preconnect_manager_, PreresolveUrl(fonts, _))
.WillOnce(Return(net::OK));
preconnect_manager_->StartPreresolveHosts({cdn.host(), fonts.host()});
base::RunLoop().RunUntilIdle();
}
TEST_F(PreconnectManagerTest, TestStartPreconnectUrl) {
GURL url("http://cdn.google.com/script.js");
GURL origin("http://cdn.google.com");
bool allow_credentials = false;
EXPECT_CALL(*preconnect_manager_, PreresolveUrl(origin, _))
.WillOnce(Return(net::OK));
EXPECT_CALL(*preconnect_manager_,
PreconnectUrl(origin, GURL(), 1, allow_credentials));
preconnect_manager_->StartPreconnectUrl(url, allow_credentials);
base::RunLoop().RunUntilIdle();
// Non http url shouldn't be preconnected.
GURL non_http_url("file:///tmp/index.html");
preconnect_manager_->StartPreconnectUrl(non_http_url, allow_credentials);
base::RunLoop().RunUntilIdle();
}
TEST_F(PreconnectManagerTest, TestDetachedRequestHasHigherPriority) {
GURL main_frame_url("http://google.com");
size_t count = PreconnectManager::kMaxInflightPreresolves;
std::vector<PreconnectRequest> requests;
std::vector<net::CompletionCallback> callbacks(count);
// Create enough asynchronous jobs to leave the last one in the queue.
for (size_t i = 0; i < count; ++i) {
requests.emplace_back(
GURL(base::StringPrintf("http://cdn%" PRIuS ".google.com", i)), 0);
EXPECT_CALL(*preconnect_manager_, PreresolveUrl(requests.back().origin, _))
.WillOnce(
DoAll(SaveArg<1>(&callbacks[i]), Return(net::ERR_IO_PENDING)));
}
// This url will wait in the queue.
GURL queued_url("http://fonts.google.com");
requests.emplace_back(queued_url, 0);
preconnect_manager_->Start(main_frame_url, std::move(requests));
// This url should come to the front of the queue.
GURL detached_preresolve("http://ads.google.com");
preconnect_manager_->StartPreresolveHost(detached_preresolve);
Mock::VerifyAndClearExpectations(preconnect_manager_.get());
net::CompletionCallback detached_callback;
EXPECT_CALL(*preconnect_manager_, PreresolveUrl(detached_preresolve, _))
.WillOnce(
DoAll(SaveArg<1>(&detached_callback), Return(net::ERR_IO_PENDING)));
callbacks[0].Run(net::OK);
Mock::VerifyAndClearExpectations(preconnect_manager_.get());
EXPECT_CALL(*preconnect_manager_, PreresolveUrl(queued_url, _))
.WillOnce(Return(net::OK));
detached_callback.Run(net::OK);
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
for (size_t i = 1; i < count; ++i)
callbacks[i].Run(net::OK);
base::RunLoop().RunUntilIdle();
}
TEST_F(PreconnectManagerTest, TestHSTSRedirectRespectedForPreconnect) {
net::TransportSecurityState transport_security_state;
transport_security_state.AddHSTS(
"google.com", base::Time::Now() + base::TimeDelta::FromDays(1000), false);
context_getter_->GetURLRequestContext()->set_transport_security_state(
&transport_security_state);
GURL url("http://google.com/search");
bool allow_credentials = false;
EXPECT_CALL(*preconnect_manager_, PreresolveUrl(GURL("http://google.com"), _))
.WillOnce(Return(net::OK));
EXPECT_CALL(
*preconnect_manager_,
PreconnectUrl(GURL("https://google.com"), GURL(), 1, allow_credentials));
preconnect_manager_->StartPreconnectUrl(url, allow_credentials);
base::RunLoop().RunUntilIdle();
}
} // namespace predictors