blob: 067b590c91de57456bedbe322858c44d736d9ee1 [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 <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/weak_ptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/post_task.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/default_clock.h"
#include "build/build_config.h"
#include "chrome/browser/offline_pages/offline_page_model_factory.h"
#include "chrome/browser/offline_pages/offline_page_tab_helper.h"
#include "chrome/browser/offline_pages/offline_page_url_loader.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/archive_validator.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.h"
#include "components/offline_pages/core/offline_page_test_archive_publisher.h"
#include "components/offline_pages/core/offline_page_test_archiver.h"
#include "components/offline_pages/core/request_header/offline_page_navigation_ui_data.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.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 "mojo/public/cpp/system/wait.h"
#include "net/base/filename_util.h"
#include "net/http/http_request_headers.h"
#include "services/network/public/cpp/resource_request.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#if defined(OS_ANDROID)
#include "components/gcm_driver/instance_id/instance_id_android.h"
#include "components/gcm_driver/instance_id/scoped_use_fake_instance_id_android.h"
#endif // OS_ANDROID
namespace offline_pages {
namespace {
const char kPrivateOfflineFileDir[] = "offline_pages";
const char kPublicOfflineFileDir[] = "public_offline_pages";
const GURL kUrl("http://test.org/page");
const GURL kUrl2("http://test.org/another");
const base::FilePath kFilename1(FILE_PATH_LITERAL("hello.mhtml"));
const base::FilePath kFilename2(FILE_PATH_LITERAL("welcome.mhtml"));
const base::FilePath kNonexistentFilename(
FILE_PATH_LITERAL("nonexistent.mhtml"));
const int kFileSize1 = 471; // Real size of hello.mhtml.
const int kFileSize2 = 461; // Real size of welcome.mhtml.
const int kMismatchedFileSize = 99999;
const std::string kDigest1(
"\x43\x60\x62\x02\x06\x15\x0f\x3e\x77\x99\x3d\xed\xdc\xd4\xe2\x0d\xbe\xbd"
"\x77\x1a\xfb\x32\x00\x51\x7e\x63\x7d\x3b\x2e\x46\x63\xf6",
32); // SHA256 Hash of hello.mhtml.
const std::string kDigest2(
"\xBD\xD3\x37\x79\xDA\x7F\x4E\x6A\x16\x66\xED\x49\x67\x18\x54\x48\xC6\x8E"
"\xA1\x47\x16\xA5\x44\x45\x43\xD0\x0E\x04\x9F\x4C\x45\xDC",
32); // SHA256 Hash of welcome.mhtml.
const std::string kMismatchedDigest(
"\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.
const int kTabId = 1;
const char kAggregatedRequestResultHistogram[] =
"OfflinePages.AggregatedRequestResult2";
const char kAccessEntryPointHistogram[] = "OfflinePages.AccessEntryPoint.";
const char kPageSizeAccessOfflineHistogramBase[] =
"OfflinePages.PageSizeOnAccess.Offline.";
const char kPageSizeAccessOnlineHistogramBase[] =
"OfflinePages.PageSizeOnAccess.Online.";
const int64_t kDownloadId = 42LL;
struct ResponseInfo {
explicit ResponseInfo(int request_status) : request_status(request_status) {
DCHECK_NE(net::OK, request_status);
}
ResponseInfo(int request_status,
const std::string& mime_type,
const std::string& data_received)
: request_status(request_status),
mime_type(mime_type),
data_received(data_received) {}
int request_status;
std::string mime_type;
std::string data_received;
};
bool GetTabId(int tab_id_value,
content::WebContents* web_content,
int* tab_id) {
*tab_id = tab_id_value;
return true;
}
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;
}
bool online() const { return online_; }
void set_online(bool online) { online_ = online; }
private:
bool online_;
DISALLOW_COPY_AND_ASSIGN(TestNetworkChangeNotifier);
};
class TestURLLoaderClient : public network::mojom::URLLoaderClient {
public:
class Observer {
public:
virtual void OnReceiveRedirect(const GURL& redirected_url) = 0;
virtual void OnReceiveResponse(
const network::ResourceResponseHead& response_head) = 0;
virtual void OnStartLoadingResponseBody() = 0;
virtual void OnComplete() = 0;
protected:
virtual ~Observer() {}
};
explicit TestURLLoaderClient(Observer* observer)
: observer_(observer), binding_(this) {}
~TestURLLoaderClient() override {}
void OnReceiveResponse(
const network::ResourceResponseHead& response_head) override {
observer_->OnReceiveResponse(response_head);
}
void OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head) override {
observer_->OnReceiveRedirect(redirect_info.new_url);
}
void OnReceiveCachedMetadata(mojo_base::BigBuffer data) override {}
void OnTransferSizeUpdated(int32_t transfer_size_diff) override {}
void OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback ack_callback) override {}
void OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) override {
response_body_ = std::move(body);
observer_->OnStartLoadingResponseBody();
}
void OnComplete(const network::URLLoaderCompletionStatus& status) override {
completion_status_ = status;
observer_->OnComplete();
}
network::mojom::URLLoaderClientPtr CreateInterfacePtr() {
network::mojom::URLLoaderClientPtr client_ptr;
binding_.Bind(mojo::MakeRequest(&client_ptr));
binding_.set_connection_error_handler(base::BindOnce(
&TestURLLoaderClient::OnConnectionError, base::Unretained(this)));
return client_ptr;
}
mojo::DataPipeConsumerHandle response_body() { return response_body_.get(); }
const network::URLLoaderCompletionStatus& completion_status() const {
return completion_status_;
}
private:
void OnConnectionError() {}
Observer* observer_ = nullptr;
mojo::Binding<network::mojom::URLLoaderClient> binding_;
mojo::ScopedDataPipeConsumerHandle response_body_;
network::URLLoaderCompletionStatus completion_status_;
DISALLOW_COPY_AND_ASSIGN(TestURLLoaderClient);
};
// Helper function to make a character array filled with |size| bytes of
// test content.
std::string MakeContentOfSize(int size) {
EXPECT_GE(size, 0);
std::string result;
result.reserve(size);
for (int i = 0; i < size; i++)
result.append(1, static_cast<char>(i % 256));
return result;
}
static network::ResourceRequest CreateResourceRequest(
const GURL& url,
const std::string& method,
const net::HttpRequestHeaders& extra_headers,
bool is_main_frame) {
network::ResourceRequest request;
request.method = method;
request.headers = extra_headers;
request.url = url;
request.is_main_frame = is_main_frame;
return request;
}
} // namespace
class OfflinePageRequestHandlerTest;
// Builds an OfflinePageURLLoader to test the request interception with network
// service enabled.
class OfflinePageURLLoaderBuilder : public TestURLLoaderClient::Observer {
public:
explicit OfflinePageURLLoaderBuilder(OfflinePageRequestHandlerTest* test);
void OnReceiveRedirect(const GURL& redirected_url) override;
void OnReceiveResponse(
const network::ResourceResponseHead& response_head) override;
void OnStartLoadingResponseBody() override;
void OnComplete() override;
void InterceptRequest(const GURL& url,
const std::string& method,
const net::HttpRequestHeaders& extra_headers,
bool is_main_frame);
OfflinePageRequestHandlerTest* test() { return test_; }
private:
void OnHandleReady(MojoResult result, const mojo::HandleSignalsState& state);
void InterceptRequestInternal(const GURL& url,
const std::string& method,
const net::HttpRequestHeaders& extra_headers,
bool is_main_frame);
void MaybeStartLoader(
const network::ResourceRequest& request,
content::URLLoaderRequestInterceptor::RequestHandler request_handler);
void ReadBody();
void ReadCompleted(const ResponseInfo& response);
OfflinePageRequestHandlerTest* test_;
std::unique_ptr<ChromeNavigationUIData> navigation_ui_data_;
std::unique_ptr<OfflinePageURLLoader> url_loader_;
std::unique_ptr<TestURLLoaderClient> client_;
std::unique_ptr<mojo::SimpleWatcher> handle_watcher_;
network::mojom::URLLoaderPtr loader_;
std::string mime_type_;
std::string body_;
};
class OfflinePageRequestHandlerTest : public testing::Test {
public:
OfflinePageRequestHandlerTest();
~OfflinePageRequestHandlerTest() override {}
void SetUp() override;
void TearDown() override;
void InterceptRequest(const GURL& url,
const std::string& method,
const net::HttpRequestHeaders& extra_headers,
bool is_main_frame);
void SimulateHasNetworkConnectivity(bool has_connectivity);
void RunUntilIdle();
void WaitForAsyncOperation();
base::FilePath CreateFileWithContent(const std::string& content);
// Returns an offline id of the saved page.
// |file_path| in SavePublicPage and SaveInternalPage can be either absolute
// or relative. If relative, |file_path| will be appended to public/internal
// archive directory used for the testing.
// |file_path| in SavePage should be absolute.
int64_t SavePublicPage(const GURL& url,
const GURL& original_url,
const base::FilePath& file_path,
int64_t file_size,
const std::string& digest);
int64_t SaveInternalPage(const GURL& url,
const GURL& original_url,
const base::FilePath& file_path,
int64_t file_size,
const std::string& digest);
int64_t SavePage(const GURL& url,
const GURL& original_url,
const base::FilePath& file_path,
int64_t file_size,
const std::string& digest);
OfflinePageItem GetPage(int64_t offline_id);
void LoadPage(const GURL& url);
void LoadPageWithHeaders(const GURL& url,
const net::HttpRequestHeaders& extra_headers);
void ReadCompleted(const ResponseInfo& reponse,
bool is_offline_page_set_in_navigation_data);
// Expect exactly one count of |result| UMA reported. No other bucket should
// have sample.
void ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestHandler::AggregatedRequestResult result);
// Expect exactly |count| of |result| UMA reported. No other bucket should
// have sample.
void ExpectMultiUniqueSampleForAggregatedRequestResult(
OfflinePageRequestHandler::AggregatedRequestResult result,
int count);
// Expect one count of |result| UMA reported. Other buckets may have samples
// as well.
void ExpectOneNonuniqueSampleForAggregatedRequestResult(
OfflinePageRequestHandler::AggregatedRequestResult result);
// Expect no samples to have been reported to the aggregated results
// histogram.
void ExpectNoSamplesInAggregatedRequestResult();
void ExpectAccessEntryPoint(
OfflinePageRequestHandler::AccessEntryPoint entry_point);
void ExpectNoAccessEntryPoint();
void ExpectOfflinePageSizeUniqueSample(int bucket, int count);
void ExpectOfflinePageSizeTotalSuffixCount(int count);
void ExpectOnlinePageSizeUniqueSample(int bucket, int count);
void ExpectOnlinePageSizeTotalSuffixCount(int count);
void ExpectOfflinePageAccessCount(int64_t offline_id, int count);
void ExpectNoOfflinePageServed(
int64_t offline_id,
OfflinePageRequestHandler::AggregatedRequestResult
expected_request_result);
void ExpectOfflinePageServed(
int64_t expected_offline_id,
int expected_file_size,
OfflinePageRequestHandler::AggregatedRequestResult
expected_request_result);
// Use the offline header with specific reason and offline_id. Return the
// full header string.
std::string UseOfflinePageHeader(OfflinePageHeader::Reason reason,
int64_t offline_id);
std::string UseOfflinePageHeaderForIntent(OfflinePageHeader::Reason reason,
int64_t offline_id,
const GURL& intent_url);
Profile* profile() { return profile_; }
content::WebContents* web_contents() const { return web_contents_.get(); }
OfflinePageTabHelper* offline_page_tab_helper() const {
return offline_page_tab_helper_;
}
int request_status() const { return response_.request_status; }
int bytes_read() const { return response_.data_received.length(); }
const std::string& data_received() const { return response_.data_received; }
const std::string& mime_type() const { return response_.mime_type; }
bool is_offline_page_set_in_navigation_data() const {
return is_offline_page_set_in_navigation_data_;
}
bool is_connected_with_good_network() {
return network_change_notifier_->online() &&
// Exclude prohibitively slow network.
!allow_preview() &&
// Exclude flaky network.
offline_page_header_.reason != OfflinePageHeader::Reason::NET_ERROR;
}
void set_allow_preview(bool allow_preview) { allow_preview_ = allow_preview; }
bool allow_preview() const { return allow_preview_; }
private:
static std::unique_ptr<KeyedService> BuildTestOfflinePageModel(
SimpleFactoryKey* key);
// TODO(https://crbug.com/809610): The static members below will be removed
// once the reference to BuildTestOfflinePageModel in SetUp is converted to a
// base::OnceCallback.
static base::FilePath private_archives_dir_;
static base::FilePath public_archives_dir_;
OfflinePageRequestHandler::AccessEntryPoint GetExpectedAccessEntryPoint()
const;
void OnSavePageDone(SavePageResult result, int64_t offline_id);
void OnGetPageByOfflineIdDone(const OfflinePageItem* pages);
// Runs on IO thread.
void CreateFileWithContentOnIO(const std::string& content,
const base::Closure& callback);
content::TestBrowserThreadBundle thread_bundle_;
TestingProfileManager profile_manager_;
TestingProfile* profile_;
std::unique_ptr<content::WebContents> web_contents_;
std::unique_ptr<base::HistogramTester> histogram_tester_;
OfflinePageTabHelper* offline_page_tab_helper_; // Not owned.
int64_t last_offline_id_;
ResponseInfo response_;
bool is_offline_page_set_in_navigation_data_;
OfflinePageItem page_;
OfflinePageHeader offline_page_header_;
#if defined(OS_ANDROID)
// OfflinePageTabHelper instantiates PrefetchService which in turn requests a
// fresh GCM token automatically. These two lines mock out InstanceID (the
// component which actually requests the token from play services). Without
// this, each test takes an extra 30s waiting on the token (because
// content::TestBrowserThreadBundle tries to finish all pending tasks before
// ending the test).
instance_id::InstanceIDAndroid::ScopedBlockOnAsyncTasksForTesting
block_async_;
instance_id::ScopedUseFakeInstanceIDAndroid use_fake_;
#endif // OS_ANDROID
// 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_;
bool allow_preview_ = false;
// These should only be accessed purely from IO thread.
base::ScopedTempDir private_archives_temp_base_dir_;
base::ScopedTempDir public_archives_temp_base_dir_;
base::ScopedTempDir temp_dir_;
base::FilePath temp_file_path_;
int file_name_sequence_num_ = 0;
bool async_operation_completed_ = false;
base::Closure async_operation_completed_callback_;
OfflinePageURLLoaderBuilder interceptor_factory_;
DISALLOW_COPY_AND_ASSIGN(OfflinePageRequestHandlerTest);
};
OfflinePageRequestHandlerTest::OfflinePageRequestHandlerTest()
: thread_bundle_(content::TestBrowserThreadBundle::REAL_IO_THREAD),
profile_manager_(TestingBrowserProcess::GetGlobal()),
last_offline_id_(0),
response_(net::ERR_IO_PENDING),
is_offline_page_set_in_navigation_data_(false),
network_change_notifier_(new TestNetworkChangeNotifier),
interceptor_factory_(this) {}
void OfflinePageRequestHandlerTest::SetUp() {
// Create a test profile.
ASSERT_TRUE(profile_manager_.SetUp());
profile_ = profile_manager_.CreateTestingProfile("Profile 1");
// Create a test web contents.
web_contents_ = 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.
// Note: The extra dir into the temp folder is needed so that the helper
// dir-copy operation works properly. That operation copies the source dir
// final path segment into the destination, and not only its immediate
// contents so this same-named path here makes the archive dir variable point
// to the correct location.
// TODO(romax): add the more recent "temporary" dir here instead of reusing
// the private one.
ASSERT_TRUE(private_archives_temp_base_dir_.CreateUniqueTempDir());
private_archives_dir_ = private_archives_temp_base_dir_.GetPath().AppendASCII(
kPrivateOfflineFileDir);
ASSERT_TRUE(public_archives_temp_base_dir_.CreateUniqueTempDir());
public_archives_dir_ = public_archives_temp_base_dir_.GetPath().AppendASCII(
kPublicOfflineFileDir);
OfflinePageModelFactory::GetInstance()->SetTestingFactoryAndUse(
profile()->GetProfileKey(),
base::BindRepeating(
&OfflinePageRequestHandlerTest::BuildTestOfflinePageModel));
// Initialize OfflinePageModel.
OfflinePageModelTaskified* model = static_cast<OfflinePageModelTaskified*>(
OfflinePageModelFactory::GetForBrowserContext(profile()));
// 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();
// Avoid running the model's maintenance tasks.
model->DoNotRunMaintenanceTasksForTesting();
// Move test data files into their respective temporary test directories. The
// model's maintenance tasks must not be executed in the meantime otherwise
// these files will be wiped by consistency checks.
base::FilePath test_data_dir_path;
base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_path);
base::FilePath test_data_private_archives_dir =
test_data_dir_path.AppendASCII(kPrivateOfflineFileDir);
ASSERT_TRUE(base::CopyDirectory(test_data_private_archives_dir,
private_archives_dir_.DirName(), true));
base::FilePath test_data_public_archives_dir =
test_data_dir_path.AppendASCII(kPublicOfflineFileDir);
ASSERT_TRUE(base::CopyDirectory(test_data_public_archives_dir,
public_archives_dir_.DirName(), true));
histogram_tester_ = std::make_unique<base::HistogramTester>();
}
void OfflinePageRequestHandlerTest::TearDown() {
EXPECT_TRUE(private_archives_temp_base_dir_.Delete());
EXPECT_TRUE(public_archives_temp_base_dir_.Delete());
// This check confirms that the model's maintenance tasks were not executed
// during the test run.
histogram_tester_->ExpectTotalCount("OfflinePages.ClearTemporaryPages.Result",
0);
}
void OfflinePageRequestHandlerTest::InterceptRequest(
const GURL& url,
const std::string& method,
const net::HttpRequestHeaders& extra_headers,
bool is_main_frame) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
interceptor_factory_.InterceptRequest(url, method, extra_headers,
is_main_frame);
}
void OfflinePageRequestHandlerTest::SimulateHasNetworkConnectivity(
bool online) {
network_change_notifier_->set_online(online);
}
void OfflinePageRequestHandlerTest::RunUntilIdle() {
base::RunLoop().RunUntilIdle();
}
void OfflinePageRequestHandlerTest::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 OfflinePageRequestHandlerTest::CreateFileWithContentOnIO(
const std::string& content,
const base::Closure& callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (!temp_dir_.IsValid()) {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
}
std::string file_name("test");
file_name += base::NumberToString(file_name_sequence_num_++);
file_name += ".mht";
temp_file_path_ = temp_dir_.GetPath().AppendASCII(file_name);
ASSERT_NE(base::WriteFile(temp_file_path_, content.c_str(), content.length()),
-1);
callback.Run();
}
base::FilePath OfflinePageRequestHandlerTest::CreateFileWithContent(
const std::string& content) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::RunLoop run_loop;
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::IO},
base::BindOnce(&OfflinePageRequestHandlerTest::CreateFileWithContentOnIO,
base::Unretained(this), content, run_loop.QuitClosure()));
run_loop.Run();
return temp_file_path_;
}
void OfflinePageRequestHandlerTest::
ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestHandler::AggregatedRequestResult result) {
histogram_tester_->ExpectUniqueSample(kAggregatedRequestResultHistogram,
static_cast<int>(result), 1);
}
void OfflinePageRequestHandlerTest::
ExpectMultiUniqueSampleForAggregatedRequestResult(
OfflinePageRequestHandler::AggregatedRequestResult result,
int count) {
histogram_tester_->ExpectUniqueSample(kAggregatedRequestResultHistogram,
static_cast<int>(result), count);
}
void OfflinePageRequestHandlerTest::
ExpectOneNonuniqueSampleForAggregatedRequestResult(
OfflinePageRequestHandler::AggregatedRequestResult result) {
histogram_tester_->ExpectBucketCount(kAggregatedRequestResultHistogram,
static_cast<int>(result), 1);
}
void OfflinePageRequestHandlerTest::ExpectNoSamplesInAggregatedRequestResult() {
histogram_tester_->ExpectTotalCount(kAggregatedRequestResultHistogram, 0);
}
void OfflinePageRequestHandlerTest::ExpectAccessEntryPoint(
OfflinePageRequestHandler::AccessEntryPoint entry_point) {
histogram_tester_->ExpectUniqueSample(
std::string(kAccessEntryPointHistogram) + kDownloadNamespace,
static_cast<int>(entry_point), 1);
}
void OfflinePageRequestHandlerTest::ExpectNoAccessEntryPoint() {
EXPECT_TRUE(
histogram_tester_->GetTotalCountsForPrefix(kAccessEntryPointHistogram)
.empty());
}
void OfflinePageRequestHandlerTest::ExpectOfflinePageSizeUniqueSample(
int bucket,
int count) {
histogram_tester_->ExpectUniqueSample(
std::string(kPageSizeAccessOfflineHistogramBase) + kDownloadNamespace,
bucket, count);
}
void OfflinePageRequestHandlerTest::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 OfflinePageRequestHandlerTest::ExpectOnlinePageSizeUniqueSample(
int bucket,
int count) {
histogram_tester_->ExpectUniqueSample(
std::string(kPageSizeAccessOnlineHistogramBase) + kDownloadNamespace,
bucket, count);
}
void OfflinePageRequestHandlerTest::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 OfflinePageRequestHandlerTest::ExpectOfflinePageAccessCount(
int64_t offline_id,
int count) {
OfflinePageItem offline_page = GetPage(offline_id);
EXPECT_EQ(count, offline_page.access_count);
}
void OfflinePageRequestHandlerTest::ExpectNoOfflinePageServed(
int64_t offline_id,
OfflinePageRequestHandler::AggregatedRequestResult
expected_request_result) {
EXPECT_NE("multipart/related", mime_type());
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
if (expected_request_result !=
OfflinePageRequestHandler::AggregatedRequestResult::
AGGREGATED_REQUEST_RESULT_MAX) {
ExpectOneUniqueSampleForAggregatedRequestResult(expected_request_result);
}
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
ExpectOfflinePageAccessCount(offline_id, 0);
}
void OfflinePageRequestHandlerTest::ExpectOfflinePageServed(
int64_t expected_offline_id,
int expected_file_size,
OfflinePageRequestHandler::AggregatedRequestResult
expected_request_result) {
EXPECT_EQ(net::OK, request_status());
EXPECT_EQ("multipart/related", mime_type());
EXPECT_EQ(expected_file_size, bytes_read());
EXPECT_TRUE(is_offline_page_set_in_navigation_data());
ASSERT_TRUE(offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(expected_offline_id,
offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
OfflinePageTrustedState expected_trusted_state =
private_archives_dir_.IsParent(
offline_page_tab_helper()->GetOfflinePageForTest()->file_path)
? OfflinePageTrustedState::TRUSTED_AS_IN_INTERNAL_DIR
: OfflinePageTrustedState::TRUSTED_AS_UNMODIFIED_AND_IN_PUBLIC_DIR;
EXPECT_EQ(expected_trusted_state,
offline_page_tab_helper()->GetTrustedStateForTest());
if (expected_request_result !=
OfflinePageRequestHandler::AggregatedRequestResult::
AGGREGATED_REQUEST_RESULT_MAX) {
ExpectOneUniqueSampleForAggregatedRequestResult(expected_request_result);
}
OfflinePageRequestHandler::AccessEntryPoint expected_entry_point =
GetExpectedAccessEntryPoint();
ExpectAccessEntryPoint(expected_entry_point);
if (is_connected_with_good_network()) {
ExpectOnlinePageSizeUniqueSample(expected_file_size / 1024, 1);
ExpectOfflinePageSizeTotalSuffixCount(0);
} else {
ExpectOfflinePageSizeUniqueSample(expected_file_size / 1024, 1);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
ExpectOfflinePageAccessCount(expected_offline_id, 1);
}
OfflinePageRequestHandler::AccessEntryPoint
OfflinePageRequestHandlerTest::GetExpectedAccessEntryPoint() const {
switch (offline_page_header_.reason) {
case OfflinePageHeader::Reason::DOWNLOAD:
return OfflinePageRequestHandler::AccessEntryPoint::DOWNLOADS;
case OfflinePageHeader::Reason::NOTIFICATION:
return OfflinePageRequestHandler::AccessEntryPoint::NOTIFICATION;
case OfflinePageHeader::Reason::FILE_URL_INTENT:
return OfflinePageRequestHandler::AccessEntryPoint::FILE_URL_INTENT;
case OfflinePageHeader::Reason::CONTENT_URL_INTENT:
return OfflinePageRequestHandler::AccessEntryPoint::CONTENT_URL_INTENT;
case OfflinePageHeader::Reason::NET_ERROR_SUGGESTION:
return OfflinePageRequestHandler::AccessEntryPoint::NET_ERROR_PAGE;
default:
return OfflinePageRequestHandler::AccessEntryPoint::LINK;
}
}
std::string OfflinePageRequestHandlerTest::UseOfflinePageHeader(
OfflinePageHeader::Reason reason,
int64_t offline_id) {
DCHECK_NE(OfflinePageHeader::Reason::NONE, reason);
offline_page_header_.reason = reason;
if (offline_id)
offline_page_header_.id = base::NumberToString(offline_id);
return offline_page_header_.GetCompleteHeaderString();
}
std::string OfflinePageRequestHandlerTest::UseOfflinePageHeaderForIntent(
OfflinePageHeader::Reason reason,
int64_t offline_id,
const GURL& intent_url) {
DCHECK_NE(OfflinePageHeader::Reason::NONE, reason);
DCHECK(offline_id);
offline_page_header_.reason = reason;
offline_page_header_.id = base::NumberToString(offline_id);
offline_page_header_.intent_url = intent_url;
return offline_page_header_.GetCompleteHeaderString();
}
int64_t OfflinePageRequestHandlerTest::SavePublicPage(
const GURL& url,
const GURL& original_url,
const base::FilePath& file_path,
int64_t file_size,
const std::string& digest) {
base::FilePath final_path;
if (file_path.IsAbsolute()) {
final_path = file_path;
} else {
final_path = public_archives_dir_.Append(file_path);
}
return SavePage(url, original_url, final_path, file_size, digest);
}
int64_t OfflinePageRequestHandlerTest::SaveInternalPage(
const GURL& url,
const GURL& original_url,
const base::FilePath& file_path,
int64_t file_size,
const std::string& digest) {
base::FilePath final_path;
if (file_path.IsAbsolute()) {
final_path = file_path;
} else {
final_path = private_archives_dir_.Append(file_path);
}
return SavePage(url, original_url, final_path, file_size, digest);
}
int64_t OfflinePageRequestHandlerTest::SavePage(const GURL& url,
const GURL& original_url,
const base::FilePath& file_path,
int64_t file_size,
const std::string& digest) {
DCHECK(file_path.IsAbsolute());
static int item_counter = 0;
++item_counter;
auto archiver = std::make_unique<OfflinePageTestArchiver>(
nullptr, url, OfflinePageArchiver::ArchiverResult::SUCCESSFULLY_CREATED,
base::string16(), file_size, digest, base::ThreadTaskRunnerHandle::Get());
archiver->set_filename(file_path);
async_operation_completed_ = false;
OfflinePageModel::SavePageParams save_page_params;
save_page_params.url = url;
save_page_params.client_id =
ClientId(kDownloadNamespace, base::NumberToString(item_counter));
save_page_params.original_url = original_url;
OfflinePageModelFactory::GetForBrowserContext(profile())->SavePage(
save_page_params, std::move(archiver), nullptr,
base::Bind(&OfflinePageRequestHandlerTest::OnSavePageDone,
base::Unretained(this)));
WaitForAsyncOperation();
return last_offline_id_;
}
// static
std::unique_ptr<KeyedService>
OfflinePageRequestHandlerTest::BuildTestOfflinePageModel(
SimpleFactoryKey* key) {
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
base::ThreadTaskRunnerHandle::Get();
base::FilePath store_path =
key->GetPath().Append(chrome::kOfflinePageMetadataDirname);
std::unique_ptr<OfflinePageMetadataStore> metadata_store(
new OfflinePageMetadataStore(task_runner, store_path));
// Since we're not saving page into temporary dir, it's set the same as the
// private dir.
auto archive_manager = std::make_unique<ArchiveManager>(
private_archives_dir_, private_archives_dir_, public_archives_dir_,
task_runner);
auto archive_publisher = std::make_unique<OfflinePageTestArchivePublisher>(
archive_manager.get(), kDownloadId);
// TODO(iwells): Figure out how to make use_verbatim_archive_path go away.
archive_publisher->use_verbatim_archive_path(true);
return std::unique_ptr<KeyedService>(new OfflinePageModelTaskified(
std::move(metadata_store), std::move(archive_manager),
std::move(archive_publisher), task_runner));
}
// static
base::FilePath OfflinePageRequestHandlerTest::private_archives_dir_;
base::FilePath OfflinePageRequestHandlerTest::public_archives_dir_;
void OfflinePageRequestHandlerTest::OnSavePageDone(SavePageResult result,
int64_t offline_id) {
ASSERT_EQ(SavePageResult::SUCCESS, result);
last_offline_id_ = offline_id;
async_operation_completed_ = true;
if (!async_operation_completed_callback_.is_null())
async_operation_completed_callback_.Run();
}
OfflinePageItem OfflinePageRequestHandlerTest::GetPage(int64_t offline_id) {
OfflinePageModelFactory::GetForBrowserContext(profile())->GetPageByOfflineId(
offline_id,
base::Bind(&OfflinePageRequestHandlerTest::OnGetPageByOfflineIdDone,
base::Unretained(this)));
RunUntilIdle();
return page_;
}
void OfflinePageRequestHandlerTest::OnGetPageByOfflineIdDone(
const OfflinePageItem* page) {
ASSERT_TRUE(page);
page_ = *page;
}
void OfflinePageRequestHandlerTest::LoadPage(const GURL& url) {
InterceptRequest(url, "GET", net::HttpRequestHeaders(),
true /* is_main_frame */);
}
void OfflinePageRequestHandlerTest::LoadPageWithHeaders(
const GURL& url,
const net::HttpRequestHeaders& extra_headers) {
InterceptRequest(url, "GET", extra_headers, true /* is_main_frame */);
}
void OfflinePageRequestHandlerTest::ReadCompleted(
const ResponseInfo& response,
bool is_offline_page_set_in_navigation_data) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
response_ = response;
is_offline_page_set_in_navigation_data_ =
is_offline_page_set_in_navigation_data;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated());
}
OfflinePageURLLoaderBuilder::OfflinePageURLLoaderBuilder(
OfflinePageRequestHandlerTest* test)
: test_(test) {
navigation_ui_data_ = std::make_unique<ChromeNavigationUIData>();
}
void OfflinePageURLLoaderBuilder::OnReceiveRedirect(
const GURL& redirected_url) {
InterceptRequestInternal(redirected_url, "GET", net::HttpRequestHeaders(),
true);
}
void OfflinePageURLLoaderBuilder::OnReceiveResponse(
const network::ResourceResponseHead& response_head) {
mime_type_ = response_head.mime_type;
}
void OfflinePageURLLoaderBuilder::OnStartLoadingResponseBody() {
ReadBody();
}
void OfflinePageURLLoaderBuilder::OnComplete() {
if (client_->completion_status().error_code != net::OK) {
mime_type_.clear();
body_.clear();
}
ReadCompleted(
ResponseInfo(client_->completion_status().error_code, mime_type_, body_));
// Clear intermediate data in preparation for next potential page loading.
mime_type_.clear();
body_.clear();
}
void OfflinePageURLLoaderBuilder::InterceptRequestInternal(
const GURL& url,
const std::string& method,
const net::HttpRequestHeaders& extra_headers,
bool is_main_frame) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
client_ = std::make_unique<TestURLLoaderClient>(this);
network::ResourceRequest request =
CreateResourceRequest(url, method, extra_headers, is_main_frame);
request.previews_state =
test_->allow_preview() ? content::OFFLINE_PAGE_ON : content::PREVIEWS_OFF;
url_loader_ = OfflinePageURLLoader::Create(
navigation_ui_data_.get(),
test_->web_contents()->GetMainFrame()->GetFrameTreeNodeId(), request,
base::BindOnce(&OfflinePageURLLoaderBuilder::MaybeStartLoader,
base::Unretained(this), request));
// |url_loader_| may not be created.
if (!url_loader_)
return;
url_loader_->SetTabIdGetterForTesting(base::BindRepeating(&GetTabId, kTabId));
}
void OfflinePageURLLoaderBuilder::InterceptRequest(
const GURL& url,
const std::string& method,
const net::HttpRequestHeaders& extra_headers,
bool is_main_frame) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
InterceptRequestInternal(url, method, extra_headers, is_main_frame);
base::RunLoop().Run();
}
void OfflinePageURLLoaderBuilder::MaybeStartLoader(
const network::ResourceRequest& request,
content::URLLoaderRequestInterceptor::RequestHandler request_handler) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!request_handler) {
ReadCompleted(ResponseInfo(net::ERR_FAILED));
return;
}
// OfflinePageURLLoader decides to handle the request as offline page. Since
// now, OfflinePageURLLoader will own itself and live as long as its URLLoader
// and URLLoaderClientPtr are alive.
url_loader_.release();
std::move(request_handler)
.Run(request, mojo::MakeRequest(&loader_), client_->CreateInterfacePtr());
}
void OfflinePageURLLoaderBuilder::ReadBody() {
while (true) {
MojoHandle consumer = client_->response_body().value();
const void* buffer;
uint32_t num_bytes;
MojoResult rv = MojoBeginReadData(consumer, nullptr, &buffer, &num_bytes);
if (rv == MOJO_RESULT_SHOULD_WAIT) {
handle_watcher_ = std::make_unique<mojo::SimpleWatcher>(
FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC,
base::SequencedTaskRunnerHandle::Get());
handle_watcher_->Watch(
client_->response_body(),
MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
MOJO_WATCH_CONDITION_SATISFIED,
base::BindRepeating(&OfflinePageURLLoaderBuilder::OnHandleReady,
base::Unretained(this)));
return;
}
// The pipe was closed.
if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
ReadCompleted(ResponseInfo(net::ERR_FAILED));
return;
}
CHECK_EQ(rv, MOJO_RESULT_OK);
body_.append(static_cast<const char*>(buffer), num_bytes);
MojoEndReadData(consumer, num_bytes, nullptr);
}
}
void OfflinePageURLLoaderBuilder::OnHandleReady(
MojoResult result,
const mojo::HandleSignalsState& state) {
if (result != MOJO_RESULT_OK) {
ReadCompleted(ResponseInfo(net::ERR_FAILED));
return;
}
ReadBody();
}
void OfflinePageURLLoaderBuilder::ReadCompleted(const ResponseInfo& response) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
handle_watcher_.reset();
client_.reset();
url_loader_.reset();
loader_.reset();
bool is_offline_page_set_in_navigation_data = false;
offline_pages::OfflinePageNavigationUIData* offline_page_data =
navigation_ui_data_->GetOfflinePageNavigationUIData();
if (offline_page_data && offline_page_data->is_offline_page())
is_offline_page_set_in_navigation_data = true;
test()->ReadCompleted(response, is_offline_page_set_in_navigation_data);
}
TEST_F(OfflinePageRequestHandlerTest, FailedToCreateRequestJob) {
this->SimulateHasNetworkConnectivity(false);
// Must be http/https URL.
this->InterceptRequest(GURL("ftp://host/doc"), "GET",
net::HttpRequestHeaders(), true /* is_main_frame */);
EXPECT_EQ(0, this->bytes_read());
EXPECT_FALSE(this->offline_page_tab_helper()->GetOfflinePageForTest());
this->InterceptRequest(GURL("file:///path/doc"), "GET",
net::HttpRequestHeaders(), true /* is_main_frame */);
EXPECT_EQ(0, this->bytes_read());
EXPECT_FALSE(this->offline_page_tab_helper()->GetOfflinePageForTest());
// Must be GET method.
this->InterceptRequest(kUrl, "POST", net::HttpRequestHeaders(),
true /* is_main_frame */);
EXPECT_EQ(0, this->bytes_read());
EXPECT_FALSE(this->offline_page_tab_helper()->GetOfflinePageForTest());
this->InterceptRequest(kUrl, "HEAD", net::HttpRequestHeaders(),
true /* is_main_frame */);
EXPECT_EQ(0, this->bytes_read());
EXPECT_FALSE(this->offline_page_tab_helper()->GetOfflinePageForTest());
// Must be main resource.
this->InterceptRequest(kUrl, "POST", net::HttpRequestHeaders(),
false /* is_main_frame */);
EXPECT_EQ(0, this->bytes_read());
EXPECT_FALSE(this->offline_page_tab_helper()->GetOfflinePageForTest());
this->ExpectNoSamplesInAggregatedRequestResult();
this->ExpectOfflinePageSizeTotalSuffixCount(0);
this->ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestHandlerTest, LoadOfflinePageOnDisconnectedNetwork) {
this->SimulateHasNetworkConnectivity(false);
int64_t offline_id = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
this->LoadPage(kUrl);
this->ExpectOfflinePageServed(
offline_id, kFileSize1,
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest, PageNotFoundOnDisconnectedNetwork) {
this->SimulateHasNetworkConnectivity(false);
int64_t offline_id = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
this->LoadPage(kUrl2);
this->ExpectNoOfflinePageServed(
offline_id, OfflinePageRequestHandler::AggregatedRequestResult::
PAGE_NOT_FOUND_ON_DISCONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest,
NetErrorPageSuggestionOnDisconnectedNetwork) {
this->SimulateHasNetworkConnectivity(false);
int64_t offline_id = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(this->UseOfflinePageHeader(
OfflinePageHeader::Reason::NET_ERROR_SUGGESTION, 0));
this->LoadPageWithHeaders(kUrl, extra_headers);
this->ExpectOfflinePageServed(
offline_id, kFileSize1,
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest,
LoadOfflinePageOnProhibitivelySlowNetwork) {
this->SimulateHasNetworkConnectivity(true);
this->set_allow_preview(true);
int64_t offline_id = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
this->LoadPage(kUrl);
this->ExpectOfflinePageServed(
offline_id, kFileSize1,
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_PROHIBITIVELY_SLOW_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest,
DontLoadReloadOfflinePageOnProhibitivelySlowNetwork) {
this->SimulateHasNetworkConnectivity(true);
this->set_allow_preview(true);
int64_t offline_id = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
// Treat this as a reloaded page.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
this->UseOfflinePageHeader(OfflinePageHeader::Reason::RELOAD, 0));
this->LoadPageWithHeaders(kUrl, extra_headers);
// The existentce of RELOAD header will force to treat the network as
// connected regardless current network condition. So we will fall back to
// the default handling immediately and no request result should be reported.
// Passing AGGREGATED_REQUEST_RESULT_MAX to skip checking request result in
// the helper function.
this->ExpectNoOfflinePageServed(
offline_id, OfflinePageRequestHandler::AggregatedRequestResult::
AGGREGATED_REQUEST_RESULT_MAX);
}
TEST_F(OfflinePageRequestHandlerTest, PageNotFoundOnProhibitivelySlowNetwork) {
this->SimulateHasNetworkConnectivity(true);
this->set_allow_preview(true);
int64_t offline_id = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
this->LoadPage(kUrl2);
this->ExpectNoOfflinePageServed(
offline_id, OfflinePageRequestHandler::AggregatedRequestResult::
PAGE_NOT_FOUND_ON_PROHIBITIVELY_SLOW_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest, LoadOfflinePageOnFlakyNetwork) {
this->SimulateHasNetworkConnectivity(true);
int64_t offline_id = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
// When custom offline header exists and contains "reason=error", it means
// that net error is hit in last request due to flaky network.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
this->UseOfflinePageHeader(OfflinePageHeader::Reason::NET_ERROR, 0));
this->LoadPageWithHeaders(kUrl, extra_headers);
this->ExpectOfflinePageServed(
offline_id, kFileSize1,
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_FLAKY_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest, PageNotFoundOnFlakyNetwork) {
this->SimulateHasNetworkConnectivity(true);
int64_t offline_id = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
// When custom offline header exists and contains "reason=error", it means
// that net error is hit in last request due to flaky network.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
this->UseOfflinePageHeader(OfflinePageHeader::Reason::NET_ERROR, 0));
this->LoadPageWithHeaders(kUrl2, extra_headers);
this->ExpectNoOfflinePageServed(
offline_id, OfflinePageRequestHandler::AggregatedRequestResult::
PAGE_NOT_FOUND_ON_FLAKY_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest, ForceLoadOfflinePageOnConnectedNetwork) {
this->SimulateHasNetworkConnectivity(true);
int64_t offline_id = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
// When custom offline header exists and contains value other than
// "reason=error", it means that offline page is forced to load.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
this->UseOfflinePageHeader(OfflinePageHeader::Reason::DOWNLOAD, 0));
this->LoadPageWithHeaders(kUrl, extra_headers);
this->ExpectOfflinePageServed(
offline_id, kFileSize1,
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_CONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest, PageNotFoundOnConnectedNetwork) {
this->SimulateHasNetworkConnectivity(true);
// Save an offline page.
int64_t offline_id = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
// When custom offline header exists and contains value other than
// "reason=error", it means that offline page is forced to load.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
this->UseOfflinePageHeader(OfflinePageHeader::Reason::DOWNLOAD, 0));
this->LoadPageWithHeaders(kUrl2, extra_headers);
this->ExpectNoOfflinePageServed(
offline_id, OfflinePageRequestHandler::AggregatedRequestResult::
PAGE_NOT_FOUND_ON_CONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest, DoNotLoadOfflinePageOnConnectedNetwork) {
this->SimulateHasNetworkConnectivity(true);
int64_t offline_id = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
this->LoadPage(kUrl);
// When the network is good, we will fall back to the default handling
// immediately. So no request result should be reported. Passing
// AGGREGATED_REQUEST_RESULT_MAX to skip checking request result in
// the helper function.
this->ExpectNoOfflinePageServed(
offline_id, OfflinePageRequestHandler::AggregatedRequestResult::
AGGREGATED_REQUEST_RESULT_MAX);
}
TEST_F(OfflinePageRequestHandlerTest, LoadMostRecentlyCreatedOfflinePage) {
this->SimulateHasNetworkConnectivity(false);
// Save 2 offline pages associated with same online URL, but pointing to
// different archive file.
int64_t offline_id1 = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
int64_t offline_id2 = this->SaveInternalPage(kUrl, GURL(), kFilename2,
kFileSize2, std::string());
// Load an URL that matches multiple offline pages. Expect that the most
// recently created offline page is fetched.
this->LoadPage(kUrl);
this->ExpectOfflinePageServed(
offline_id2, kFileSize2,
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
this->ExpectOfflinePageAccessCount(offline_id1, 0);
}
TEST_F(OfflinePageRequestHandlerTest, LoadOfflinePageByOfflineID) {
this->SimulateHasNetworkConnectivity(true);
// Save 2 offline pages associated with same online URL, but pointing to
// different archive file.
int64_t offline_id1 = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
int64_t offline_id2 = this->SaveInternalPage(kUrl, GURL(), kFilename2,
kFileSize2, std::string());
// Load an URL with a specific offline ID designated in the custom header.
// Expect the offline page matching the offline id is fetched.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(this->UseOfflinePageHeader(
OfflinePageHeader::Reason::DOWNLOAD, offline_id1));
this->LoadPageWithHeaders(kUrl, extra_headers);
this->ExpectOfflinePageServed(
offline_id1, kFileSize1,
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_CONNECTED_NETWORK);
this->ExpectOfflinePageAccessCount(offline_id2, 0);
}
TEST_F(OfflinePageRequestHandlerTest, FailToLoadByOfflineIDOnUrlMismatch) {
this->SimulateHasNetworkConnectivity(true);
int64_t offline_id = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
// 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.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(this->UseOfflinePageHeader(
OfflinePageHeader::Reason::DOWNLOAD, offline_id));
this->LoadPageWithHeaders(kUrl2, extra_headers);
this->ExpectNoOfflinePageServed(
offline_id, OfflinePageRequestHandler::AggregatedRequestResult::
PAGE_NOT_FOUND_ON_CONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest, LoadOfflinePageForUrlWithFragment) {
this->SimulateHasNetworkConnectivity(false);
// Save an offline page associated with online URL without fragment.
int64_t offline_id1 = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
// Save another offline page associated with online URL that has a fragment.
GURL url2_with_fragment(kUrl2.spec() + "#ref");
int64_t offline_id2 = this->SaveInternalPage(
url2_with_fragment, GURL(), kFilename2, kFileSize2, std::string());
this->ExpectOfflinePageAccessCount(offline_id1, 0);
this->ExpectOfflinePageAccessCount(offline_id2, 0);
// Loads an url with fragment, that will match the offline URL without the
// fragment.
GURL url_with_fragment(kUrl.spec() + "#ref");
this->LoadPage(url_with_fragment);
this->ExpectOfflinePageServed(
offline_id1, kFileSize1,
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
this->ExpectOfflinePageAccessCount(offline_id2, 0);
// Loads an url without fragment, that will match the offline URL with the
// fragment.
this->LoadPage(kUrl2);
EXPECT_EQ(kFileSize2, this->bytes_read());
ASSERT_TRUE(this->offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(
offline_id2,
this->offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
this->ExpectMultiUniqueSampleForAggregatedRequestResult(
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK,
2);
this->ExpectOfflinePageSizeTotalSuffixCount(2);
this->ExpectOnlinePageSizeTotalSuffixCount(0);
this->ExpectOfflinePageAccessCount(offline_id1, 1);
this->ExpectOfflinePageAccessCount(offline_id2, 1);
// Loads an url with fragment, that will match the offline URL with different
// fragment.
GURL url2_with_different_fragment(kUrl2.spec() + "#different_ref");
this->LoadPage(url2_with_different_fragment);
EXPECT_EQ(kFileSize2, this->bytes_read());
ASSERT_TRUE(this->offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(
offline_id2,
this->offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
this->ExpectMultiUniqueSampleForAggregatedRequestResult(
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK,
3);
this->ExpectOfflinePageSizeTotalSuffixCount(3);
this->ExpectOnlinePageSizeTotalSuffixCount(0);
this->ExpectOfflinePageAccessCount(offline_id1, 1);
this->ExpectOfflinePageAccessCount(offline_id2, 2);
}
TEST_F(OfflinePageRequestHandlerTest, LoadOfflinePageAfterRedirect) {
this->SimulateHasNetworkConnectivity(false);
// Save an offline page with same original URL and final URL.
int64_t offline_id = this->SaveInternalPage(kUrl, kUrl2, kFilename1,
kFileSize1, std::string());
// This should trigger redirect first.
this->LoadPage(kUrl2);
// Passing AGGREGATED_REQUEST_RESULT_MAX to skip checking request result in
// the helper function. Different checks will be done after that.
this->ExpectOfflinePageServed(
offline_id, kFileSize1,
OfflinePageRequestHandler::AggregatedRequestResult::
AGGREGATED_REQUEST_RESULT_MAX);
this->ExpectOneNonuniqueSampleForAggregatedRequestResult(
OfflinePageRequestHandler::AggregatedRequestResult::
REDIRECTED_ON_DISCONNECTED_NETWORK);
this->ExpectOneNonuniqueSampleForAggregatedRequestResult(
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest,
NoRedirectForOfflinePageWithSameOriginalURL) {
this->SimulateHasNetworkConnectivity(false);
// 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.
OfflinePageModelTaskified* model = static_cast<OfflinePageModelTaskified*>(
OfflinePageModelFactory::GetForBrowserContext(this->profile()));
model->SetSkipClearingOriginalUrlForTesting();
// Save an offline page with same original URL and final URL.
int64_t offline_id =
this->SaveInternalPage(kUrl, kUrl, kFilename1, kFileSize1, std::string());
// Check if the original URL is still present.
OfflinePageItem page = this->GetPage(offline_id);
EXPECT_EQ(kUrl, page.original_url_if_different);
// No redirect should be triggered when original URL is same as final URL.
this->LoadPage(kUrl);
this->ExpectOfflinePageServed(
offline_id, kFileSize1,
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest,
LoadOfflinePageFromNonExistentInternalFile) {
this->SimulateHasNetworkConnectivity(false);
// Save an offline page pointing to non-existent internal archive file.
int64_t offline_id = this->SaveInternalPage(
kUrl, GURL(), kNonexistentFilename, kFileSize1, std::string());
this->LoadPage(kUrl);
this->ExpectNoOfflinePageServed(
offline_id,
OfflinePageRequestHandler::AggregatedRequestResult::FILE_NOT_FOUND);
}
TEST_F(OfflinePageRequestHandlerTest,
LoadOfflinePageFromNonExistentPublicFile) {
this->SimulateHasNetworkConnectivity(false);
// Save an offline page pointing to non-existent public archive file.
int64_t offline_id = this->SavePublicPage(kUrl, GURL(), kNonexistentFilename,
kFileSize1, kDigest1);
this->LoadPage(kUrl);
this->ExpectNoOfflinePageServed(
offline_id,
OfflinePageRequestHandler::AggregatedRequestResult::FILE_NOT_FOUND);
}
TEST_F(OfflinePageRequestHandlerTest, FileSizeMismatchOnDisconnectedNetwork) {
this->SimulateHasNetworkConnectivity(false);
// Save an offline page in public location with mismatched file size.
int64_t offline_id = this->SavePublicPage(kUrl, GURL(), kFilename1,
kMismatchedFileSize, kDigest1);
this->LoadPage(kUrl);
this->ExpectNoOfflinePageServed(
offline_id, OfflinePageRequestHandler::AggregatedRequestResult::
DIGEST_MISMATCH_ON_DISCONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest,
FileSizeMismatchOnProhibitivelySlowNetwork) {
this->SimulateHasNetworkConnectivity(true);
this->set_allow_preview(true);
// Save an offline page in public location with mismatched file size.
int64_t offline_id = this->SavePublicPage(kUrl, GURL(), kFilename1,
kMismatchedFileSize, kDigest1);
this->LoadPage(kUrl);
this->ExpectNoOfflinePageServed(
offline_id, OfflinePageRequestHandler::AggregatedRequestResult::
DIGEST_MISMATCH_ON_PROHIBITIVELY_SLOW_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest, FileSizeMismatchOnConnectedNetwork) {
this->SimulateHasNetworkConnectivity(true);
// Save an offline page in public location with mismatched file size.
int64_t offline_id = this->SavePublicPage(kUrl, GURL(), kFilename1,
kMismatchedFileSize, kDigest1);
// When custom offline header exists and contains value other than
// "reason=error", it means that offline page is forced to load.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
this->UseOfflinePageHeader(OfflinePageHeader::Reason::DOWNLOAD, 0));
this->LoadPageWithHeaders(kUrl, extra_headers);
this->ExpectNoOfflinePageServed(
offline_id, OfflinePageRequestHandler::AggregatedRequestResult::
DIGEST_MISMATCH_ON_CONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest, FileSizeMismatchOnFlakyNetwork) {
this->SimulateHasNetworkConnectivity(true);
// Save an offline page in public location with mismatched file size.
int64_t offline_id = this->SavePublicPage(kUrl, GURL(), kFilename1,
kMismatchedFileSize, kDigest1);
// When custom offline header exists and contains "reason=error", it means
// that net error is hit in last request due to flaky network.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
this->UseOfflinePageHeader(OfflinePageHeader::Reason::NET_ERROR, 0));
this->LoadPageWithHeaders(kUrl, extra_headers);
this->ExpectNoOfflinePageServed(
offline_id, OfflinePageRequestHandler::AggregatedRequestResult::
DIGEST_MISMATCH_ON_FLAKY_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest, DigestMismatchOnDisconnectedNetwork) {
this->SimulateHasNetworkConnectivity(false);
// Save an offline page in public location with mismatched digest.
int64_t offline_id = this->SavePublicPage(kUrl, GURL(), kFilename1,
kFileSize1, kMismatchedDigest);
this->LoadPage(kUrl);
this->ExpectNoOfflinePageServed(
offline_id, OfflinePageRequestHandler::AggregatedRequestResult::
DIGEST_MISMATCH_ON_DISCONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest,
DigestMismatchOnProhibitivelySlowNetwork) {
this->SimulateHasNetworkConnectivity(true);
this->set_allow_preview(true);
// Save an offline page in public location with mismatched digest.
int64_t offline_id = this->SavePublicPage(kUrl, GURL(), kFilename1,
kFileSize1, kMismatchedDigest);
this->LoadPage(kUrl);
this->ExpectNoOfflinePageServed(
offline_id, OfflinePageRequestHandler::AggregatedRequestResult::
DIGEST_MISMATCH_ON_PROHIBITIVELY_SLOW_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest, DigestMismatchOnConnectedNetwork) {
this->SimulateHasNetworkConnectivity(true);
// Save an offline page in public location with mismatched digest.
int64_t offline_id = this->SavePublicPage(kUrl, GURL(), kFilename1,
kFileSize1, kMismatchedDigest);
// When custom offline header exists and contains value other than
// "reason=error", it means that offline page is forced to load.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
this->UseOfflinePageHeader(OfflinePageHeader::Reason::DOWNLOAD, 0));
this->LoadPageWithHeaders(kUrl, extra_headers);
this->ExpectNoOfflinePageServed(
offline_id, OfflinePageRequestHandler::AggregatedRequestResult::
DIGEST_MISMATCH_ON_CONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest, DigestMismatchOnFlakyNetwork) {
this->SimulateHasNetworkConnectivity(true);
// Save an offline page in public location with mismatched digest.
int64_t offline_id = this->SavePublicPage(kUrl, GURL(), kFilename1,
kFileSize1, kMismatchedDigest);
// When custom offline header exists and contains "reason=error", it means
// that net error is hit in last request due to flaky network.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
this->UseOfflinePageHeader(OfflinePageHeader::Reason::NET_ERROR, 0));
this->LoadPageWithHeaders(kUrl, extra_headers);
this->ExpectNoOfflinePageServed(
offline_id, OfflinePageRequestHandler::AggregatedRequestResult::
DIGEST_MISMATCH_ON_FLAKY_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest, FailOnNoDigestForPublicArchiveFile) {
this->SimulateHasNetworkConnectivity(false);
// Save an offline page in public location with no digest.
int64_t offline_id =
this->SavePublicPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
this->LoadPage(kUrl);
this->ExpectNoOfflinePageServed(
offline_id, OfflinePageRequestHandler::AggregatedRequestResult::
DIGEST_MISMATCH_ON_DISCONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestHandlerTest, FailToLoadByOfflineIDOnDigestMismatch) {
this->SimulateHasNetworkConnectivity(true);
// Save 2 offline pages associated with same online URL, one in internal
// location, while another in public location with mismatched digest.
int64_t offline_id1 = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
int64_t offline_id2 = this->SavePublicPage(kUrl, GURL(), kFilename1,
kFileSize1, kMismatchedDigest);
// 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.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(this->UseOfflinePageHeader(
OfflinePageHeader::Reason::DOWNLOAD, offline_id2));
this->LoadPageWithHeaders(kUrl, extra_headers);
this->ExpectNoOfflinePageServed(
offline_id1, OfflinePageRequestHandler::AggregatedRequestResult::
DIGEST_MISMATCH_ON_CONNECTED_NETWORK);
this->ExpectOfflinePageAccessCount(offline_id2, 0);
}
TEST_F(OfflinePageRequestHandlerTest, LoadOtherPageOnDigestMismatch) {
this->SimulateHasNetworkConnectivity(false);
// Save 2 offline pages associated with same online URL, one in internal
// location, while another in public location with mismatched digest.
int64_t offline_id1 = this->SaveInternalPage(kUrl, GURL(), kFilename1,
kFileSize1, std::string());
int64_t offline_id2 = this->SavePublicPage(kUrl, GURL(), kFilename2,
kFileSize2, kMismatchedDigest);
this->ExpectOfflinePageAccessCount(offline_id1, 0);
this->ExpectOfflinePageAccessCount(offline_id2, 0);
// There're 2 offline pages matching kUrl. The most recently created one
// should fail on mistmatched digest. The second most recently created offline
// page should work.
this->LoadPage(kUrl);
this->ExpectOfflinePageServed(
offline_id1, kFileSize1,
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
this->ExpectOfflinePageAccessCount(offline_id2, 0);
}
// Disabled due to https://crbug.com/917113.
TEST_F(OfflinePageRequestHandlerTest, DISABLED_EmptyFile) {
this->SimulateHasNetworkConnectivity(false);
const std::string expected_data("");
base::FilePath temp_file_path = this->CreateFileWithContent(expected_data);
ArchiveValidator archive_validator;
const std::string expected_digest = archive_validator.Finish();
int64_t offline_id =
this->SavePublicPage(kUrl, GURL(), temp_file_path, 0, expected_digest);
this->LoadPage(kUrl);
this->ExpectOfflinePageServed(
offline_id, 0,
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
EXPECT_EQ(expected_data, this->data_received());
}
TEST_F(OfflinePageRequestHandlerTest, TinyFile) {
this->SimulateHasNetworkConnectivity(false);
std::string expected_data("hello world");
base::FilePath temp_file_path = this->CreateFileWithContent(expected_data);
ArchiveValidator archive_validator;
archive_validator.Update(expected_data.c_str(), expected_data.length());
std::string expected_digest = archive_validator.Finish();
int expected_size = expected_data.length();
int64_t offline_id = this->SavePublicPage(kUrl, GURL(), temp_file_path,
expected_size, expected_digest);
this->LoadPage(kUrl);
this->ExpectOfflinePageServed(
offline_id, expected_size,
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
EXPECT_EQ(expected_data, this->data_received());
}
TEST_F(OfflinePageRequestHandlerTest, SmallFile) {
this->SimulateHasNetworkConnectivity(false);
std::string expected_data(MakeContentOfSize(2 * 1024));
base::FilePath temp_file_path = this->CreateFileWithContent(expected_data);
ArchiveValidator archive_validator;
archive_validator.Update(expected_data.c_str(), expected_data.length());
std::string expected_digest = archive_validator.Finish();
int expected_size = expected_data.length();
int64_t offline_id = this->SavePublicPage(kUrl, GURL(), temp_file_path,
expected_size, expected_digest);
this->LoadPage(kUrl);
this->ExpectOfflinePageServed(
offline_id, expected_size,
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
EXPECT_EQ(expected_data, this->data_received());
}
TEST_F(OfflinePageRequestHandlerTest, BigFile) {
this->SimulateHasNetworkConnectivity(false);
std::string expected_data(MakeContentOfSize(3 * 1024 * 1024));
base::FilePath temp_file_path = this->CreateFileWithContent(expected_data);
ArchiveValidator archive_validator;
archive_validator.Update(expected_data.c_str(), expected_data.length());
std::string expected_digest = archive_validator.Finish();
int expected_size = expected_data.length();
int64_t offline_id = this->SavePublicPage(kUrl, GURL(), temp_file_path,
expected_size, expected_digest);
this->LoadPage(kUrl);
this->ExpectOfflinePageServed(
offline_id, expected_size,
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
EXPECT_EQ(expected_data, this->data_received());
}
TEST_F(OfflinePageRequestHandlerTest, LoadFromFileUrlIntent) {
this->SimulateHasNetworkConnectivity(true);
std::string expected_data(MakeContentOfSize(2 * 1024));
ArchiveValidator archive_validator;
archive_validator.Update(expected_data.c_str(), expected_data.length());
std::string expected_digest = archive_validator.Finish();
int expected_size = expected_data.length();
// Create a file with unmodified data. The path to this file will be feed
// into "intent_url" of extra headers.
base::FilePath unmodified_file_path =
this->CreateFileWithContent(expected_data);
// Create a file with modified data. An offline page is created to associate
// with this file, but with size and digest matching the unmodified version.
std::string modified_data(expected_data);
modified_data[10] = '@';
base::FilePath modified_file_path =
this->CreateFileWithContent(modified_data);
int64_t offline_id = this->SavePublicPage(kUrl, GURL(), modified_file_path,
expected_size, expected_digest);
// Load an URL with custom header that contains "intent_url" pointing to
// unmodified file. Expect the file from the intent URL is fetched.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(this->UseOfflinePageHeaderForIntent(
OfflinePageHeader::Reason::FILE_URL_INTENT, offline_id,
net::FilePathToFileURL(unmodified_file_path)));
this->LoadPageWithHeaders(kUrl, extra_headers);
this->ExpectOfflinePageServed(
offline_id, expected_size,
OfflinePageRequestHandler::AggregatedRequestResult::
SHOW_OFFLINE_ON_CONNECTED_NETWORK);
EXPECT_EQ(expected_data, this->data_received());
}
TEST_F(OfflinePageRequestHandlerTest, IntentFileNotFound) {
this->SimulateHasNetworkConnectivity(true);
std::string expected_data(MakeContentOfSize(2 * 1024));
ArchiveValidator archive_validator;
archive_validator.Update(expected_data.c_str(), expected_data.length());
std::string expected_digest = archive_validator.Finish();
int expected_size = expected_data.length();
// Create a file with unmodified data. An offline page is created to associate
// with this file.
base::FilePath unmodified_file_path =
this->CreateFileWithContent(expected_data);
// Get a path pointing to non-existing file. This path will be feed into
// "intent_url" of extra headers.
base::FilePath nonexistent_file_path =
unmodified_file_path.DirName().AppendASCII("nonexistent");
int64_t offline_id = this->SavePublicPage(kUrl, GURL(), unmodified_file_path,
expected_size, expected_digest);
// Load an URL with custom header that contains "intent_url" pointing to
// non-existent file. Expect the request fails.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(this->UseOfflinePageHeaderForIntent(
OfflinePageHeader::Reason::FILE_URL_INTENT, offline_id,
net::FilePathToFileURL(nonexistent_file_path)));
this->LoadPageWithHeaders(kUrl, extra_headers);
EXPECT_EQ(net::ERR_FAILED, this->request_status());
EXPECT_NE("multipart/related", this->mime_type());
EXPECT_EQ(0, this->bytes_read());
EXPECT_FALSE(this->is_offline_page_set_in_navigation_data());
EXPECT_FALSE(this->offline_page_tab_helper()->GetOfflinePageForTest());
}
TEST_F(OfflinePageRequestHandlerTest, IntentFileModifiedInTheMiddle) {
this->SimulateHasNetworkConnectivity(true);
std::string expected_data(MakeContentOfSize(2 * 1024));
ArchiveValidator archive_validator;
archive_validator.Update(expected_data.c_str(), expected_data.length());
std::string expected_digest = archive_validator.Finish();
int expected_size = expected_data.length();
// Create a file with modified data in the middle. An offline page is created
// to associate with this modified file, but with size and digest matching the
// unmodified version.
std::string modified_data(expected_data);
modified_data[10] = '@';
base::FilePath modified_file_path =
this->CreateFileWithContent(modified_data);
int64_t offline_id = this->SavePublicPage(kUrl, GURL(), modified_file_path,
expected_size, expected_digest);
// Load an URL with custom header that contains "intent_url" pointing to
// modified file. Expect the request fails.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(this->UseOfflinePageHeaderForIntent(
OfflinePageHeader::Reason::FILE_URL_INTENT, offline_id,
net::FilePathToFileURL(modified_file_path)));
this->LoadPageWithHeaders(kUrl, extra_headers);
EXPECT_EQ(net::ERR_FAILED, this->request_status());
EXPECT_NE("multipart/related", this->mime_type());
EXPECT_EQ(0, this->bytes_read());
// Note that the offline bit is not cleared on purpose due to the fact that
// other flag, like request status, should already indicate that the offline
// page fails to load.
EXPECT_TRUE(this->is_offline_page_set_in_navigation_data());
EXPECT_FALSE(this->offline_page_tab_helper()->GetOfflinePageForTest());
}
TEST_F(OfflinePageRequestHandlerTest, IntentFileModifiedWithMoreDataAppended) {
this->SimulateHasNetworkConnectivity(true);
std::string expected_data(MakeContentOfSize(2 * 1024));
ArchiveValidator archive_validator;
archive_validator.Update(expected_data.c_str(), expected_data.length());
std::string expected_digest = archive_validator.Finish();
int expected_size = expected_data.length();
// Create a file with more data appended. An offline page is created to
// associate with this modified file, but with size and digest matching the
// unmodified version.
std::string modified_data(expected_data);
modified_data += "foo";
base::FilePath modified_file_path =
this->CreateFileWithContent(modified_data);
int64_t offline_id = this->SavePublicPage(kUrl, GURL(), modified_file_path,
expected_size, expected_digest);
// Load an URL with custom header that contains "intent_url" pointing to
// modified file. Expect the request fails.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(this->UseOfflinePageHeaderForIntent(
OfflinePageHeader::Reason::FILE_URL_INTENT, offline_id,
net::FilePathToFileURL(modified_file_path)));
this->LoadPageWithHeaders(kUrl, extra_headers);
EXPECT_EQ(net::ERR_FAILED, this->request_status());
EXPECT_NE("multipart/related", this->mime_type());
EXPECT_EQ(0, this->bytes_read());
// Note that the offline bit is not cleared on purpose due to the fact that
// other flag, like request status, should already indicate that the offline
// page fails to load.
EXPECT_TRUE(this->is_offline_page_set_in_navigation_data());
EXPECT_FALSE(this->offline_page_tab_helper()->GetOfflinePageForTest());
}
} // namespace offline_pages