blob: 722c42d04dac9ca883bba1398034dd4b56541b3a [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 <map>
#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/test/metrics/histogram_tester.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/predictors/loading_test_util.h"
#include "chrome/browser/predictors/predictors_features.h"
#include "chrome/browser/predictors/proxy_lookup_client_impl.h"
#include "chrome/browser/predictors/resolve_host_client_impl.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/browser_task_environment.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/load_flags.h"
#include "net/base/network_isolation_key.h"
#include "net/dns/public/resolve_error_info.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/test/test_network_context.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"
using testing::_;
using testing::Mock;
using testing::SaveArg;
using testing::StrictMock;
namespace predictors {
namespace {
net::ProxyInfo GetIndirectProxyInfo() {
net::ProxyInfo proxy_info;
proxy_info.UseNamedProxy("proxy.com");
return proxy_info;
}
net::ProxyInfo GetDirectProxyInfo() {
net::ProxyInfo proxy_info;
proxy_info.UseDirect();
return proxy_info;
}
class MockPreconnectManagerDelegate
: public PreconnectManager::Delegate,
public base::SupportsWeakPtr<MockPreconnectManagerDelegate> {
public:
// Gmock doesn't support mocking methods with move-only argument types.
void PreconnectFinished(std::unique_ptr<PreconnectStats> stats) override {
PreconnectFinishedProxy(stats->url);
}
MOCK_METHOD1(PreconnectFinishedProxy, void(const GURL& url));
MOCK_METHOD2(PreconnectInitiated,
void(const GURL& url, const GURL& preconnect_url));
};
class MockNetworkContext : public network::TestNetworkContext {
public:
MockNetworkContext() = default;
~MockNetworkContext() override {
EXPECT_TRUE(resolve_host_clients_.empty())
<< "Not all resolve host requests were satisfied";
}
void ResolveHost(const net::HostPortPair& host_port,
const net::NetworkIsolationKey& network_isolation_key,
network::mojom::ResolveHostParametersPtr optional_parameters,
mojo::PendingRemote<network::mojom::ResolveHostClient>
response_client) override {
const std::string& host = host_port.host();
EXPECT_FALSE(IsHangingHost(GURL(host)))
<< " Hosts marked as hanging should not be resolved.";
EXPECT_TRUE(resolve_host_clients_
.emplace(ResolveHostClientKey{host, network_isolation_key},
mojo::Remote<network::mojom::ResolveHostClient>(
std::move(response_client)))
.second);
ResolveHostProxy(host);
}
void LookUpProxyForURL(const GURL& url,
const net::NetworkIsolationKey& network_isolation_key,
mojo::PendingRemote<network::mojom::ProxyLookupClient>
proxy_lookup_client) override {
EXPECT_TRUE(
proxy_lookup_clients_.emplace(url, std::move(proxy_lookup_client))
.second);
if (!enabled_proxy_testing_) {
// We don't want to test proxy, return that the proxy is disabled.
CompleteProxyLookup(url, absl::nullopt);
}
}
void CompleteHostLookup(const std::string& host,
const net::NetworkIsolationKey& network_isolation_key,
int result) {
DCHECK(result == net::OK || result == net::ERR_NAME_NOT_RESOLVED);
auto it = resolve_host_clients_.find(
ResolveHostClientKey{host, network_isolation_key});
if (it == resolve_host_clients_.end()) {
ADD_FAILURE() << host << " wasn't found";
return;
}
it->second->OnComplete(result, net::ResolveErrorInfo(result),
absl::nullopt);
resolve_host_clients_.erase(it);
// Wait for OnComplete() to be executed on the UI thread.
base::RunLoop().RunUntilIdle();
}
void CompleteProxyLookup(const GURL& url,
const absl::optional<net::ProxyInfo>& result) {
if (IsHangingHost(url))
return;
auto it = proxy_lookup_clients_.find(url);
if (it == proxy_lookup_clients_.end()) {
ADD_FAILURE() << url.spec() << " wasn't found";
return;
}
it->second->OnProxyLookupComplete(net::ERR_FAILED, result);
proxy_lookup_clients_.erase(it);
// Wait for OnProxyLookupComplete() to be executed on the UI thread.
base::RunLoop().RunUntilIdle();
}
// Preresolve/preconnect requests for all hosts in |hanging_url_requests| is
// never completed.
void SetHangingHostsFromPreconnectRequests(
const std::vector<PreconnectRequest>& hanging_url_requests) {
hanging_hosts_.clear();
for (const auto& request : hanging_url_requests) {
hanging_hosts_.push_back(request.origin.host());
}
}
void EnableProxyTesting() { enabled_proxy_testing_ = true; }
MOCK_METHOD1(ResolveHostProxy, void(const std::string& host));
MOCK_METHOD4(PreconnectSockets,
void(uint32_t num_streams,
const GURL& url,
bool allow_credentials,
const net::NetworkIsolationKey& network_isolation_key));
private:
bool IsHangingHost(const GURL& url) const {
return std::find(hanging_hosts_.begin(), hanging_hosts_.end(),
url.host()) != hanging_hosts_.end();
}
using ResolveHostClientKey = std::pair<std::string, net::NetworkIsolationKey>;
std::map<ResolveHostClientKey,
mojo::Remote<network::mojom::ResolveHostClient>>
resolve_host_clients_;
std::map<GURL, mojo::Remote<network::mojom::ProxyLookupClient>>
proxy_lookup_clients_;
bool enabled_proxy_testing_ = false;
std::vector<std::string> hanging_hosts_;
};
// Creates a NetworkIsolationKey for a main frame navigation to URL.
net::NetworkIsolationKey CreateNetworkIsolationKey(const GURL& main_frame_url) {
url::Origin origin = url::Origin::Create(main_frame_url);
return net::NetworkIsolationKey(origin, origin);
}
} // namespace
class PreconnectManagerTest : public testing::Test {
public:
PreconnectManagerTest();
PreconnectManagerTest(const PreconnectManagerTest&) = delete;
PreconnectManagerTest& operator=(const PreconnectManagerTest&) = delete;
~PreconnectManagerTest() override;
void VerifyAndClearExpectations() const {
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(mock_network_context_.get());
Mock::VerifyAndClearExpectations(mock_delegate_.get());
}
protected:
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<StrictMock<MockNetworkContext>> mock_network_context_;
std::unique_ptr<StrictMock<MockPreconnectManagerDelegate>> mock_delegate_;
std::unique_ptr<PreconnectManager> preconnect_manager_;
};
PreconnectManagerTest::PreconnectManagerTest()
: profile_(std::make_unique<TestingProfile>()),
mock_network_context_(std::make_unique<StrictMock<MockNetworkContext>>()),
mock_delegate_(
std::make_unique<StrictMock<MockPreconnectManagerDelegate>>()),
preconnect_manager_(
std::make_unique<PreconnectManager>(mock_delegate_->AsWeakPtr(),
profile_.get())) {
preconnect_manager_->SetNetworkContextForTesting(mock_network_context_.get());
}
PreconnectManagerTest::~PreconnectManagerTest() {
VerifyAndClearExpectations();
}
TEST_F(PreconnectManagerTest, TestStartOneUrlPreresolve) {
GURL main_frame_url("http://google.com");
url::Origin origin_to_preresolve =
url::Origin::Create(GURL("http://cdn.google.com"));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url, origin_to_preresolve.GetURL()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preresolve.host()));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
preconnect_manager_->Start(
main_frame_url,
{PreconnectRequest(origin_to_preresolve, 0,
CreateNetworkIsolationKey(main_frame_url))});
mock_network_context_->CompleteHostLookup(
origin_to_preresolve.host(), CreateNetworkIsolationKey(main_frame_url),
net::OK);
}
TEST_F(PreconnectManagerTest, TestStartOneUrlPreconnect) {
GURL main_frame_url("http://google.com");
net::NetworkIsolationKey network_isolation_key =
CreateNetworkIsolationKey(main_frame_url);
url::Origin origin_to_preconnect =
url::Origin::Create(GURL("http://cdn.google.com"));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url, origin_to_preconnect.GetURL()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect.host()));
preconnect_manager_->Start(
main_frame_url,
{PreconnectRequest(origin_to_preconnect, 1, network_isolation_key)});
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, origin_to_preconnect.GetURL(),
true /* allow credentials */, network_isolation_key));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
mock_network_context_->CompleteHostLookup(origin_to_preconnect.host(),
network_isolation_key, net::OK);
}
TEST_F(PreconnectManagerTest,
TestStartOneUrlPreconnectWithNetworkIsolationKey) {
GURL main_frame_url("http://google.com");
net::NetworkIsolationKey network_isolation_key =
CreateNetworkIsolationKey(main_frame_url);
url::Origin origin_to_preconnect =
url::Origin::Create(GURL("http://cdn.google.com"));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url, origin_to_preconnect.GetURL()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect.host()));
preconnect_manager_->Start(
main_frame_url,
{PreconnectRequest(origin_to_preconnect, 1, network_isolation_key)});
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, origin_to_preconnect.GetURL(),
true /* allow credentials */, network_isolation_key));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
mock_network_context_->CompleteHostLookup(origin_to_preconnect.host(),
network_isolation_key, net::OK);
}
// Sends preconnect request for a webpage, and stops the request before
// all pertaining preconnect requests finish. Next, preconnect request
// for the same webpage is sent again. Verifies that all the preconnects
// related to the second request are dispatched on the network.
TEST_F(PreconnectManagerTest, TestStartOneUrlPreconnect_MultipleTimes) {
GURL main_frame_url("http://google.com");
net::NetworkIsolationKey network_isolation_key =
CreateNetworkIsolationKey(main_frame_url);
size_t count = features::GetMaxInflightPreresolves();
std::vector<PreconnectRequest> requests;
for (size_t i = 0; i < count + 1; ++i) {
// Exactly features::GetMaxInflightPreresolves() should be preresolved.
std::string url = base::StringPrintf("http://cdn%" PRIuS ".google.com", i);
requests.emplace_back(url::Origin::Create(GURL(url)), 1,
network_isolation_key);
}
for (size_t i = 0; i < count; ++i) {
// Exactly features::GetMaxInflightPreresolves() should be initiated
// and preresolved.
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url, requests[i].origin.GetURL()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(requests[i].origin.host()));
}
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
preconnect_manager_->Start(main_frame_url, requests);
preconnect_manager_->Stop(main_frame_url);
for (size_t i = 0; i < count; ++i) {
mock_network_context_->CompleteHostLookup(requests[i].origin.host(),
network_isolation_key, net::OK);
}
VerifyAndClearExpectations();
// Now, restart the preconnect request.
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url, requests.back().origin.GetURL()));
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, requests.back().origin.GetURL(),
true /* allow credentials */, network_isolation_key));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(requests.back().origin.host()));
for (size_t i = 0; i < count; ++i) {
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url, requests[i].origin.GetURL()));
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, requests[i].origin.GetURL(),
true /* allow credentials */, network_isolation_key));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(requests[i].origin.host()));
}
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
preconnect_manager_->Start(main_frame_url, requests);
for (size_t i = 0; i < count + 1; ++i) {
mock_network_context_->CompleteHostLookup(requests[i].origin.host(),
network_isolation_key, net::OK);
}
}
// Sends preconnect request for two webpages, and stops one request before
// all pertaining preconnect requests finish. Next, preconnect request
// for the same webpage is sent again. Verifies that all the preconnects
// related to the second request are dispatched on the network.
TEST_F(PreconnectManagerTest, TestTwoConcurrentMainFrameUrls_MultipleTimes) {
GURL main_frame_url_1("http://google.com");
net::NetworkIsolationKey network_isolation_key_1 =
CreateNetworkIsolationKey(main_frame_url_1);
size_t count = features::GetMaxInflightPreresolves();
std::vector<PreconnectRequest> requests;
for (size_t i = 0; i < count + 1; ++i) {
std::string url = base::StringPrintf("http://cdn%" PRIuS ".google.com", i);
requests.emplace_back(url::Origin::Create(GURL(url)), 1,
network_isolation_key_1);
}
GURL main_frame_url_2("http://google2.com");
net::NetworkIsolationKey network_isolation_key_2 =
CreateNetworkIsolationKey(main_frame_url_1);
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url_1, requests[0].origin.GetURL()));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url_1, requests[1].origin.GetURL()));
EXPECT_CALL(*mock_delegate_,
PreconnectInitiated(main_frame_url_2,
requests[count - 1].origin.GetURL()));
for (size_t i = 0; i < count; ++i) {
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(requests[i].origin.host()));
}
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url_1));
for (size_t i = 0; i < count - 1; ++i) {
EXPECT_CALL(*mock_network_context_,
PreconnectSockets(1, requests[i].origin.GetURL(),
true /* allow credentials */,
network_isolation_key_1));
}
preconnect_manager_->Start(
main_frame_url_1, std::vector<PreconnectRequest>(
requests.begin(), requests.begin() + count - 1));
preconnect_manager_->Start(main_frame_url_2,
std::vector<PreconnectRequest>(
requests.begin() + count - 1, requests.end()));
preconnect_manager_->Stop(main_frame_url_2);
for (size_t i = 0; i < count - 1; ++i) {
mock_network_context_->CompleteHostLookup(requests[i].origin.host(),
network_isolation_key_1, net::OK);
}
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url_2));
// Preconnect to |requests[count-1].origin| finishes after |main_frame_url_2|
// is stopped. Finishing of |requests[count-1].origin| should cause preconnect
// manager to clear all internal state related to |main_frame_url_2|.
mock_network_context_->CompleteHostLookup(requests[count - 1].origin.host(),
network_isolation_key_1, net::OK);
VerifyAndClearExpectations();
// Now, restart the preconnect request.
EXPECT_CALL(*mock_delegate_,
PreconnectInitiated(main_frame_url_2,
requests[count - 1].origin.GetURL()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(requests[count - 1].origin.host()));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url_2, requests[count].origin.GetURL()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(requests[count].origin.host()));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url_2));
// Since state related to |main_frame_url_2| has been cleared,
// re-issuing a request for connect to |main_frame_url_2| should be
// successful.
preconnect_manager_->Start(main_frame_url_2,
std::vector<PreconnectRequest>(
requests.begin() + count - 1, requests.end()));
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, requests[count - 1].origin.GetURL(),
true /* allow credentials */, network_isolation_key_2));
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, requests[count].origin.GetURL(),
true /* allow credentials */, network_isolation_key_2));
mock_network_context_->CompleteHostLookup(requests[count - 1].origin.host(),
network_isolation_key_2, net::OK);
mock_network_context_->CompleteHostLookup(requests[count].origin.host(),
network_isolation_key_2, net::OK);
}
// Starts preconnect request for two webpages. The preconnect request for the
// second webpage is cancelled after one of its associated preconnect request
// goes in-flight.
// Verifies that if (i) Preconnect for a webpage is cancelled then its state is
// cleared after its associated in-flight requests finish; and, (ii) If the
// preconnect for that webpage is requested again, then
// the pertaining requests are dispatched to the network.
TEST_F(PreconnectManagerTest,
TestStartOneUrlPreconnect_MultipleTimes_CancelledAfterInFlight) {
GURL main_frame_url_1("http://google1.com");
net::NetworkIsolationKey network_isolation_key_1 =
CreateNetworkIsolationKey(main_frame_url_1);
size_t count = features::GetMaxInflightPreresolves();
std::vector<PreconnectRequest> requests;
for (size_t i = 0; i < count - 1; ++i) {
std::string url =
base::StringPrintf("http://hanging.cdn%" PRIuS ".google.com", i);
requests.emplace_back(url::Origin::Create(GURL(url)), 1,
network_isolation_key_1);
// Although it hangs, the requests should still be initiated.
EXPECT_CALL(*mock_delegate_,
PreconnectInitiated(main_frame_url_1, GURL(url)));
}
mock_network_context_->SetHangingHostsFromPreconnectRequests(requests);
// Preconnect requests to |requests| would hang.
preconnect_manager_->Start(main_frame_url_1, requests);
GURL main_frame_url_2("http://google2.com");
net::NetworkIsolationKey network_isolation_key_2 =
CreateNetworkIsolationKey(main_frame_url_2);
url::Origin origin_to_preconnect_1 =
url::Origin::Create(GURL("http://cdn.google1.com"));
url::Origin origin_to_preconnect_2 =
url::Origin::Create(GURL("http://cdn.google2.com"));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url_2, origin_to_preconnect_1.GetURL()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect_1.host()));
// Starting and stopping preconnect request for |main_frame_url_2|
// should still dispatch the request for |origin_to_preconnect_1| on the
// network.
preconnect_manager_->Start(
main_frame_url_2,
{PreconnectRequest(origin_to_preconnect_1, 1, network_isolation_key_2),
PreconnectRequest(origin_to_preconnect_2, 1, network_isolation_key_2)});
// preconnect request for |origin_to_preconnect_1| is still in-flight and
// Stop() is called on the associated webpage.
preconnect_manager_->Stop(main_frame_url_2);
VerifyAndClearExpectations();
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url_2));
mock_network_context_->CompleteHostLookup(origin_to_preconnect_1.host(),
network_isolation_key_2, net::OK);
VerifyAndClearExpectations();
// Request preconnect for |main_frame_url_2| again.
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url_2, origin_to_preconnect_1.GetURL()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect_1.host()));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url_2, origin_to_preconnect_2.GetURL()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect_2.host()));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url_2));
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, origin_to_preconnect_1.GetURL(),
true /* allow credentials */, network_isolation_key_2));
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, origin_to_preconnect_2.GetURL(),
true /* allow credentials */, network_isolation_key_2));
preconnect_manager_->Start(
main_frame_url_2,
{PreconnectRequest(origin_to_preconnect_1, 1, network_isolation_key_2),
PreconnectRequest(origin_to_preconnect_2, 1, network_isolation_key_2)});
mock_network_context_->CompleteHostLookup(origin_to_preconnect_1.host(),
network_isolation_key_2, net::OK);
mock_network_context_->CompleteHostLookup(origin_to_preconnect_2.host(),
network_isolation_key_2, net::OK);
}
// Sends a preconnect request again after the first request finishes. Verifies
// that the second preconnect request is dispatched to the network.
TEST_F(PreconnectManagerTest,
TestStartOneUrlPreconnect_MultipleTimes_LessThanThree) {
GURL main_frame_url("http://google.com");
net::NetworkIsolationKey network_isolation_key =
CreateNetworkIsolationKey(main_frame_url);
url::Origin origin_to_preconnect_1 =
url::Origin::Create(GURL("http://cdn.google1.com"));
url::Origin origin_to_preconnect_2 =
url::Origin::Create(GURL("http://cdn.google2.com"));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url, origin_to_preconnect_1.GetURL()));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url, origin_to_preconnect_2.GetURL()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect_1.host()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect_2.host()));
preconnect_manager_->Start(
main_frame_url,
{PreconnectRequest(origin_to_preconnect_1, 1, network_isolation_key),
PreconnectRequest(origin_to_preconnect_2, 1, network_isolation_key)});
preconnect_manager_->Stop(main_frame_url);
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
mock_network_context_->CompleteHostLookup(origin_to_preconnect_1.host(),
network_isolation_key, net::OK);
mock_network_context_->CompleteHostLookup(origin_to_preconnect_2.host(),
network_isolation_key, net::OK);
VerifyAndClearExpectations();
// Now, start the preconnect request again.
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url, origin_to_preconnect_1.GetURL()));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url, origin_to_preconnect_2.GetURL()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect_1.host()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect_2.host()));
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, origin_to_preconnect_1.GetURL(),
true /* allow credentials */, network_isolation_key));
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, origin_to_preconnect_2.GetURL(),
true /* allow credentials */, network_isolation_key));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
preconnect_manager_->Start(
main_frame_url,
{PreconnectRequest(origin_to_preconnect_1, 1, network_isolation_key),
PreconnectRequest(origin_to_preconnect_2, 1, network_isolation_key)});
mock_network_context_->CompleteHostLookup(origin_to_preconnect_1.host(),
network_isolation_key, net::OK);
mock_network_context_->CompleteHostLookup(origin_to_preconnect_2.host(),
network_isolation_key, net::OK);
}
TEST_F(PreconnectManagerTest, TestStopOneUrlBeforePreconnect) {
GURL main_frame_url("http://google.com");
net::NetworkIsolationKey network_isolation_key =
CreateNetworkIsolationKey(main_frame_url);
url::Origin origin_to_preconnect =
url::Origin::Create(GURL("http://cdn.google.com"));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url, origin_to_preconnect.GetURL()));
// Preconnect job isn't started before preresolve is completed asynchronously.
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect.host()));
preconnect_manager_->Start(
main_frame_url,
{PreconnectRequest(origin_to_preconnect, 1, network_isolation_key)});
// 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));
mock_network_context_->CompleteHostLookup(origin_to_preconnect.host(),
network_isolation_key, net::OK);
}
TEST_F(PreconnectManagerTest, TestGetCallbackAfterDestruction) {
GURL main_frame_url("http://google.com");
net::NetworkIsolationKey network_isolation_key =
CreateNetworkIsolationKey(main_frame_url);
url::Origin origin_to_preconnect =
url::Origin::Create(GURL("http://cdn.google.com"));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url, origin_to_preconnect.GetURL()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect.host()));
preconnect_manager_->Start(
main_frame_url,
{PreconnectRequest(origin_to_preconnect, 1, network_isolation_key)});
// Callback may outlive PreconnectManager but it shouldn't cause a crash.
preconnect_manager_ = nullptr;
mock_network_context_->CompleteHostLookup(origin_to_preconnect.host(),
network_isolation_key, net::OK);
}
TEST_F(PreconnectManagerTest, TestUnqueuedPreresolvesCanceled) {
GURL main_frame_url("http://google.com");
net::NetworkIsolationKey network_isolation_key =
CreateNetworkIsolationKey(main_frame_url);
size_t count = features::GetMaxInflightPreresolves();
std::vector<PreconnectRequest> requests;
for (size_t i = 0; i < count; ++i) {
// Exactly features::GetMaxInflightPreresolves() should be preresolved.
std::string url = base::StringPrintf("http://cdn%" PRIuS ".google.com", i);
requests.emplace_back(url::Origin::Create(GURL(url)), 1,
network_isolation_key);
EXPECT_CALL(*mock_delegate_,
PreconnectInitiated(main_frame_url, GURL(url)));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(requests.back().origin.host()));
}
// This url shouldn't be preresolved.
requests.emplace_back(url::Origin::Create(GURL("http://no.preresolve.com")),
1, network_isolation_key);
preconnect_manager_->Start(main_frame_url, requests);
preconnect_manager_->Stop(main_frame_url);
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
for (size_t i = 0; i < count; ++i) {
mock_network_context_->CompleteHostLookup(requests[i].origin.host(),
network_isolation_key, net::OK);
}
}
TEST_F(PreconnectManagerTest, TestQueueingMetricsRecorded) {
base::HistogramTester histogram_tester;
GURL main_frame_url("http://google.com");
net::NetworkIsolationKey network_isolation_key =
CreateNetworkIsolationKey(main_frame_url);
size_t num_preresolves = features::GetMaxInflightPreresolves();
std::vector<PreconnectRequest> requests;
for (size_t i = 0; i < num_preresolves; ++i) {
// Exactly features::GetMaxInflightPreresolves() should be preresolved.
std::string url = base::StringPrintf("http://cdn%" PRIuS ".google.com", i);
requests.emplace_back(url::Origin::Create(GURL(url)), 1,
network_isolation_key);
EXPECT_CALL(*mock_delegate_,
PreconnectInitiated(main_frame_url, GURL(url)));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(requests.back().origin.host()));
}
// This url shouldn't be preresolved.
requests.emplace_back(url::Origin::Create(GURL("http://no.preresolve.com")),
1, network_isolation_key);
preconnect_manager_->Start(main_frame_url, requests);
// The number of queued jobs should have been recorded.
histogram_tester.ExpectUniqueSample(
"Navigation.Preconnect.PreresolveJobQueueLength", num_preresolves + 1, 1);
// Each job that was actually executed should have had its queueing time
// recorded.
histogram_tester.ExpectTotalCount(
"Navigation.Preconnect.PreresolveJobQueueingTime", num_preresolves);
preconnect_manager_->Stop(main_frame_url);
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
for (size_t i = 0; i < num_preresolves; ++i) {
mock_network_context_->CompleteHostLookup(requests[i].origin.host(),
network_isolation_key, net::OK);
}
}
TEST_F(PreconnectManagerTest, TestTwoConcurrentMainFrameUrls) {
GURL main_frame_url1("http://google.com");
net::NetworkIsolationKey network_isolation_key1 =
CreateNetworkIsolationKey(main_frame_url1);
url::Origin origin_to_preconnect1 =
url::Origin::Create(GURL("http://cdn.google.com"));
GURL main_frame_url2("http://facebook.com");
net::NetworkIsolationKey network_isolation_key2 =
CreateNetworkIsolationKey(main_frame_url2);
url::Origin origin_to_preconnect2 =
url::Origin::Create(GURL("http://cdn.facebook.com"));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url1, origin_to_preconnect1.GetURL()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect1.host()));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url2, origin_to_preconnect2.GetURL()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect2.host()));
preconnect_manager_->Start(
main_frame_url1,
{PreconnectRequest(origin_to_preconnect1, 1, network_isolation_key1)});
preconnect_manager_->Start(
main_frame_url2,
{PreconnectRequest(origin_to_preconnect2, 1, network_isolation_key2)});
// 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(
*mock_network_context_,
PreconnectSockets(1, origin_to_preconnect1.GetURL(),
true /* allow credentials */, network_isolation_key1));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url1));
mock_network_context_->CompleteHostLookup(origin_to_preconnect1.host(),
network_isolation_key1, net::OK);
// No preconnect for the second url.
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url2));
mock_network_context_->CompleteHostLookup(origin_to_preconnect2.host(),
network_isolation_key2, net::OK);
}
// Checks that the PreconnectManager queues up preconnect requests for URLs
// with same host.
TEST_F(PreconnectManagerTest, TestTwoConcurrentSameHostMainFrameUrls) {
GURL main_frame_url1("http://google.com/search?query=cats");
net::NetworkIsolationKey network_isolation_key1 =
CreateNetworkIsolationKey(main_frame_url1);
url::Origin origin_to_preconnect1 =
url::Origin::Create(GURL("http://cats.google.com"));
GURL main_frame_url2("http://google.com/search?query=dogs");
net::NetworkIsolationKey network_isolation_key2 =
CreateNetworkIsolationKey(main_frame_url2);
url::Origin origin_to_preconnect2 =
url::Origin::Create(GURL("http://dogs.google.com"));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url1, origin_to_preconnect1.GetURL()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect1.host()));
preconnect_manager_->Start(
main_frame_url1,
{PreconnectRequest(origin_to_preconnect1, 1, network_isolation_key1)});
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url2, origin_to_preconnect2.GetURL()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect2.host()));
preconnect_manager_->Start(
main_frame_url2,
{PreconnectRequest(origin_to_preconnect2, 1, network_isolation_key2)});
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, origin_to_preconnect1.GetURL(),
true /* allow credentials */, network_isolation_key1));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url1));
mock_network_context_->CompleteHostLookup(origin_to_preconnect1.host(),
network_isolation_key1, net::OK);
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, origin_to_preconnect2.GetURL(),
true /* allow credentials */, network_isolation_key2));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url2));
mock_network_context_->CompleteHostLookup(origin_to_preconnect2.host(),
network_isolation_key2, net::OK);
}
TEST_F(PreconnectManagerTest, TestStartPreresolveHost) {
GURL url("http://cdn.google.com/script.js");
GURL origin("http://cdn.google.com");
net::NetworkIsolationKey network_isolation_key =
CreateNetworkIsolationKey(origin);
// PreconnectFinished shouldn't be called.
EXPECT_CALL(*mock_network_context_, ResolveHostProxy(origin.host()));
preconnect_manager_->StartPreresolveHost(url, network_isolation_key);
mock_network_context_->CompleteHostLookup(origin.host(),
network_isolation_key, net::OK);
// Non http url shouldn't be preresovled.
GURL non_http_url("file:///tmp/index.html");
preconnect_manager_->StartPreresolveHost(non_http_url, network_isolation_key);
}
TEST_F(PreconnectManagerTest, TestStartPreresolveHosts) {
GURL cdn("http://cdn.google.com");
GURL fonts("http://fonts.google.com");
net::NetworkIsolationKey network_isolation_key =
CreateNetworkIsolationKey(cdn);
EXPECT_CALL(*mock_network_context_, ResolveHostProxy(cdn.host()));
EXPECT_CALL(*mock_network_context_, ResolveHostProxy(fonts.host()));
preconnect_manager_->StartPreresolveHosts({cdn.host(), fonts.host()},
network_isolation_key);
mock_network_context_->CompleteHostLookup(cdn.host(), network_isolation_key,
net::OK);
mock_network_context_->CompleteHostLookup(fonts.host(), network_isolation_key,
net::OK);
}
TEST_F(PreconnectManagerTest, TestStartPreconnectUrl) {
GURL url("http://cdn.google.com/script.js");
net::NetworkIsolationKey network_isolation_key =
CreateNetworkIsolationKey(url);
GURL origin("http://cdn.google.com");
bool allow_credentials = false;
EXPECT_CALL(*mock_network_context_, ResolveHostProxy(origin.host()));
preconnect_manager_->StartPreconnectUrl(url, allow_credentials,
network_isolation_key);
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, origin, allow_credentials, network_isolation_key));
mock_network_context_->CompleteHostLookup(origin.host(),
network_isolation_key, net::OK);
// Non http url shouldn't be preconnected.
GURL non_http_url("file:///tmp/index.html");
preconnect_manager_->StartPreconnectUrl(non_http_url, allow_credentials,
network_isolation_key);
}
TEST_F(PreconnectManagerTest, TestStartPreconnectUrlWithNetworkIsolationKey) {
GURL url("http://cdn.google.com/script.js");
GURL origin("http://cdn.google.com");
bool allow_credentials = false;
url::Origin requesting_origin = url::Origin::Create(GURL("http://foo.test"));
net::NetworkIsolationKey network_isolation_key(requesting_origin,
requesting_origin);
EXPECT_CALL(*mock_network_context_, ResolveHostProxy(origin.host()));
preconnect_manager_->StartPreconnectUrl(url, allow_credentials,
network_isolation_key);
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, origin, allow_credentials, network_isolation_key));
mock_network_context_->CompleteHostLookup(origin.host(),
network_isolation_key, net::OK);
}
TEST_F(PreconnectManagerTest, TestDetachedRequestHasHigherPriority) {
GURL main_frame_url("http://google.com");
net::NetworkIsolationKey network_isolation_key =
CreateNetworkIsolationKey(main_frame_url);
size_t count = features::GetMaxInflightPreresolves();
std::vector<PreconnectRequest> requests;
// Create enough asynchronous jobs to leave the last one in the queue.
for (size_t i = 0; i < count; ++i) {
std::string url = base::StringPrintf("http://cdn%" PRIuS ".google.com", i);
requests.emplace_back(url::Origin::Create(GURL(url)), 0,
network_isolation_key);
EXPECT_CALL(*mock_delegate_,
PreconnectInitiated(main_frame_url, GURL(url)));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(requests.back().origin.host()));
}
// This url will wait in the queue.
url::Origin queued_origin =
url::Origin::Create(GURL("http://fonts.google.com"));
requests.emplace_back(queued_origin, 0, network_isolation_key);
preconnect_manager_->Start(main_frame_url, requests);
// This url should come to the front of the queue.
GURL detached_preresolve("http://ads.google.com");
preconnect_manager_->StartPreresolveHost(detached_preresolve,
network_isolation_key);
Mock::VerifyAndClearExpectations(preconnect_manager_.get());
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(detached_preresolve.host()));
mock_network_context_->CompleteHostLookup(requests[0].origin.host(),
network_isolation_key, net::OK);
Mock::VerifyAndClearExpectations(preconnect_manager_.get());
EXPECT_CALL(*mock_delegate_,
PreconnectInitiated(main_frame_url, queued_origin.GetURL()));
EXPECT_CALL(*mock_network_context_, ResolveHostProxy(queued_origin.host()));
mock_network_context_->CompleteHostLookup(detached_preresolve.host(),
network_isolation_key, net::OK);
mock_network_context_->CompleteHostLookup(queued_origin.host(),
network_isolation_key, net::OK);
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
for (size_t i = 1; i < count; ++i) {
mock_network_context_->CompleteHostLookup(requests[i].origin.host(),
network_isolation_key, net::OK);
}
}
TEST_F(PreconnectManagerTest, TestSuccessfulProxyLookup) {
mock_network_context_->EnableProxyTesting();
GURL main_frame_url("http://google.com");
net::NetworkIsolationKey network_isolation_key =
CreateNetworkIsolationKey(main_frame_url);
url::Origin origin_to_preconnect =
url::Origin::Create(GURL("http://cdn.google.com"));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url, origin_to_preconnect.GetURL()));
preconnect_manager_->Start(
main_frame_url,
{PreconnectRequest(origin_to_preconnect, 1, network_isolation_key)});
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, origin_to_preconnect.GetURL(),
true /* allow credentials */, network_isolation_key));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
mock_network_context_->CompleteProxyLookup(origin_to_preconnect.GetURL(),
GetIndirectProxyInfo());
}
TEST_F(PreconnectManagerTest, TestSuccessfulHostLookupAfterProxyLookupFailure) {
mock_network_context_->EnableProxyTesting();
GURL main_frame_url("http://google.com");
net::NetworkIsolationKey network_isolation_key =
CreateNetworkIsolationKey(main_frame_url);
url::Origin origin_to_preconnect =
url::Origin::Create(GURL("http://cdn.google.com"));
url::Origin origin_to_preconnect2 =
url::Origin::Create(GURL("http://ads.google.com"));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url, origin_to_preconnect.GetURL()));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url, origin_to_preconnect2.GetURL()));
preconnect_manager_->Start(
main_frame_url,
{PreconnectRequest(origin_to_preconnect, 1, network_isolation_key),
PreconnectRequest(origin_to_preconnect2, 1, network_isolation_key)});
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect.host()));
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect2.host()));
// First URL uses direct connection.
mock_network_context_->CompleteProxyLookup(origin_to_preconnect.GetURL(),
GetDirectProxyInfo());
// Second URL proxy lookup failed.
mock_network_context_->CompleteProxyLookup(origin_to_preconnect2.GetURL(),
absl::nullopt);
Mock::VerifyAndClearExpectations(mock_network_context_.get());
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, origin_to_preconnect.GetURL(),
true /* allow credentials */, network_isolation_key));
EXPECT_CALL(
*mock_network_context_,
PreconnectSockets(1, origin_to_preconnect2.GetURL(),
true /* allow credentials */, network_isolation_key));
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
mock_network_context_->CompleteHostLookup(origin_to_preconnect.host(),
network_isolation_key, net::OK);
mock_network_context_->CompleteHostLookup(origin_to_preconnect2.host(),
network_isolation_key, net::OK);
}
TEST_F(PreconnectManagerTest, TestBothProxyAndHostLookupFailed) {
mock_network_context_->EnableProxyTesting();
GURL main_frame_url("http://google.com");
net::NetworkIsolationKey network_isolation_key =
CreateNetworkIsolationKey(main_frame_url);
url::Origin origin_to_preconnect =
url::Origin::Create(GURL("http://cdn.google.com"));
EXPECT_CALL(
*mock_delegate_,
PreconnectInitiated(main_frame_url, origin_to_preconnect.GetURL()));
preconnect_manager_->Start(
main_frame_url,
{PreconnectRequest(origin_to_preconnect, 1, network_isolation_key)});
EXPECT_CALL(*mock_network_context_,
ResolveHostProxy(origin_to_preconnect.host()));
mock_network_context_->CompleteProxyLookup(origin_to_preconnect.GetURL(),
absl::nullopt);
Mock::VerifyAndClearExpectations(mock_network_context_.get());
EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
mock_network_context_->CompleteHostLookup(origin_to_preconnect.host(),
network_isolation_key,
net::ERR_NAME_NOT_RESOLVED);
}
} // namespace predictors