blob: c5b9108f28276e40ae23e83514f72f95475c07d8 [file] [log] [blame]
// Copyright 2016 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/offline_pages/offline_page_request_job.h"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/callback.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/histogram_tester.h"
#include "base/test/simple_test_clock.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/default_clock.h"
#include "chrome/browser/offline_pages/offline_page_model_factory.h"
#include "chrome/browser/offline_pages/offline_page_request_interceptor.h"
#include "chrome/browser/offline_pages/offline_page_tab_helper.h"
#include "chrome/browser/renderer_host/chrome_navigation_ui_data.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/offline_pages/core/client_namespace_constants.h"
#include "components/offline_pages/core/model/offline_page_model_taskified.h"
#include "components/offline_pages/core/offline_page_metadata_store_sql.h"
#include "components/offline_pages/core/request_header/offline_page_navigation_ui_data.h"
#include "components/previews/core/previews_decider.h"
#include "components/previews/core/previews_experiments.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/previews_state.h"
#include "content/public/common/resource_type.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_intercepting_job_factory.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace offline_pages {
namespace {
const char kPrivateOfflineFileDir[] = "offline_pages";
const char kPublicOfflineFileDir[] = "public_offline_pages";
const GURL kTestUrl1("http://test.org/page1");
const GURL kTestUrl2("http://test.org/page2");
const GURL kTestUrl3("http://test.org/page3");
const GURL kTestUrl3WithFragment("http://test.org/page3#ref1");
const GURL kTestUrl4("http://test.org/page4");
const GURL kTestUrl5("http://test.org/page5");
const GURL kTestUrl6("http://test.org/page6");
const GURL kTestUrl7("http://test.org/page7");
const GURL kTestUrlRedirectsTo3("http://test.org/first");
const GURL kTestUrl8("http://test.org/page8");
const GURL kTestUrl9("http://test.org/page9");
const ClientId kTestClientId1 = ClientId(kBookmarkNamespace, "1234");
const ClientId kTestClientId2 = ClientId(kDownloadNamespace, "1a2b3c4d");
const ClientId kTestClientId3 = ClientId(kAsyncNamespace, "3456abcd");
const ClientId kTestClientId4 = ClientId(kNTPSuggestionsNamespace, "5678");
const ClientId kTestClientId5 = ClientId(kBrowserActionsNamespace, "9999");
const ClientId kTestClientId6 = ClientId(kDownloadNamespace, "a1");
const ClientId kTestClientId7 = ClientId(kDownloadNamespace, "b2");
const ClientId kTestClientId8 = ClientId(kDownloadNamespace, "c3");
const ClientId kTestClientId9 = ClientId(kDownloadNamespace, "d4");
const ClientId kTestClientId10 = ClientId(kDownloadNamespace, "e5");
// Note: as most file size values are below 1 KiB, the file size samples added
// to page size histograms will mostly fall into the 0-sized bucket. So when
// checking for correct reporting a 0 will be directly used instead of the
// specific file size constant divided by 1024.
const int kTestFileSize1 = 444; // Real size of test.mhtml.
const int kTestFileSize2 = 450; // Real size of hello.mhtml.
const int kTestFileSize3 = 450; // Real size of hello.mhtml.
const int kTestFileSize4NonExistent = 9999;
const int kTestFileSize5 = 450; // Real size of hello.mhtml.
const int kTestFileSize6Mismatch = 1450; // Wrong size of hello.mhtml.
const int kTestFileSize7 = 450; // Real size of hello.mhtml.
const int kTestFileSize8 = 450; // Real size of hello.mhtml.
const int kTestFileSize9 = 444; // Real size of test.mhtml.
const int kTestFileSize10 = 450; // Real size of hello.mhtml.
const std::string kTestDigest2(
"\x90\x64\xF9\x7C\x94\xE5\x9E\x91\x83\x3D\x41\xB0\x36\x90\x0A\xDF\xB3\xB1"
"\x5C\x13\xBE\xB8\x35\x8C\xF6\x5B\xC4\xB5\x5A\xFC\x3A\xCC",
32); // SHA256 Hash of hello.mhtml.
const std::string kTestDigest6(kTestDigest2); // SHA256 Hash of hello.mhtml.
const std::string kTestDigest7Mismatch(
"\xff\x64\xF9\x7C\x94\xE5\x9E\x91\x83\x3D\x41\xB0\x36\x90\x0A\xDF\xB3\xB1"
"\x5C\x13\xBE\xB8\x35\x8C\xF6\x5B\xC4\xB5\x5A\xFC\x3A\xCC",
32); // Wrong SHA256 Hash of hello.mhtml.
const std::string kTestDigest10Mismatch(
"\xff\x64\xF9\x7C\x94\xE5\x9E\x91\x83\x3D\x41\xB0\x36\x90\x0A\xDF\xB3\xB1"
"\x5C\x13\xBE\xB8\x35\x8C\xF6\x5B\xC4\xB5\x5A\xFC\x3A\xCC",
32); // Wrong SHA256 Hash of hello.mhtml.
const int kTabId = 1;
const int kBufSize = 1024;
const char kAggregatedRequestResultHistogram[] =
"OfflinePages.AggregatedRequestResult2";
const char kOpenFileErrorCodeHistogram[] =
"OfflinePages.RequestJob.OpenFileErrorCode";
const char kAccessEntryPointHistogram[] = "OfflinePages.AccessEntryPoint.";
const char kPageSizeAccessOfflineHistogramBase[] =
"OfflinePages.PageSizeOnAccess.Offline.";
const char kPageSizeAccessOnlineHistogramBase[] =
"OfflinePages.PageSizeOnAccess.Online.";
class OfflinePageRequestJobTestDelegate
: public OfflinePageRequestJob::Delegate {
public:
OfflinePageRequestJobTestDelegate(content::WebContents* web_content,
int tab_id)
: web_content_(web_content), tab_id_(tab_id) {}
content::ResourceRequestInfo::WebContentsGetter GetWebContentsGetter(
net::URLRequest* request) const override {
return base::Bind(&OfflinePageRequestJobTestDelegate::GetWebContents,
web_content_);
}
OfflinePageRequestJob::Delegate::TabIdGetter GetTabIdGetter() const override {
return base::Bind(&OfflinePageRequestJobTestDelegate::GetTabId, tab_id_);
}
private:
static content::WebContents* GetWebContents(
content::WebContents* web_content) {
return web_content;
}
static bool GetTabId(int tab_id_value,
content::WebContents* web_content,
int* tab_id) {
*tab_id = tab_id_value;
return true;
}
content::WebContents* web_content_;
int tab_id_;
DISALLOW_COPY_AND_ASSIGN(OfflinePageRequestJobTestDelegate);
};
class TestURLRequestDelegate : public net::URLRequest::Delegate {
public:
typedef base::Callback<void(int)> ReadCompletedCallback;
explicit TestURLRequestDelegate(const ReadCompletedCallback& callback)
: read_completed_callback_(callback),
buffer_(new net::IOBuffer(kBufSize)) {}
void OnResponseStarted(net::URLRequest* request, int net_error) override {
DCHECK_NE(net::ERR_IO_PENDING, net_error);
if (net_error != net::OK) {
OnReadCompleted(request, 0);
return;
}
int bytes_read = 0;
request->Read(buffer_.get(), kBufSize, &bytes_read);
}
void OnReadCompleted(net::URLRequest* request, int bytes_read) override {
if (!read_completed_callback_.is_null())
read_completed_callback_.Run(bytes_read);
}
private:
ReadCompletedCallback read_completed_callback_;
scoped_refptr<net::IOBuffer> buffer_;
DISALLOW_COPY_AND_ASSIGN(TestURLRequestDelegate);
};
class TestURLRequestInterceptingJobFactory
: public net::URLRequestInterceptingJobFactory {
public:
TestURLRequestInterceptingJobFactory(
std::unique_ptr<net::URLRequestJobFactory> job_factory,
std::unique_ptr<net::URLRequestInterceptor> interceptor,
content::WebContents* web_contents)
: net::URLRequestInterceptingJobFactory(std::move(job_factory),
std::move(interceptor)),
web_contents_(web_contents) {}
~TestURLRequestInterceptingJobFactory() override {}
net::URLRequestJob* MaybeCreateJobWithProtocolHandler(
const std::string& scheme,
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const override {
net::URLRequestJob* job = net::URLRequestInterceptingJobFactory::
MaybeCreateJobWithProtocolHandler(scheme, request, network_delegate);
if (job) {
static_cast<OfflinePageRequestJob*>(job)->SetDelegateForTesting(
base::MakeUnique<OfflinePageRequestJobTestDelegate>(web_contents_,
kTabId));
}
return job;
}
private:
content::WebContents* web_contents_;
DISALLOW_COPY_AND_ASSIGN(TestURLRequestInterceptingJobFactory);
};
class TestNetworkChangeNotifier : public net::NetworkChangeNotifier {
public:
TestNetworkChangeNotifier() : online_(true) {}
~TestNetworkChangeNotifier() override {}
net::NetworkChangeNotifier::ConnectionType GetCurrentConnectionType()
const override {
return online_ ? net::NetworkChangeNotifier::CONNECTION_UNKNOWN
: net::NetworkChangeNotifier::CONNECTION_NONE;
}
void set_online(bool online) { online_ = online; }
private:
bool online_;
DISALLOW_COPY_AND_ASSIGN(TestNetworkChangeNotifier);
};
class TestPreviewsDecider : public previews::PreviewsDecider {
public:
TestPreviewsDecider() : should_allow_preview_(false) {}
~TestPreviewsDecider() override {}
bool ShouldAllowPreview(const net::URLRequest& request,
previews::PreviewsType type) const override {
return should_allow_preview_;
}
bool ShouldAllowPreviewAtECT(
const net::URLRequest& request,
previews::PreviewsType type,
net::EffectiveConnectionType effective_connection_type_threshold,
const std::vector<std::string>& host_blacklist_from_server)
const override {
return should_allow_preview_;
}
void set_should_allow_preview(bool should_allow_preview) {
should_allow_preview_ = should_allow_preview;
}
private:
bool should_allow_preview_;
DISALLOW_COPY_AND_ASSIGN(TestPreviewsDecider);
};
class TestOfflinePageArchiver : public OfflinePageArchiver {
public:
TestOfflinePageArchiver(const GURL& url,
const base::FilePath& archive_file_path,
int archive_file_size,
const std::string& digest)
: url_(url),
archive_file_path_(archive_file_path),
archive_file_size_(archive_file_size),
digest_(digest) {}
~TestOfflinePageArchiver() override {}
void CreateArchive(const base::FilePath& archives_dir,
const CreateArchiveParams& create_archive_params,
const CreateArchiveCallback& callback) override {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(callback, this, ArchiverResult::SUCCESSFULLY_CREATED, url_,
archive_file_path_, base::string16(), archive_file_size_,
digest_));
}
private:
const GURL url_;
const base::FilePath archive_file_path_;
const int archive_file_size_;
const std::string digest_;
DISALLOW_COPY_AND_ASSIGN(TestOfflinePageArchiver);
};
std::unique_ptr<KeyedService> BuildTestOfflinePageModel(
content::BrowserContext* context) {
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
base::ThreadTaskRunnerHandle::Get();
base::FilePath store_path =
context->GetPath().Append(chrome::kOfflinePageMetadataDirname);
std::unique_ptr<OfflinePageMetadataStoreSQL> metadata_store(
new OfflinePageMetadataStoreSQL(task_runner, store_path));
base::FilePath test_data_dir_path;
PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_path);
base::FilePath private_archives_dir =
test_data_dir_path.AppendASCII(kPrivateOfflineFileDir);
base::FilePath public_archives_dir =
test_data_dir_path.AppendASCII(kPublicOfflineFileDir);
// Since we're not saving page into temporary dir, it's set the same as the
// private dir.
std::unique_ptr<ArchiveManager> archive_manager(
new ArchiveManager(private_archives_dir, private_archives_dir,
public_archives_dir, task_runner));
std::unique_ptr<base::Clock> clock(new base::DefaultClock);
return std::unique_ptr<KeyedService>(new OfflinePageModelTaskified(
std::move(metadata_store), std::move(archive_manager), task_runner,
std::move(clock)));
}
} // namespace
class OfflinePageRequestJobTest : public testing::Test {
public:
OfflinePageRequestJobTest();
~OfflinePageRequestJobTest() override {}
void SetUp() override;
void TearDown() override;
void SimulateHasNetworkConnectivity(bool has_connectivity);
void RunUntilIdle();
void WaitForAsyncOperation();
void SavePage(const GURL& url,
const ClientId& client_id,
const GURL& original_url,
std::unique_ptr<OfflinePageArchiver> archiver);
OfflinePageItem GetPage(int64_t offline_id);
void InterceptRequest(const GURL& url,
const std::string& method,
const std::string& extra_header_name,
const std::string& extra_header_value,
content::ResourceType resource_type);
// Expect exactly one count of |result| UMA reported. No other bucket should
// have sample.
void ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult result);
// Expect exactly |count| of |result| UMA reported. No other bucket should
// have sample.
void ExpectMultiUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult result,
int count);
// Expect one count of |result| UMA reported. Other buckets may have samples
// as well.
void ExpectOneNonuniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult result);
// Expect no samples to have been reported to the aggregated results
// histogram.
void ExpectNoSamplesInAggregatedRequestResult();
void ExpectOpenFileErrorCode(int result);
void ExpectAccessEntryPoint(
const ClientId& client_id,
OfflinePageRequestJob::AccessEntryPoint entry_point);
void ExpectNoAccessEntryPoint();
void ExpectOfflinePageSizeUniqueSample(ClientId client_id,
int bucket,
int count);
void ExpectOfflinePageSizeTotalSuffixCount(int count);
void ExpectOnlinePageSizeUniqueSample(ClientId client_id,
int bucket,
int count);
void ExpectOnlinePageSizeTotalSuffixCount(int count);
net::TestURLRequestContext* url_request_context() {
return test_url_request_context_.get();
}
Profile* profile() { return profile_; }
OfflinePageTabHelper* offline_page_tab_helper() const {
return offline_page_tab_helper_;
}
const std::vector<int64_t>& offline_ids() const { return offline_ids_; }
int bytes_read() const { return bytes_read_; }
bool is_offline_page_set_in_navigation_data() const {
return is_offline_page_set_in_navigation_data_;
}
TestPreviewsDecider* test_previews_decider() {
return test_previews_decider_.get();
}
private:
void OnSavePageDone(SavePageResult result, int64_t offline_id);
std::unique_ptr<net::URLRequest> CreateRequest(
const GURL& url,
const std::string& method,
content::ResourceType resource_type);
void OnGetPageByOfflineIdDone(const OfflinePageItem* pages);
void ReadCompleted(int bytes_read,
bool is_offline_page_set_in_navigation_data);
// Runs on IO thread.
void SetUpNetworkObjectsOnIO();
void TearDownNetworkObjectsOnIO();
void InterceptRequestOnIO(const GURL& url,
const std::string& method,
const std::string& extra_header_name,
const std::string& extra_header_value,
content::ResourceType resource_type);
void ReadCompletedOnIO(int bytes_read);
void TearDownOnReadCompletedOnIO(int bytes_read,
bool is_offline_page_set_in_navigation_data);
content::TestBrowserThreadBundle thread_bundle_;
std::unique_ptr<base::SimpleTestClock> clock_;
base::SimpleTestClock* clock_ptr_;
TestingProfileManager profile_manager_;
TestingProfile* profile_;
std::unique_ptr<content::WebContents> web_contents_;
base::HistogramTester histogram_tester_;
OfflinePageTabHelper* offline_page_tab_helper_; // Not owned.
std::vector<int64_t> offline_ids_;
int bytes_read_;
bool is_offline_page_set_in_navigation_data_;
OfflinePageItem page_;
// These are not thread-safe. But they can be used in the pattern that
// setting the state is done first from one thread and reading this state
// can be from any other thread later.
std::unique_ptr<TestNetworkChangeNotifier> network_change_notifier_;
std::unique_ptr<TestPreviewsDecider> test_previews_decider_;
// These should only be accessed purely from IO thread.
std::unique_ptr<net::TestURLRequestContext> test_url_request_context_;
std::unique_ptr<net::URLRequestJobFactoryImpl> url_request_job_factory_;
std::unique_ptr<net::URLRequestInterceptingJobFactory>
intercepting_job_factory_;
std::unique_ptr<TestURLRequestDelegate> url_request_delegate_;
std::unique_ptr<net::URLRequest> request_;
bool async_operation_completed_ = false;
base::Closure async_operation_completed_callback_;
DISALLOW_COPY_AND_ASSIGN(OfflinePageRequestJobTest);
};
OfflinePageRequestJobTest::OfflinePageRequestJobTest()
: thread_bundle_(content::TestBrowserThreadBundle::REAL_IO_THREAD),
clock_(new base::SimpleTestClock),
clock_ptr_(clock_.get()),
profile_manager_(TestingBrowserProcess::GetGlobal()),
bytes_read_(0),
is_offline_page_set_in_navigation_data_(false),
network_change_notifier_(new TestNetworkChangeNotifier),
test_previews_decider_(new TestPreviewsDecider) {}
void OfflinePageRequestJobTest::SetUp() {
// Create a test profile.
ASSERT_TRUE(profile_manager_.SetUp());
profile_ = profile_manager_.CreateTestingProfile("Profile 1");
// Create a test web contents.
web_contents_.reset(content::WebContents::Create(
content::WebContents::CreateParams(profile_)));
OfflinePageTabHelper::CreateForWebContents(web_contents_.get());
offline_page_tab_helper_ =
OfflinePageTabHelper::FromWebContents(web_contents_.get());
// Set up the factory for testing.
OfflinePageModelFactory::GetInstance()->SetTestingFactoryAndUse(
profile(), BuildTestOfflinePageModel);
OfflinePageModelTaskified* model = static_cast<OfflinePageModelTaskified*>(
OfflinePageModelFactory::GetForBrowserContext(profile()));
// Hook up a test clock such that we can control the time when the offline
// page is created.
clock_ptr_->SetNow(base::Time::Now());
model->SetClockForTesting(std::move(clock_));
// Skip the logic to clear the original URL if it is same as final URL.
// This is needed in order to test that offline page request handler can
// omit the redirect under this circumstance, for compatibility with the
// metadata already written to the store.
model->SetSkipClearingOriginalUrlForTesting();
// Initialize OfflinePageModel.
RunUntilIdle();
// All offline pages being created below will point to real archive files
// residing in test data directory.
base::FilePath test_data_dir_path;
ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_path));
// Save an offline page.
base::FilePath archive_file_path =
test_data_dir_path.AppendASCII(kPrivateOfflineFileDir)
.AppendASCII("test.mhtml");
std::unique_ptr<TestOfflinePageArchiver> archiver(new TestOfflinePageArchiver(
kTestUrl1, archive_file_path, kTestFileSize1, std::string()));
SavePage(kTestUrl1, kTestClientId1, GURL(), std::move(archiver));
// Save another offline page associated with same online URL as above, but
// pointing to different archive file.
base::FilePath archive_file_path2 =
test_data_dir_path.AppendASCII(kPrivateOfflineFileDir)
.AppendASCII("hello.mhtml");
std::unique_ptr<TestOfflinePageArchiver> archiver2(
new TestOfflinePageArchiver(kTestUrl1, archive_file_path2, kTestFileSize2,
kTestDigest2));
// Make sure that the creation time of 2nd offline file is later.
clock_ptr_->Advance(base::TimeDelta::FromMinutes(10));
SavePage(kTestUrl1, kTestClientId2, GURL(), std::move(archiver2));
// Save an offline page associated with online URL that has a fragment
// identifier.
base::FilePath archive_file_path3 =
test_data_dir_path.AppendASCII(kPrivateOfflineFileDir)
.AppendASCII("hello.mhtml");
std::unique_ptr<TestOfflinePageArchiver> archiver3(
new TestOfflinePageArchiver(kTestUrl3WithFragment, archive_file_path3,
kTestFileSize3, std::string()));
SavePage(kTestUrl3WithFragment, kTestClientId3, kTestUrlRedirectsTo3,
std::move(archiver3));
// Save an offline page pointing to non-existent archive file.
base::FilePath archive_file_path4 =
test_data_dir_path.AppendASCII(kPrivateOfflineFileDir)
.AppendASCII("nonexistent.mhtml");
std::unique_ptr<TestOfflinePageArchiver> archiver4(
new TestOfflinePageArchiver(kTestUrl4, archive_file_path4,
kTestFileSize4NonExistent, std::string()));
SavePage(kTestUrl4, kTestClientId4, GURL(), std::move(archiver4));
// Save an offline page with same original URL and final URL.
base::FilePath archive_file_path5 =
test_data_dir_path.AppendASCII(kPrivateOfflineFileDir)
.AppendASCII("hello.mhtml");
std::unique_ptr<TestOfflinePageArchiver> archiver5(
new TestOfflinePageArchiver(kTestUrl5, archive_file_path5, kTestFileSize5,
std::string()));
SavePage(kTestUrl5, kTestClientId5, kTestUrl5, std::move(archiver5));
// Check if the original URL is still present.
OfflinePageItem page = GetPage(offline_ids()[5]);
EXPECT_EQ(kTestUrl5, page.original_url);
// Save an offline page in public location with mismatched file size.
base::FilePath archive_file_path6 =
test_data_dir_path.AppendASCII(kPublicOfflineFileDir)
.AppendASCII("hello.mhtml");
std::unique_ptr<TestOfflinePageArchiver> archiver6(
new TestOfflinePageArchiver(kTestUrl6, archive_file_path6,
kTestFileSize6Mismatch, kTestDigest6));
SavePage(kTestUrl6, kTestClientId6, GURL(), std::move(archiver6));
// Save an offline page in public location with mismatched digest.
base::FilePath archive_file_path7 =
test_data_dir_path.AppendASCII(kPublicOfflineFileDir)
.AppendASCII("hello.mhtml");
std::unique_ptr<TestOfflinePageArchiver> archiver7(
new TestOfflinePageArchiver(kTestUrl7, archive_file_path7, kTestFileSize7,
kTestDigest7Mismatch));
SavePage(kTestUrl7, kTestClientId7, GURL(), std::move(archiver7));
// Save an offline page in public location with no digest.
base::FilePath archive_file_path8 =
test_data_dir_path.AppendASCII(kPublicOfflineFileDir)
.AppendASCII("hello.mhtml");
std::unique_ptr<TestOfflinePageArchiver> archiver8(
new TestOfflinePageArchiver(kTestUrl8, archive_file_path8, kTestFileSize8,
std::string()));
SavePage(kTestUrl8, kTestClientId8, GURL(), std::move(archiver8));
// Save 2 offline pages associated with same online URL, one in private
// location, while another in public location with mismatched digest.
base::FilePath archive_file_path9 =
test_data_dir_path.AppendASCII(kPrivateOfflineFileDir)
.AppendASCII("test.mhtml");
std::unique_ptr<TestOfflinePageArchiver> archiver9(
new TestOfflinePageArchiver(kTestUrl9, archive_file_path9, kTestFileSize9,
std::string()));
SavePage(kTestUrl9, kTestClientId9, GURL(), std::move(archiver9));
base::FilePath archive_file_path10 =
test_data_dir_path.AppendASCII(kPublicOfflineFileDir)
.AppendASCII("hello.mhtml");
std::unique_ptr<TestOfflinePageArchiver> archiver10(
new TestOfflinePageArchiver(kTestUrl9, archive_file_path10,
kTestFileSize10, kTestDigest10Mismatch));
SavePage(kTestUrl9, kTestClientId10, GURL(), std::move(archiver10));
}
void OfflinePageRequestJobTest::TearDown() {
OfflinePageModel* model =
OfflinePageModelFactory::GetForBrowserContext(profile());
static_cast<OfflinePageModelTaskified*>(model)->SetClockForTesting(nullptr);
}
void OfflinePageRequestJobTest::SimulateHasNetworkConnectivity(bool online) {
network_change_notifier_->set_online(online);
}
void OfflinePageRequestJobTest::RunUntilIdle() {
base::RunLoop().RunUntilIdle();
}
void OfflinePageRequestJobTest::WaitForAsyncOperation() {
// No need to wait if async operation is not needed.
if (async_operation_completed_)
return;
base::RunLoop run_loop;
async_operation_completed_callback_ = run_loop.QuitClosure();
run_loop.Run();
}
void OfflinePageRequestJobTest::SetUpNetworkObjectsOnIO() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (test_url_request_context_.get())
return;
url_request_job_factory_.reset(new net::URLRequestJobFactoryImpl);
// Create a context with delayed initialization.
test_url_request_context_.reset(new net::TestURLRequestContext(true));
// Install the interceptor.
std::unique_ptr<net::URLRequestInterceptor> interceptor(
new OfflinePageRequestInterceptor(test_previews_decider_.get()));
std::unique_ptr<net::URLRequestJobFactoryImpl> job_factory_impl(
new net::URLRequestJobFactoryImpl());
intercepting_job_factory_.reset(new TestURLRequestInterceptingJobFactory(
std::move(job_factory_impl), std::move(interceptor),
web_contents_.get()));
test_url_request_context_->set_job_factory(intercepting_job_factory_.get());
test_url_request_context_->Init();
}
void OfflinePageRequestJobTest::TearDownNetworkObjectsOnIO() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
request_.reset();
url_request_delegate_.reset();
intercepting_job_factory_.reset();
url_request_job_factory_.reset();
test_url_request_context_.reset();
}
std::unique_ptr<net::URLRequest> OfflinePageRequestJobTest::CreateRequest(
const GURL& url,
const std::string& method,
content::ResourceType resource_type) {
url_request_delegate_ = base::MakeUnique<TestURLRequestDelegate>(base::Bind(
&OfflinePageRequestJobTest::ReadCompletedOnIO, base::Unretained(this)));
std::unique_ptr<net::URLRequest> request =
url_request_context()->CreateRequest(url, net::DEFAULT_PRIORITY,
url_request_delegate_.get());
request->set_method(method);
content::ResourceRequestInfo::AllocateForTesting(
request.get(), resource_type, nullptr, /*render_process_id=*/1,
/*render_view_id=*/-1,
/*render_frame_id=*/1,
/*is_main_frame=*/true,
/*allow_download=*/true,
/*is_async=*/true, content::PREVIEWS_OFF,
std::make_unique<ChromeNavigationUIData>());
return request;
}
void OfflinePageRequestJobTest::ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult result) {
histogram_tester_.ExpectUniqueSample(kAggregatedRequestResultHistogram,
static_cast<int>(result), 1);
}
void OfflinePageRequestJobTest::
ExpectMultiUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult result,
int count) {
histogram_tester_.ExpectUniqueSample(kAggregatedRequestResultHistogram,
static_cast<int>(result), count);
}
void OfflinePageRequestJobTest::
ExpectOneNonuniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult result) {
histogram_tester_.ExpectBucketCount(kAggregatedRequestResultHistogram,
static_cast<int>(result), 1);
}
void OfflinePageRequestJobTest::ExpectNoSamplesInAggregatedRequestResult() {
histogram_tester_.ExpectTotalCount(kAggregatedRequestResultHistogram, 0);
}
void OfflinePageRequestJobTest::ExpectOpenFileErrorCode(int result) {
histogram_tester_.ExpectUniqueSample(kOpenFileErrorCodeHistogram, -result, 1);
}
void OfflinePageRequestJobTest::ExpectAccessEntryPoint(
const ClientId& client_id,
OfflinePageRequestJob::AccessEntryPoint entry_point) {
histogram_tester_.ExpectUniqueSample(
kAccessEntryPointHistogram + client_id.name_space,
static_cast<int>(entry_point), 1);
}
void OfflinePageRequestJobTest::ExpectNoAccessEntryPoint() {
EXPECT_TRUE(
histogram_tester_.GetTotalCountsForPrefix(kAccessEntryPointHistogram)
.empty());
}
void OfflinePageRequestJobTest::ExpectOfflinePageSizeUniqueSample(
ClientId client_id,
int bucket,
int count) {
histogram_tester_.ExpectUniqueSample(
kPageSizeAccessOfflineHistogramBase + client_id.name_space, bucket,
count);
}
void OfflinePageRequestJobTest::ExpectOfflinePageSizeTotalSuffixCount(
int count) {
int total_offline_count = 0;
base::HistogramTester::CountsMap all_offline_counts =
histogram_tester_.GetTotalCountsForPrefix(
kPageSizeAccessOfflineHistogramBase);
for (const std::pair<std::string, base::HistogramBase::Count>&
namespace_and_count : all_offline_counts) {
total_offline_count += namespace_and_count.second;
}
EXPECT_EQ(count, total_offline_count)
<< "Wrong histogram samples count under prefix "
<< kPageSizeAccessOfflineHistogramBase << "*";
}
void OfflinePageRequestJobTest::ExpectOnlinePageSizeUniqueSample(
ClientId client_id,
int bucket,
int count) {
histogram_tester_.ExpectUniqueSample(
kPageSizeAccessOnlineHistogramBase + client_id.name_space, bucket, count);
}
void OfflinePageRequestJobTest::ExpectOnlinePageSizeTotalSuffixCount(
int count) {
int online_count = 0;
base::HistogramTester::CountsMap all_online_counts =
histogram_tester_.GetTotalCountsForPrefix(
kPageSizeAccessOnlineHistogramBase);
for (const std::pair<std::string, base::HistogramBase::Count>&
namespace_and_count : all_online_counts) {
online_count += namespace_and_count.second;
}
EXPECT_EQ(count, online_count)
<< "Wrong histogram samples count under prefix "
<< kPageSizeAccessOnlineHistogramBase << "*";
}
void OfflinePageRequestJobTest::SavePage(
const GURL& url,
const ClientId& client_id,
const GURL& original_url,
std::unique_ptr<OfflinePageArchiver> archiver) {
async_operation_completed_ = false;
OfflinePageModel::SavePageParams save_page_params;
save_page_params.url = url;
save_page_params.client_id = client_id;
save_page_params.original_url = original_url;
OfflinePageModelFactory::GetForBrowserContext(profile())->SavePage(
save_page_params, std::move(archiver),
base::Bind(&OfflinePageRequestJobTest::OnSavePageDone,
base::Unretained(this)));
WaitForAsyncOperation();
}
void OfflinePageRequestJobTest::OnSavePageDone(SavePageResult result,
int64_t offline_id) {
ASSERT_EQ(SavePageResult::SUCCESS, result);
// Make a dummy item at index 0 to make offline_ids_ look like to start from
// index 1 in order to match all the indices used in constants.
if (offline_ids_.empty())
offline_ids_.push_back(0);
offline_ids_.push_back(offline_id);
async_operation_completed_ = true;
if (!async_operation_completed_callback_.is_null())
async_operation_completed_callback_.Run();
}
OfflinePageItem OfflinePageRequestJobTest::GetPage(int64_t offline_id) {
OfflinePageModelFactory::GetForBrowserContext(profile())->GetPageByOfflineId(
offline_id,
base::Bind(&OfflinePageRequestJobTest::OnGetPageByOfflineIdDone,
base::Unretained(this)));
RunUntilIdle();
return page_;
}
void OfflinePageRequestJobTest::OnGetPageByOfflineIdDone(
const OfflinePageItem* page) {
ASSERT_TRUE(page);
page_ = *page;
}
void OfflinePageRequestJobTest::InterceptRequestOnIO(
const GURL& url,
const std::string& method,
const std::string& extra_header_name,
const std::string& extra_header_value,
content::ResourceType resource_type) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
SetUpNetworkObjectsOnIO();
request_ = CreateRequest(url, method, resource_type);
if (!extra_header_name.empty()) {
request_->SetExtraRequestHeaderByName(extra_header_name, extra_header_value,
true);
}
request_->Start();
}
void OfflinePageRequestJobTest::InterceptRequest(
const GURL& url,
const std::string& method,
const std::string& extra_header_name,
const std::string& extra_header_value,
content::ResourceType resource_type) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&OfflinePageRequestJobTest::InterceptRequestOnIO,
base::Unretained(this), url, method, extra_header_name,
extra_header_value, resource_type));
}
void OfflinePageRequestJobTest::ReadCompletedOnIO(int bytes_read) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
bool is_offline_page_set_in_navigation_data = false;
const content::ResourceRequestInfo* info =
content::ResourceRequestInfo::ForRequest(request_.get());
ChromeNavigationUIData* navigation_data =
static_cast<ChromeNavigationUIData*>(info->GetNavigationUIData());
if (navigation_data) {
offline_pages::OfflinePageNavigationUIData* offline_page_data =
navigation_data->GetOfflinePageNavigationUIData();
if (offline_page_data && offline_page_data->is_offline_page())
is_offline_page_set_in_navigation_data = true;
}
// Since the caller is still holding a request object which we want to dispose
// as part of tearing down on IO thread, we need to do it in a separate task.
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&OfflinePageRequestJobTest::TearDownOnReadCompletedOnIO,
base::Unretained(this), bytes_read,
is_offline_page_set_in_navigation_data));
}
void OfflinePageRequestJobTest::TearDownOnReadCompletedOnIO(
int bytes_read,
bool is_offline_page_set_in_navigation_data) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
TearDownNetworkObjectsOnIO();
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&OfflinePageRequestJobTest::ReadCompleted,
base::Unretained(this), bytes_read,
is_offline_page_set_in_navigation_data));
}
void OfflinePageRequestJobTest::ReadCompleted(
int bytes_read,
bool is_offline_page_set_in_navigation_data) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
bytes_read_ = bytes_read;
is_offline_page_set_in_navigation_data_ =
is_offline_page_set_in_navigation_data;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
}
TEST_F(OfflinePageRequestJobTest, FailedToCreateRequestJob) {
SimulateHasNetworkConnectivity(false);
// Must be http/https URL.
InterceptRequest(GURL("ftp://host/doc"), "GET", "", "",
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
InterceptRequest(GURL("file:///path/doc"), "GET", "", "",
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
// Must be GET method.
InterceptRequest(kTestUrl1, "POST", "", "",
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
InterceptRequest(kTestUrl1, "HEAD", "", "",
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
// Must be main resource.
InterceptRequest(kTestUrl1, "POST", "", "", content::RESOURCE_TYPE_SUB_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
InterceptRequest(kTestUrl1, "POST", "", "", content::RESOURCE_TYPE_IMAGE);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectNoSamplesInAggregatedRequestResult();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, LoadOfflinePageOnDisconnectedNetwork) {
SimulateHasNetworkConnectivity(false);
InterceptRequest(kTestUrl1, "GET", "", "", content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(kTestFileSize2, bytes_read());
EXPECT_TRUE(is_offline_page_set_in_navigation_data());
ASSERT_TRUE(offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(offline_ids()[2],
offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
ExpectAccessEntryPoint(kTestClientId2,
OfflinePageRequestJob::AccessEntryPoint::LINK);
ExpectOfflinePageSizeUniqueSample(kTestClientId2, 0, 1);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, PageNotFoundOnDisconnectedNetwork) {
SimulateHasNetworkConnectivity(false);
InterceptRequest(kTestUrl2, "GET", "", "", content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
PAGE_NOT_FOUND_ON_DISCONNECTED_NETWORK);
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, LoadOfflinePageOnProhibitivelySlowNetwork) {
SimulateHasNetworkConnectivity(true);
test_previews_decider()->set_should_allow_preview(true);
InterceptRequest(kTestUrl1, "GET", "", "", content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(kTestFileSize2, bytes_read());
EXPECT_TRUE(is_offline_page_set_in_navigation_data());
ASSERT_TRUE(offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(offline_ids()[2],
offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_PROHIBITIVELY_SLOW_NETWORK);
ExpectAccessEntryPoint(kTestClientId2,
OfflinePageRequestJob::AccessEntryPoint::LINK);
ExpectOfflinePageSizeUniqueSample(kTestClientId2, 0, 1);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest,
DontLoadReloadOfflinePageOnProhibitivelySlowNetwork) {
SimulateHasNetworkConnectivity(true);
test_previews_decider()->set_should_allow_preview(true);
// Treat this as a reloaded page.
InterceptRequest(kTestUrl1, "GET", kOfflinePageHeader,
std::string(kOfflinePageHeaderReasonKey) + "=" +
kOfflinePageHeaderReasonValueReload,
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectNoAccessEntryPoint();
ExpectNoSamplesInAggregatedRequestResult();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, PageNotFoundOnProhibitivelySlowNetwork) {
SimulateHasNetworkConnectivity(true);
test_previews_decider()->set_should_allow_preview(true);
InterceptRequest(kTestUrl2, "GET", "", "", content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
PAGE_NOT_FOUND_ON_PROHIBITIVELY_SLOW_NETWORK);
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, LoadOfflinePageOnFlakyNetwork) {
SimulateHasNetworkConnectivity(true);
// When custom offline header exists and contains "reason=error", it means
// that net error is hit in last request due to flaky network.
InterceptRequest(kTestUrl1, "GET", kOfflinePageHeader,
std::string(kOfflinePageHeaderReasonKey) + "=" +
kOfflinePageHeaderReasonValueDueToNetError,
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(kTestFileSize2, bytes_read());
EXPECT_TRUE(is_offline_page_set_in_navigation_data());
ASSERT_TRUE(offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(offline_ids()[2],
offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_FLAKY_NETWORK);
ExpectAccessEntryPoint(kTestClientId2,
OfflinePageRequestJob::AccessEntryPoint::LINK);
ExpectOfflinePageSizeUniqueSample(kTestClientId2, 0, 1);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, PageNotFoundOnFlakyNetwork) {
SimulateHasNetworkConnectivity(true);
// When custom offline header exists and contains "reason=error", it means
// that net error is hit in last request due to flaky network.
InterceptRequest(kTestUrl2, "GET", kOfflinePageHeader,
std::string(kOfflinePageHeaderReasonKey) + "=" +
kOfflinePageHeaderReasonValueDueToNetError,
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
PAGE_NOT_FOUND_ON_FLAKY_NETWORK);
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, ForceLoadOfflinePageOnConnectedNetwork) {
SimulateHasNetworkConnectivity(true);
// When custom offline header exists and contains value other than
// "reason=error", it means that offline page is forced to load.
InterceptRequest(kTestUrl1, "GET", kOfflinePageHeader,
std::string(kOfflinePageHeaderReasonKey) + "=download",
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(kTestFileSize2, bytes_read());
EXPECT_TRUE(is_offline_page_set_in_navigation_data());
ASSERT_TRUE(offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(offline_ids()[2],
offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_CONNECTED_NETWORK);
ExpectAccessEntryPoint(kTestClientId2,
OfflinePageRequestJob::AccessEntryPoint::DOWNLOADS);
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeUniqueSample(kTestClientId2, 0, 1);
}
TEST_F(OfflinePageRequestJobTest, PageNotFoundOnConnectedNetwork) {
SimulateHasNetworkConnectivity(true);
// When custom offline header exists and contains value other than
// "reason=error", it means that offline page is forced to load.
InterceptRequest(kTestUrl2, "GET", kOfflinePageHeader,
std::string(kOfflinePageHeaderReasonKey) + "=download",
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
PAGE_NOT_FOUND_ON_CONNECTED_NETWORK);
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, DoNotLoadOfflinePageOnConnectedNetwork) {
SimulateHasNetworkConnectivity(true);
InterceptRequest(kTestUrl1, "GET", "", "", content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectNoAccessEntryPoint();
ExpectNoSamplesInAggregatedRequestResult();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, LoadOfflinePageByOfflineID) {
SimulateHasNetworkConnectivity(true);
InterceptRequest(kTestUrl1, "GET", kOfflinePageHeader,
std::string(kOfflinePageHeaderReasonKey) + "=download " +
kOfflinePageHeaderIDKey + "=" +
base::Int64ToString(offline_ids()[1]),
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(kTestFileSize1, bytes_read());
EXPECT_TRUE(is_offline_page_set_in_navigation_data());
ASSERT_TRUE(offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(offline_ids()[1],
offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_CONNECTED_NETWORK);
ExpectAccessEntryPoint(kTestClientId1,
OfflinePageRequestJob::AccessEntryPoint::DOWNLOADS);
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeUniqueSample(kTestClientId1, 0, 1);
}
TEST_F(OfflinePageRequestJobTest, FailToLoadByOfflineIDOnUrlMismatch) {
SimulateHasNetworkConnectivity(true);
// The offline page found with specific offline ID does not match the passed
// online URL. Should fall back to find the offline page based on the online
// URL.
InterceptRequest(kTestUrl2, "GET", kOfflinePageHeader,
std::string(kOfflinePageHeaderReasonKey) + "=download " +
kOfflinePageHeaderIDKey + "=" +
base::Int64ToString(offline_ids()[1]),
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
PAGE_NOT_FOUND_ON_CONNECTED_NETWORK);
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, LoadOfflinePageForUrlWithFragment) {
SimulateHasNetworkConnectivity(false);
// Loads an url with fragment, that will match the offline URL without the
// fragment.
GURL url_with_fragment(kTestUrl1.spec() + "#ref");
InterceptRequest(
url_with_fragment, "GET", "", "", content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(kTestFileSize2, bytes_read());
ASSERT_TRUE(offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(offline_ids()[2],
offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
ExpectOfflinePageSizeUniqueSample(kTestClientId2, 0, 1);
ExpectOfflinePageSizeTotalSuffixCount(1);
ExpectOnlinePageSizeTotalSuffixCount(0);
// Loads an url without fragment, that will match the offline URL with the
// fragment.
InterceptRequest(kTestUrl3, "GET", "", "", content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(kTestFileSize3, bytes_read());
ASSERT_TRUE(offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(offline_ids()[3],
offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
ExpectMultiUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK, 2);
ExpectOfflinePageSizeUniqueSample(kTestClientId2, 0, 1);
ExpectOfflinePageSizeUniqueSample(kTestClientId3, 0, 1);
ExpectOfflinePageSizeTotalSuffixCount(2);
ExpectOnlinePageSizeTotalSuffixCount(0);
// Loads an url with fragment, that will match the offline URL with different
// fragment.
GURL url3_with_different_fragment(kTestUrl3.spec() + "#different_ref");
InterceptRequest(url3_with_different_fragment, "GET", "", "",
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(kTestFileSize3, bytes_read());
ASSERT_TRUE(offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(offline_ids()[3],
offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
ExpectMultiUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK, 3);
ExpectOfflinePageSizeUniqueSample(kTestClientId2, 0, 1);
ExpectOfflinePageSizeUniqueSample(kTestClientId3, 0, 2);
ExpectOfflinePageSizeTotalSuffixCount(3);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, LoadOfflinePageAfterRedirect) {
SimulateHasNetworkConnectivity(false);
// This should trigger redirect first.
InterceptRequest(kTestUrlRedirectsTo3, "GET", "", "",
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(kTestFileSize3, bytes_read());
EXPECT_TRUE(is_offline_page_set_in_navigation_data());
ASSERT_TRUE(offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(offline_ids()[3],
offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
ExpectOneNonuniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
REDIRECTED_ON_DISCONNECTED_NETWORK);
ExpectOneNonuniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
ExpectAccessEntryPoint(kTestClientId3,
OfflinePageRequestJob::AccessEntryPoint::LINK);
ExpectOfflinePageSizeUniqueSample(kTestClientId3, 0, 1);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, NoRedirectForOfflinePageWithSameOriginalURL) {
SimulateHasNetworkConnectivity(false);
// No redirect should be triggered when original URL is same as final URL.
InterceptRequest(kTestUrl5, "GET", "", "", content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(kTestFileSize5, bytes_read());
EXPECT_TRUE(is_offline_page_set_in_navigation_data());
ASSERT_TRUE(offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(offline_ids()[5],
offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
ExpectAccessEntryPoint(kTestClientId5,
OfflinePageRequestJob::AccessEntryPoint::LINK);
ExpectOfflinePageSizeUniqueSample(kTestClientId5, 0, 1);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, LoadOfflinePageFromNonExistentFile) {
SimulateHasNetworkConnectivity(false);
InterceptRequest(kTestUrl4, "GET", "", "", content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_TRUE(is_offline_page_set_in_navigation_data());
ASSERT_TRUE(offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(offline_ids()[4],
offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
ExpectOpenFileErrorCode(net::ERR_FILE_NOT_FOUND);
ExpectAccessEntryPoint(kTestClientId4,
OfflinePageRequestJob::AccessEntryPoint::LINK);
ExpectOfflinePageSizeUniqueSample(kTestClientId4,
kTestFileSize4NonExistent / 1024, 1);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, FileSizeMismatchOnDisconnectedNetwork) {
SimulateHasNetworkConnectivity(false);
InterceptRequest(kTestUrl6, "GET", "", "", content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_DISCONNECTED_NETWORK);
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, FileSizeMismatchOnProhibitivelySlowNetwork) {
SimulateHasNetworkConnectivity(true);
test_previews_decider()->set_should_allow_preview(true);
InterceptRequest(kTestUrl6, "GET", "", "", content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_PROHIBITIVELY_SLOW_NETWORK);
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, FileSizeMismatchOnConnectedNetwork) {
SimulateHasNetworkConnectivity(true);
// When custom offline header exists and contains value other than
// "reason=error", it means that offline page is forced to load.
InterceptRequest(kTestUrl6, "GET", kOfflinePageHeader,
std::string(kOfflinePageHeaderReasonKey) + "=download",
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_CONNECTED_NETWORK);
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, FileSizeMismatchOnFlakyNetwork) {
SimulateHasNetworkConnectivity(true);
// When custom offline header exists and contains "reason=error", it means
// that net error is hit in last request due to flaky network.
InterceptRequest(kTestUrl6, "GET", kOfflinePageHeader,
std::string(kOfflinePageHeaderReasonKey) + "=" +
kOfflinePageHeaderReasonValueDueToNetError,
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_FLAKY_NETWORK);
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, DigestMismatchOnDisconnectedNetwork) {
SimulateHasNetworkConnectivity(false);
InterceptRequest(kTestUrl7, "GET", "", "", content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_DISCONNECTED_NETWORK);
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, DigestMismatchOnProhibitivelySlowNetwork) {
SimulateHasNetworkConnectivity(true);
test_previews_decider()->set_should_allow_preview(true);
InterceptRequest(kTestUrl7, "GET", "", "", content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_PROHIBITIVELY_SLOW_NETWORK);
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, DigestMismatchOnConnectedNetwork) {
SimulateHasNetworkConnectivity(true);
// When custom offline header exists and contains value other than
// "reason=error", it means that offline page is forced to load.
InterceptRequest(kTestUrl7, "GET", kOfflinePageHeader,
std::string(kOfflinePageHeaderReasonKey) + "=download",
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_CONNECTED_NETWORK);
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, DigestMismatchOnFlakyNetwork) {
SimulateHasNetworkConnectivity(true);
// When custom offline header exists and contains "reason=error", it means
// that net error is hit in last request due to flaky network.
InterceptRequest(kTestUrl7, "GET", kOfflinePageHeader,
std::string(kOfflinePageHeaderReasonKey) + "=" +
kOfflinePageHeaderReasonValueDueToNetError,
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_FLAKY_NETWORK);
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, FailOnNoDigestForPublicArchiveFile) {
SimulateHasNetworkConnectivity(false);
InterceptRequest(kTestUrl8, "GET", "", "", content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_DISCONNECTED_NETWORK);
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, FailToLoadByOfflineIDOnDigestMismatch) {
SimulateHasNetworkConnectivity(true);
// The offline page found with specific offline ID does not pass the
// validation. Though there is another page with the same URL, it will not be
// fetched. Instead, fall back to load the online URL.
InterceptRequest(kTestUrl9, "GET", kOfflinePageHeader,
std::string(kOfflinePageHeaderReasonKey) + "=download " +
kOfflinePageHeaderIDKey + "=" +
base::Int64ToString(offline_ids()[10]),
content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_CONNECTED_NETWORK);
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, LoadOtherPageOnDigestMismatch) {
SimulateHasNetworkConnectivity(false);
// There're 2 offline pages matching kTestUrl9. The most recently created one
// (kTestClientId10) should fail on mistmatched digest. The second most
// recently created offline page (kTestClientId9) should be fetched.
InterceptRequest(kTestUrl9, "GET", "", "", content::RESOURCE_TYPE_MAIN_FRAME);
base::RunLoop().Run();
EXPECT_EQ(kTestFileSize9, bytes_read());
EXPECT_TRUE(is_offline_page_set_in_navigation_data());
ASSERT_TRUE(offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(offline_ids()[9],
offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
ExpectAccessEntryPoint(kTestClientId9,
OfflinePageRequestJob::AccessEntryPoint::LINK);
ExpectOfflinePageSizeUniqueSample(kTestClientId9, 0, 1);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
} // namespace offline_pages