blob: ce7066ee9131a664684f21a565be89ddc71dc192 [file] [log] [blame]
// Copyright 2014 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 <stddef.h>
#include <stdint.h>
#include <map>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string16.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h"
#include "content/browser/cache_storage/cache_storage_cache.h"
#include "content/browser/cache_storage/cache_storage_cache_handle.h"
#include "content/browser/cache_storage/cache_storage_context_impl.h"
#include "content/browser/cache_storage/cache_storage_manager.h"
#include "content/browser/service_worker/embedded_worker_instance.h"
#include "content/browser/service_worker/embedded_worker_registry.h"
#include "content/browser/service_worker/embedded_worker_status.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_context_observer.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/service_worker/service_worker_fetch_dispatcher.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_test_utils.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/service_worker/service_worker_messages.h"
#include "content/common/service_worker/service_worker_status_code.h"
#include "content/common/service_worker/service_worker_types.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/referrer.h"
#include "content/public/common/resource_type.h"
#include "content/public/common/security_style.h"
#include "content/public/common/ssl_status.h"
#include "content/public/common/web_preferences.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_content_browser_client.h"
#include "content/test/test_content_browser_client.h"
#include "net/log/net_log.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/url_request/url_request_filter.h"
#include "net/url_request/url_request_interceptor.h"
#include "net/url_request/url_request_test_job.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_data_snapshot.h"
#include "storage/browser/blob/blob_reader.h"
#include "storage/browser/blob/blob_storage_context.h"
namespace content {
namespace {
struct FetchResult {
ServiceWorkerStatusCode status;
ServiceWorkerFetchEventResult result;
ServiceWorkerResponse response;
std::unique_ptr<storage::BlobDataHandle> blob_data_handle;
};
void RunAndQuit(const base::Closure& closure,
const base::Closure& quit,
base::SingleThreadTaskRunner* original_message_loop) {
closure.Run();
original_message_loop->PostTask(FROM_HERE, quit);
}
void RunOnIOThreadWithDelay(const base::Closure& closure,
base::TimeDelta delay) {
base::RunLoop run_loop;
BrowserThread::PostDelayedTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&RunAndQuit, closure, run_loop.QuitClosure(),
base::RetainedRef(base::ThreadTaskRunnerHandle::Get())),
delay);
run_loop.Run();
}
void RunOnIOThread(const base::Closure& closure) {
RunOnIOThreadWithDelay(closure, base::TimeDelta());
}
void RunOnIOThread(
const base::Callback<void(const base::Closure& continuation)>& closure) {
base::RunLoop run_loop;
base::Closure quit_on_original_thread =
base::Bind(base::IgnoreResult(&base::SingleThreadTaskRunner::PostTask),
base::ThreadTaskRunnerHandle::Get().get(),
FROM_HERE,
run_loop.QuitClosure());
BrowserThread::PostTask(BrowserThread::IO,
FROM_HERE,
base::Bind(closure, quit_on_original_thread));
run_loop.Run();
}
void ReceivePrepareResult(bool* is_prepared) {
*is_prepared = true;
}
base::Closure CreatePrepareReceiver(bool* is_prepared) {
return base::Bind(&ReceivePrepareResult, is_prepared);
}
void ReceiveFindRegistrationStatus(
BrowserThread::ID run_quit_thread,
const base::Closure& quit,
ServiceWorkerStatusCode* out_status,
ServiceWorkerStatusCode status,
scoped_refptr<ServiceWorkerRegistration> registration) {
*out_status = status;
if (!quit.is_null())
BrowserThread::PostTask(run_quit_thread, FROM_HERE, quit);
}
ServiceWorkerStorage::FindRegistrationCallback CreateFindRegistrationReceiver(
BrowserThread::ID run_quit_thread,
const base::Closure& quit,
ServiceWorkerStatusCode* status) {
return base::Bind(&ReceiveFindRegistrationStatus, run_quit_thread, quit,
status);
}
void ReadResponseBody(std::string* body,
storage::BlobDataHandle* blob_data_handle) {
ASSERT_TRUE(blob_data_handle);
std::unique_ptr<storage::BlobDataSnapshot> data =
blob_data_handle->CreateSnapshot();
ASSERT_EQ(1U, data->items().size());
*body = std::string(data->items()[0]->bytes(), data->items()[0]->length());
}
void ExpectResultAndRun(bool expected,
const base::Closure& continuation,
bool actual) {
EXPECT_EQ(expected, actual);
continuation.Run();
}
class WorkerActivatedObserver
: public ServiceWorkerContextObserver,
public base::RefCountedThreadSafe<WorkerActivatedObserver> {
public:
explicit WorkerActivatedObserver(ServiceWorkerContextWrapper* context)
: context_(context) {}
void Init() {
RunOnIOThread(base::Bind(&WorkerActivatedObserver::InitOnIOThread, this));
}
// ServiceWorkerContextObserver overrides.
void OnVersionStateChanged(int64_t version_id,
ServiceWorkerVersion::Status) override {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
const ServiceWorkerVersion* version = context_->GetLiveVersion(version_id);
if (version->status() == ServiceWorkerVersion::ACTIVATED) {
context_->RemoveObserver(this);
BrowserThread::PostTask(BrowserThread::UI,
FROM_HERE,
base::Bind(&WorkerActivatedObserver::Quit, this));
}
}
void Wait() { run_loop_.Run(); }
private:
friend class base::RefCountedThreadSafe<WorkerActivatedObserver>;
~WorkerActivatedObserver() override {}
void InitOnIOThread() { context_->AddObserver(this); }
void Quit() { run_loop_.Quit(); }
base::RunLoop run_loop_;
ServiceWorkerContextWrapper* context_;
DISALLOW_COPY_AND_ASSIGN(WorkerActivatedObserver);
};
std::unique_ptr<net::test_server::HttpResponse>
VerifyServiceWorkerHeaderInRequest(
const net::test_server::HttpRequest& request) {
EXPECT_EQ(request.relative_url, "/service_worker/generated_sw.js");
auto it = request.headers.find("Service-Worker");
EXPECT_TRUE(it != request.headers.end());
EXPECT_EQ("script", it->second);
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse());
http_response->set_content_type("text/javascript");
return std::move(http_response);
}
std::unique_ptr<net::test_server::HttpResponse> VerifySaveDataHeaderInRequest(
const net::test_server::HttpRequest& request) {
auto it = request.headers.find("Save-Data");
EXPECT_NE(request.headers.end(), it);
EXPECT_EQ("on", it->second);
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse());
http_response->set_content_type("text/javascript");
return std::move(http_response);
}
std::unique_ptr<net::test_server::HttpResponse>
VerifySaveDataHeaderNotInRequest(const net::test_server::HttpRequest& request) {
auto it = request.headers.find("Save-Data");
EXPECT_EQ(request.headers.end(), it);
return base::WrapUnique(new net::test_server::BasicHttpResponse());
}
std::unique_ptr<net::test_server::HttpResponse>
VerifySaveDataNotInAccessControlRequestHeader(
const net::test_server::HttpRequest& request) {
// Save-Data should be present.
auto it = request.headers.find("Save-Data");
EXPECT_NE(request.headers.end(), it);
EXPECT_EQ("on", it->second);
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse());
if (request.method == net::test_server::METHOD_OPTIONS) {
// Access-Control-Request-Headers should contain 'X-Custom-Header' and not
// contain 'Save-Data'.
auto acrh_iter = request.headers.find("Access-Control-Request-Headers");
EXPECT_NE(request.headers.end(), acrh_iter);
EXPECT_NE(std::string::npos, acrh_iter->second.find("x-custom-header"));
EXPECT_EQ(std::string::npos, acrh_iter->second.find("save-data"));
http_response->AddCustomHeader("Access-Control-Allow-Headers",
acrh_iter->second);
http_response->AddCustomHeader("Access-Control-Allow-Methods", "GET");
http_response->AddCustomHeader("Access-Control-Allow-Origin", "*");
} else {
http_response->AddCustomHeader("Access-Control-Allow-Origin", "*");
http_response->set_content("PASS");
}
return std::move(http_response);
}
// The ImportsBustMemcache test requires that the imported script
// would naturally be cached in blink's memcache, but the embedded
// test server doesn't produce headers that allow the blink's memcache
// to do that. This interceptor injects headers that give the import
// an experiration far in the future.
class LongLivedResourceInterceptor : public net::URLRequestInterceptor {
public:
explicit LongLivedResourceInterceptor(const std::string& body)
: body_(body) {}
~LongLivedResourceInterceptor() override {}
// net::URLRequestInterceptor implementation
net::URLRequestJob* MaybeInterceptRequest(
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const override {
const char kHeaders[] =
"HTTP/1.1 200 OK\n"
"Content-Type: text/javascript\n"
"Expires: Thu, 1 Jan 2100 20:00:00 GMT\n"
"\n";
std::string headers(kHeaders, arraysize(kHeaders));
return new net::URLRequestTestJob(
request, network_delegate, headers, body_, true);
}
private:
std::string body_;
DISALLOW_COPY_AND_ASSIGN(LongLivedResourceInterceptor);
};
void CreateLongLivedResourceInterceptors(
const GURL& worker_url, const GURL& import_url) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
std::unique_ptr<net::URLRequestInterceptor> interceptor;
interceptor.reset(new LongLivedResourceInterceptor(
"importScripts('long_lived_import.js');"));
net::URLRequestFilter::GetInstance()->AddUrlInterceptor(
worker_url, std::move(interceptor));
interceptor.reset(new LongLivedResourceInterceptor(
"// the imported script does nothing"));
net::URLRequestFilter::GetInstance()->AddUrlInterceptor(
import_url, std::move(interceptor));
}
void CountScriptResources(
ServiceWorkerContextWrapper* wrapper,
const GURL& scope,
int* num_resources) {
*num_resources = -1;
std::vector<ServiceWorkerRegistrationInfo> infos =
wrapper->GetAllLiveRegistrationInfo();
if (infos.empty())
return;
int version_id;
size_t index = infos.size() - 1;
if (infos[index].installing_version.version_id !=
kInvalidServiceWorkerVersionId)
version_id = infos[index].installing_version.version_id;
else if (infos[index].waiting_version.version_id !=
kInvalidServiceWorkerVersionId)
version_id = infos[1].waiting_version.version_id;
else if (infos[index].active_version.version_id !=
kInvalidServiceWorkerVersionId)
version_id = infos[index].active_version.version_id;
else
return;
ServiceWorkerVersion* version = wrapper->GetLiveVersion(version_id);
*num_resources = static_cast<int>(version->script_cache_map()->size());
}
} // namespace
class ServiceWorkerBrowserTest : public ContentBrowserTest {
protected:
using self = ServiceWorkerBrowserTest;
void SetUpOnMainThread() override {
ASSERT_TRUE(embedded_test_server()->Start());
StoragePartition* partition = BrowserContext::GetDefaultStoragePartition(
shell()->web_contents()->GetBrowserContext());
wrapper_ = static_cast<ServiceWorkerContextWrapper*>(
partition->GetServiceWorkerContext());
// Navigate to the page to set up a renderer page (where we can embed
// a worker).
NavigateToURLBlockUntilNavigationsComplete(
shell(),
embedded_test_server()->GetURL("/service_worker/empty.html"), 1);
RunOnIOThread(base::Bind(&self::SetUpOnIOThread, base::Unretained(this)));
}
void TearDownOnMainThread() override {
RunOnIOThread(
base::Bind(&self::TearDownOnIOThread, base::Unretained(this)));
wrapper_ = NULL;
}
virtual void SetUpOnIOThread() {}
virtual void TearDownOnIOThread() {}
ServiceWorkerContextWrapper* wrapper() { return wrapper_.get(); }
ServiceWorkerContext* public_context() { return wrapper(); }
void AssociateRendererProcessToPattern(const GURL& pattern) {
wrapper_->process_manager()->AddProcessReferenceToPattern(
pattern, shell()->web_contents()->GetRenderProcessHost()->GetID());
}
private:
scoped_refptr<ServiceWorkerContextWrapper> wrapper_;
};
class ConsoleListener : public EmbeddedWorkerInstance::Listener {
public:
void OnReportConsoleMessage(int source_identifier,
int message_level,
const base::string16& message,
int line_number,
const GURL& source_url) override {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&ConsoleListener::OnReportConsoleMessageOnUI,
base::Unretained(this), message));
}
void WaitForConsoleMessages(size_t expected_message_count) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (messages_.size() >= expected_message_count)
return;
expected_message_count_ = expected_message_count;
base::RunLoop console_run_loop;
quit_ = console_run_loop.QuitClosure();
console_run_loop.Run();
ASSERT_EQ(messages_.size(), expected_message_count);
}
bool OnMessageReceived(const IPC::Message& message) override { return false; }
const std::vector<base::string16>& messages() const { return messages_; }
private:
void OnReportConsoleMessageOnUI(const base::string16& message) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
messages_.push_back(message);
if (!quit_.is_null() && messages_.size() == expected_message_count_)
quit_.Run();
}
// These parameters must be accessed on the UI thread.
std::vector<base::string16> messages_;
size_t expected_message_count_ = 0;
base::Closure quit_;
};
class ServiceWorkerVersionBrowserTest : public ServiceWorkerBrowserTest {
public:
using self = ServiceWorkerVersionBrowserTest;
~ServiceWorkerVersionBrowserTest() override {}
void TearDownOnIOThread() override {
registration_ = NULL;
version_ = NULL;
}
void InstallTestHelper(const std::string& worker_url,
ServiceWorkerStatusCode expected_status) {
RunOnIOThread(base::Bind(&self::SetUpRegistrationOnIOThread,
base::Unretained(this), worker_url));
// Dispatch install on a worker.
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
base::RunLoop install_run_loop;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&self::InstallOnIOThread, base::Unretained(this),
install_run_loop.QuitClosure(), &status));
install_run_loop.Run();
ASSERT_EQ(expected_status, status);
// Stop the worker.
status = SERVICE_WORKER_ERROR_FAILED;
base::RunLoop stop_run_loop;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&self::StopOnIOThread, base::Unretained(this),
stop_run_loop.QuitClosure(), &status));
stop_run_loop.Run();
ASSERT_EQ(SERVICE_WORKER_OK, status);
}
void ActivateTestHelper(
const std::string& worker_url,
ServiceWorkerStatusCode expected_status) {
RunOnIOThread(base::Bind(&self::SetUpRegistrationOnIOThread,
base::Unretained(this), worker_url));
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
base::RunLoop run_loop;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&self::ActivateOnIOThread, base::Unretained(this),
run_loop.QuitClosure(), &status));
run_loop.Run();
ASSERT_EQ(expected_status, status);
}
void FetchOnRegisteredWorker(
ServiceWorkerFetchEventResult* result,
ServiceWorkerResponse* response,
std::unique_ptr<storage::BlobDataHandle>* blob_data_handle) {
blob_context_ = ChromeBlobStorageContext::GetFor(
shell()->web_contents()->GetBrowserContext());
bool prepare_result = false;
FetchResult fetch_result;
fetch_result.status = SERVICE_WORKER_ERROR_FAILED;
base::RunLoop fetch_run_loop;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&self::FetchOnIOThread, base::Unretained(this),
fetch_run_loop.QuitClosure(), &prepare_result,
&fetch_result));
fetch_run_loop.Run();
ASSERT_TRUE(prepare_result);
*result = fetch_result.result;
*response = fetch_result.response;
*blob_data_handle = std::move(fetch_result.blob_data_handle);
ASSERT_EQ(SERVICE_WORKER_OK, fetch_result.status);
}
void SetUpRegistrationOnIOThread(const std::string& worker_url) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
const GURL pattern = embedded_test_server()->GetURL("/service_worker/");
registration_ = new ServiceWorkerRegistration(
pattern,
wrapper()->context()->storage()->NewRegistrationId(),
wrapper()->context()->AsWeakPtr());
// Set the update check time to avoid triggering updates in the middle of
// tests.
registration_->set_last_update_check(base::Time::Now());
version_ = new ServiceWorkerVersion(
registration_.get(),
embedded_test_server()->GetURL(worker_url),
wrapper()->context()->storage()->NewVersionId(),
wrapper()->context()->AsWeakPtr());
// Make the registration findable via storage functions.
wrapper()->context()->storage()->NotifyInstallingRegistration(
registration_.get());
AssociateRendererProcessToPattern(pattern);
}
void TimeoutWorkerOnIOThread() {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
version_->SimulatePingTimeoutForTesting();
}
void AddControlleeOnIOThread() {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
std::unique_ptr<ServiceWorkerProviderHost> host(
new ServiceWorkerProviderHost(
33 /* dummy render process id */,
MSG_ROUTING_NONE /* render_frame_id */, 1 /* dummy provider_id */,
SERVICE_WORKER_PROVIDER_FOR_WINDOW,
ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
wrapper()->context()->AsWeakPtr(), NULL));
host->SetDocumentUrl(
embedded_test_server()->GetURL("/service_worker/host"));
host->AssociateRegistration(registration_.get(),
false /* notify_controllerchange */);
wrapper()->context()->AddProviderHost(std::move(host));
}
void AddWaitingWorkerOnIOThread(const std::string& worker_url) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
scoped_refptr<ServiceWorkerVersion> waiting_version(
new ServiceWorkerVersion(
registration_.get(), embedded_test_server()->GetURL(worker_url),
wrapper()->context()->storage()->NewVersionId(),
wrapper()->context()->AsWeakPtr()));
waiting_version->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
waiting_version->SetStatus(ServiceWorkerVersion::INSTALLED);
registration_->SetWaitingVersion(waiting_version.get());
registration_->ActivateWaitingVersionWhenReady();
}
void StartWorker(ServiceWorkerStatusCode expected_status) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI));
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
base::RunLoop start_run_loop;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&self::StartOnIOThread, base::Unretained(this),
start_run_loop.QuitClosure(), &status));
start_run_loop.Run();
ASSERT_EQ(expected_status, status);
}
void StopWorker(ServiceWorkerStatusCode expected_status) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI));
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
base::RunLoop stop_run_loop;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&self::StopOnIOThread, base::Unretained(this),
stop_run_loop.QuitClosure(), &status));
stop_run_loop.Run();
ASSERT_EQ(expected_status, status);
}
void StoreRegistration(int64_t version_id,
ServiceWorkerStatusCode expected_status) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI));
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
base::RunLoop store_run_loop;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&self::StoreOnIOThread, base::Unretained(this),
store_run_loop.QuitClosure(), &status, version_id));
store_run_loop.Run();
ASSERT_EQ(expected_status, status);
RunOnIOThread(base::Bind(&self::NotifyDoneInstallingRegistrationOnIOThread,
base::Unretained(this), status));
}
void FindRegistrationForId(int64_t id,
const GURL& origin,
ServiceWorkerStatusCode expected_status) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI));
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
base::RunLoop run_loop;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&self::FindRegistrationForIdOnIOThread,
base::Unretained(this), run_loop.QuitClosure(), &status, id,
origin));
run_loop.Run();
ASSERT_EQ(expected_status, status);
}
void FindRegistrationForIdOnIOThread(const base::Closure& done,
ServiceWorkerStatusCode* result,
int64_t id,
const GURL& origin) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
wrapper()->context()->storage()->FindRegistrationForId(
id, origin,
CreateFindRegistrationReceiver(BrowserThread::UI, done, result));
}
void NotifyDoneInstallingRegistrationOnIOThread(
ServiceWorkerStatusCode status) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
wrapper()->context()->storage()->NotifyDoneInstallingRegistration(
registration_.get(), version_.get(), status);
}
void RemoveLiveRegistrationOnIOThread(int64_t id) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
wrapper()->context()->RemoveLiveRegistration(id);
}
void StartOnIOThread(const base::Closure& done,
ServiceWorkerStatusCode* result) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiver(BrowserThread::UI, done, result));
}
void InstallOnIOThread(const base::Closure& done,
ServiceWorkerStatusCode* result) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
version_->SetStatus(ServiceWorkerVersion::INSTALLING);
version_->RunAfterStartWorker(
ServiceWorkerMetrics::EventType::INSTALL,
base::Bind(&self::DispatchInstallEventOnIOThread,
base::Unretained(this), done, result),
CreateReceiver(BrowserThread::UI, done, result));
}
void DispatchInstallEventOnIOThread(const base::Closure& done,
ServiceWorkerStatusCode* result) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
int request_id =
version_->StartRequest(ServiceWorkerMetrics::EventType::INSTALL,
CreateReceiver(BrowserThread::UI, done, result));
version_
->RegisterRequestCallback<ServiceWorkerHostMsg_InstallEventFinished>(
request_id, base::Bind(&self::ReceiveInstallEventOnIOThread,
base::Unretained(this), done, result));
version_->DispatchEvent({request_id},
ServiceWorkerMsg_InstallEvent(request_id));
}
void ReceiveInstallEventOnIOThread(const base::Closure& done,
ServiceWorkerStatusCode* out_result,
int request_id,
blink::WebServiceWorkerEventResult result,
bool has_fetch_handler) {
version_->FinishRequest(
request_id, result == blink::WebServiceWorkerEventResultCompleted);
version_->set_fetch_handler_existence(
has_fetch_handler
? ServiceWorkerVersion::FetchHandlerExistence::EXISTS
: ServiceWorkerVersion::FetchHandlerExistence::DOES_NOT_EXIST);
ServiceWorkerStatusCode status = SERVICE_WORKER_OK;
if (result == blink::WebServiceWorkerEventResultRejected)
status = SERVICE_WORKER_ERROR_EVENT_WAITUNTIL_REJECTED;
*out_result = status;
if (!done.is_null())
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, done);
}
void StoreOnIOThread(const base::Closure& done,
ServiceWorkerStatusCode* result,
int64_t version_id) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
ServiceWorkerVersion* version =
wrapper()->context()->GetLiveVersion(version_id);
wrapper()->context()->storage()->StoreRegistration(
registration_.get(), version,
CreateReceiver(BrowserThread::UI, done, result));
}
void ActivateOnIOThread(const base::Closure& done,
ServiceWorkerStatusCode* result) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
version_->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version_->SetStatus(ServiceWorkerVersion::ACTIVATING);
registration_->SetActiveVersion(version_.get());
version_->RunAfterStartWorker(
ServiceWorkerMetrics::EventType::ACTIVATE,
base::Bind(&self::DispatchActivateEventOnIOThread,
base::Unretained(this), done, result),
CreateReceiver(BrowserThread::UI, done, result));
}
void DispatchActivateEventOnIOThread(const base::Closure& done,
ServiceWorkerStatusCode* result) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
int request_id =
version_->StartRequest(ServiceWorkerMetrics::EventType::INSTALL,
CreateReceiver(BrowserThread::UI, done, result));
version_->DispatchSimpleEvent<ServiceWorkerHostMsg_ActivateEventFinished>(
request_id, ServiceWorkerMsg_ActivateEvent(request_id));
}
void FetchOnIOThread(const base::Closure& done,
bool* prepare_result,
FetchResult* result) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
std::unique_ptr<ServiceWorkerFetchRequest> request(
new ServiceWorkerFetchRequest(
embedded_test_server()->GetURL("/service_worker/empty.html"), "GET",
ServiceWorkerHeaderMap(), Referrer(), false));
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
fetch_dispatcher_.reset(new ServiceWorkerFetchDispatcher(
std::move(request), version_.get(), RESOURCE_TYPE_MAIN_FRAME,
net::BoundNetLog(), CreatePrepareReceiver(prepare_result),
CreateResponseReceiver(done, blob_context_.get(), result)));
fetch_dispatcher_->Run();
}
// Contrary to the style guide, the output parameter of this function comes
// before input parameters so Bind can be used on it to create a FetchCallback
// to pass to DispatchFetchEvent.
void ReceiveFetchResultOnIOThread(
const base::Closure& quit,
ChromeBlobStorageContext* blob_context,
FetchResult* out_result,
ServiceWorkerStatusCode actual_status,
ServiceWorkerFetchEventResult actual_result,
const ServiceWorkerResponse& actual_response,
const scoped_refptr<ServiceWorkerVersion>& worker) {
ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
ASSERT_TRUE(fetch_dispatcher_);
fetch_dispatcher_.reset();
out_result->status = actual_status;
out_result->result = actual_result;
out_result->response = actual_response;
if (!actual_response.blob_uuid.empty()) {
out_result->blob_data_handle =
blob_context->context()->GetBlobDataFromUUID(
actual_response.blob_uuid);
}
if (!quit.is_null())
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, quit);
}
ServiceWorkerFetchDispatcher::FetchCallback CreateResponseReceiver(
const base::Closure& quit,
ChromeBlobStorageContext* blob_context,
FetchResult* result) {
return base::Bind(&self::ReceiveFetchResultOnIOThread,
base::Unretained(this), quit,
base::RetainedRef(blob_context), result);
}
void StopOnIOThread(const base::Closure& done,
ServiceWorkerStatusCode* result) {
ASSERT_TRUE(version_.get());
version_->StopWorker(CreateReceiver(BrowserThread::UI, done, result));
}
protected:
scoped_refptr<ServiceWorkerRegistration> registration_;
scoped_refptr<ServiceWorkerVersion> version_;
scoped_refptr<ChromeBlobStorageContext> blob_context_;
std::unique_ptr<ServiceWorkerFetchDispatcher> fetch_dispatcher_;
};
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, StartAndStop) {
RunOnIOThread(base::Bind(&self::SetUpRegistrationOnIOThread,
base::Unretained(this),
"/service_worker/worker.js"));
// Start a worker.
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
base::RunLoop start_run_loop;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&self::StartOnIOThread, base::Unretained(this),
start_run_loop.QuitClosure(), &status));
start_run_loop.Run();
ASSERT_EQ(SERVICE_WORKER_OK, status);
// Stop the worker.
status = SERVICE_WORKER_ERROR_FAILED;
base::RunLoop stop_run_loop;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&self::StopOnIOThread, base::Unretained(this),
stop_run_loop.QuitClosure(), &status));
stop_run_loop.Run();
ASSERT_EQ(SERVICE_WORKER_OK, status);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, StartNotFound) {
RunOnIOThread(base::Bind(&self::SetUpRegistrationOnIOThread,
base::Unretained(this),
"/service_worker/nonexistent.js"));
// Start a worker for nonexistent URL.
StartWorker(SERVICE_WORKER_ERROR_NETWORK);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, ReadResourceFailure) {
// Create a registration.
RunOnIOThread(base::Bind(&self::SetUpRegistrationOnIOThread,
base::Unretained(this),
"/service_worker/worker.js"));
version_->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
// Add a non-existent resource to the version.
std::vector<ServiceWorkerDatabase::ResourceRecord> records;
records.push_back(
ServiceWorkerDatabase::ResourceRecord(30, version_->script_url(), 100));
version_->script_cache_map()->SetResources(records);
// Store the registration.
StoreRegistration(version_->version_id(), SERVICE_WORKER_OK);
// Start the worker. We'll fail to read the resource.
StartWorker(SERVICE_WORKER_ERROR_DISK_CACHE);
EXPECT_EQ(ServiceWorkerVersion::REDUNDANT, version_->status());
// The registration should be deleted from storage since the broken worker was
// the stored one.
RunOnIOThread(base::Bind(&self::RemoveLiveRegistrationOnIOThread,
base::Unretained(this), registration_->id()));
FindRegistrationForId(registration_->id(),
registration_->pattern().GetOrigin(),
SERVICE_WORKER_ERROR_NOT_FOUND);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest,
ReadResourceFailure_WaitingWorker) {
// Create a registration and active version.
RunOnIOThread(base::Bind(&self::SetUpRegistrationOnIOThread,
base::Unretained(this),
"/service_worker/worker.js"));
base::RunLoop activate_run_loop;
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&self::ActivateOnIOThread, base::Unretained(this),
activate_run_loop.QuitClosure(), &status));
activate_run_loop.Run();
EXPECT_EQ(SERVICE_WORKER_OK, status);
ASSERT_TRUE(registration_->active_version());
// Give the version a controllee.
RunOnIOThread(
base::Bind(&self::AddControlleeOnIOThread, base::Unretained(this)));
// Add a non-existent resource to the version.
std::vector<ServiceWorkerDatabase::ResourceRecord> records;
records.push_back(
ServiceWorkerDatabase::ResourceRecord(30, version_->script_url(), 100));
version_->script_cache_map()->SetResources(records);
// Make a waiting version and store it.
RunOnIOThread(base::Bind(&self::AddWaitingWorkerOnIOThread,
base::Unretained(this),
"/service_worker/worker.js"));
registration_->waiting_version()->script_cache_map()->SetResources(records);
StoreRegistration(registration_->waiting_version()->version_id(),
SERVICE_WORKER_OK);
// Start the broken worker. We'll fail to read from disk and the worker should
// be doomed.
StopWorker(SERVICE_WORKER_OK); // in case it's already running
StartWorker(SERVICE_WORKER_ERROR_DISK_CACHE);
EXPECT_EQ(ServiceWorkerVersion::REDUNDANT, version_->status());
// The registration should still be in storage since the waiting worker was
// the stored one.
RunOnIOThread(base::Bind(&self::RemoveLiveRegistrationOnIOThread,
base::Unretained(this), registration_->id()));
FindRegistrationForId(registration_->id(),
registration_->pattern().GetOrigin(),
SERVICE_WORKER_OK);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, Install) {
InstallTestHelper("/service_worker/worker.js", SERVICE_WORKER_OK);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest,
InstallWithWaitUntil_Fulfilled) {
InstallTestHelper("/service_worker/worker_install_fulfilled.js",
SERVICE_WORKER_OK);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest,
InstallWithFetchHandler) {
InstallTestHelper("/service_worker/fetch_event.js", SERVICE_WORKER_OK);
EXPECT_EQ(ServiceWorkerVersion::FetchHandlerExistence::EXISTS,
version_->fetch_handler_existence());
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest,
InstallWithoutFetchHandler) {
InstallTestHelper("/service_worker/worker.js", SERVICE_WORKER_OK);
EXPECT_EQ(ServiceWorkerVersion::FetchHandlerExistence::DOES_NOT_EXIST,
version_->fetch_handler_existence());
}
// Check that ServiceWorker script requests set a "Service-Worker: script"
// header.
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest,
ServiceWorkerScriptHeader) {
embedded_test_server()->RegisterRequestHandler(
base::Bind(&VerifyServiceWorkerHeaderInRequest));
InstallTestHelper("/service_worker/generated_sw.js", SERVICE_WORKER_OK);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest,
Activate_NoEventListener) {
ActivateTestHelper("/service_worker/worker.js", SERVICE_WORKER_OK);
ASSERT_EQ(ServiceWorkerVersion::ACTIVATING, version_->status());
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, Activate_Rejected) {
ActivateTestHelper("/service_worker/worker_activate_rejected.js",
SERVICE_WORKER_ERROR_EVENT_WAITUNTIL_REJECTED);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest,
InstallWithWaitUntil_Rejected) {
InstallTestHelper("/service_worker/worker_install_rejected.js",
SERVICE_WORKER_ERROR_EVENT_WAITUNTIL_REJECTED);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest,
InstallWithWaitUntil_RejectConsoleMessage) {
RunOnIOThread(base::Bind(&self::SetUpRegistrationOnIOThread,
base::Unretained(this),
"/service_worker/worker_install_rejected.js"));
ConsoleListener console_listener;
RunOnIOThread(base::Bind(&EmbeddedWorkerInstance::AddListener,
base::Unretained(version_->embedded_worker()),
&console_listener));
// Dispatch install on a worker.
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
base::RunLoop install_run_loop;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&self::InstallOnIOThread, base::Unretained(this),
install_run_loop.QuitClosure(), &status));
install_run_loop.Run();
ASSERT_EQ(SERVICE_WORKER_ERROR_EVENT_WAITUNTIL_REJECTED, status);
const base::string16 expected =
base::ASCIIToUTF16("Rejecting oninstall event");
console_listener.WaitForConsoleMessages(1);
ASSERT_NE(base::string16::npos,
console_listener.messages()[0].find(expected));
RunOnIOThread(base::Bind(&EmbeddedWorkerInstance::RemoveListener,
base::Unretained(version_->embedded_worker()),
&console_listener));
}
class WaitForLoaded : public EmbeddedWorkerInstance::Listener {
public:
explicit WaitForLoaded(const base::Closure& quit) : quit_(quit) {}
void OnThreadStarted() override {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, quit_);
}
bool OnMessageReceived(const IPC::Message& message) override { return false; }
private:
base::Closure quit_;
};
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, TimeoutStartingWorker) {
RunOnIOThread(base::Bind(&self::SetUpRegistrationOnIOThread,
base::Unretained(this),
"/service_worker/while_true_worker.js"));
// Start a worker, waiting until the script is loaded.
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
base::RunLoop start_run_loop;
base::RunLoop load_run_loop;
WaitForLoaded wait_for_load(load_run_loop.QuitClosure());
RunOnIOThread(base::Bind(&EmbeddedWorkerInstance::AddListener,
base::Unretained(version_->embedded_worker()),
&wait_for_load));
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&self::StartOnIOThread, base::Unretained(this),
start_run_loop.QuitClosure(), &status));
load_run_loop.Run();
RunOnIOThread(base::Bind(&EmbeddedWorkerInstance::RemoveListener,
base::Unretained(version_->embedded_worker()),
&wait_for_load));
// The script has loaded but start has not completed yet.
ASSERT_EQ(SERVICE_WORKER_ERROR_FAILED, status);
EXPECT_EQ(EmbeddedWorkerStatus::STARTING, version_->running_status());
// Simulate execution timeout. Use a delay to prevent killing the worker
// before it's started execution.
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
RunOnIOThreadWithDelay(
base::Bind(&self::TimeoutWorkerOnIOThread, base::Unretained(this)),
base::TimeDelta::FromMilliseconds(100));
start_run_loop.Run();
EXPECT_EQ(SERVICE_WORKER_ERROR_TIMEOUT, status);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, TimeoutWorkerInEvent) {
RunOnIOThread(base::Bind(&self::SetUpRegistrationOnIOThread,
base::Unretained(this),
"/service_worker/while_true_in_install_worker.js"));
// Start a worker.
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
base::RunLoop start_run_loop;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&self::StartOnIOThread, base::Unretained(this),
start_run_loop.QuitClosure(), &status));
start_run_loop.Run();
ASSERT_EQ(SERVICE_WORKER_OK, status);
// Dispatch an event.
base::RunLoop install_run_loop;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&self::InstallOnIOThread, base::Unretained(this),
install_run_loop.QuitClosure(), &status));
// Simulate execution timeout. Use a delay to prevent killing the worker
// before it's started execution.
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
RunOnIOThreadWithDelay(
base::Bind(&self::TimeoutWorkerOnIOThread, base::Unretained(this)),
base::TimeDelta::FromMilliseconds(100));
install_run_loop.Run();
// Terminating a worker, even one in an infinite loop, is treated as if
// waitUntil was rejected in the renderer code.
EXPECT_EQ(SERVICE_WORKER_ERROR_EVENT_WAITUNTIL_REJECTED, status);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, FetchEvent_Response) {
ServiceWorkerFetchEventResult result;
ServiceWorkerResponse response;
std::unique_ptr<storage::BlobDataHandle> blob_data_handle;
ActivateTestHelper("/service_worker/fetch_event.js", SERVICE_WORKER_OK);
FetchOnRegisteredWorker(&result, &response, &blob_data_handle);
ASSERT_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, result);
EXPECT_EQ(301, response.status_code);
EXPECT_EQ("Moved Permanently", response.status_text);
ServiceWorkerHeaderMap expected_headers;
expected_headers["content-language"] = "fi";
expected_headers["content-type"] = "text/html; charset=UTF-8";
EXPECT_EQ(expected_headers, response.headers);
std::string body;
RunOnIOThread(
base::Bind(&ReadResponseBody,
&body, base::Owned(blob_data_handle.release())));
EXPECT_EQ("This resource is gone. Gone, gone, gone.", body);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest,
FetchEvent_ResponseViaCache) {
ServiceWorkerFetchEventResult result;
ServiceWorkerResponse response1;
ServiceWorkerResponse response2;
std::unique_ptr<storage::BlobDataHandle> blob_data_handle;
const base::Time start_time(base::Time::Now());
ActivateTestHelper("/service_worker/fetch_event_response_via_cache.js",
SERVICE_WORKER_OK);
FetchOnRegisteredWorker(&result, &response1, &blob_data_handle);
ASSERT_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, result);
EXPECT_EQ(200, response1.status_code);
EXPECT_EQ("OK", response1.status_text);
EXPECT_TRUE(response1.response_time >= start_time);
EXPECT_FALSE(response1.is_in_cache_storage);
EXPECT_EQ(std::string(), response2.cache_storage_cache_name);
FetchOnRegisteredWorker(&result, &response2, &blob_data_handle);
ASSERT_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, result);
EXPECT_EQ(200, response2.status_code);
EXPECT_EQ("OK", response2.status_text);
EXPECT_EQ(response1.response_time, response2.response_time);
EXPECT_TRUE(response2.is_in_cache_storage);
EXPECT_EQ("cache_name", response2.cache_storage_cache_name);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest,
FetchEvent_respondWithRejection) {
ServiceWorkerFetchEventResult result;
ServiceWorkerResponse response;
std::unique_ptr<storage::BlobDataHandle> blob_data_handle;
ActivateTestHelper("/service_worker/fetch_event_rejected.js",
SERVICE_WORKER_OK);
ConsoleListener console_listener;
RunOnIOThread(base::Bind(&EmbeddedWorkerInstance::AddListener,
base::Unretained(version_->embedded_worker()),
&console_listener));
FetchOnRegisteredWorker(&result, &response, &blob_data_handle);
const base::string16 expected1 = base::ASCIIToUTF16(
"resulted in a network error response: the promise was rejected.");
const base::string16 expected2 =
base::ASCIIToUTF16("Uncaught (in promise) Rejecting respondWith promise");
console_listener.WaitForConsoleMessages(2);
ASSERT_NE(base::string16::npos,
console_listener.messages()[0].find(expected1));
ASSERT_EQ(0u, console_listener.messages()[1].find(expected2));
RunOnIOThread(base::Bind(&EmbeddedWorkerInstance::RemoveListener,
base::Unretained(version_->embedded_worker()),
&console_listener));
ASSERT_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, result);
EXPECT_EQ(0, response.status_code);
ASSERT_FALSE(blob_data_handle);
}
class MockContentBrowserClient : public TestContentBrowserClient {
public:
MockContentBrowserClient()
: TestContentBrowserClient(), data_saver_enabled_(false) {}
~MockContentBrowserClient() override {}
void set_data_saver_enabled(bool enabled) { data_saver_enabled_ = enabled; }
// ContentBrowserClient overrides:
bool IsDataSaverEnabled(BrowserContext* context) override {
return data_saver_enabled_;
}
void OverrideWebkitPrefs(RenderViewHost* render_view_host,
WebPreferences* prefs) override {
prefs->data_saver_enabled = data_saver_enabled_;
}
private:
bool data_saver_enabled_;
};
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, FetchWithSaveData) {
embedded_test_server()->RegisterRequestHandler(
base::Bind(&VerifySaveDataHeaderInRequest));
MockContentBrowserClient content_browser_client;
content_browser_client.set_data_saver_enabled(true);
ContentBrowserClient* old_client =
SetBrowserClientForTesting(&content_browser_client);
InstallTestHelper("/service_worker/fetch_in_install.js", SERVICE_WORKER_OK);
SetBrowserClientForTesting(old_client);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest,
RequestWorkerScriptWithSaveData) {
embedded_test_server()->RegisterRequestHandler(
base::Bind(&VerifySaveDataHeaderInRequest));
MockContentBrowserClient content_browser_client;
content_browser_client.set_data_saver_enabled(true);
ContentBrowserClient* old_client =
SetBrowserClientForTesting(&content_browser_client);
InstallTestHelper("/service_worker/generated_sw.js", SERVICE_WORKER_OK);
SetBrowserClientForTesting(old_client);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, FetchWithoutSaveData) {
embedded_test_server()->RegisterRequestHandler(
base::Bind(&VerifySaveDataHeaderNotInRequest));
MockContentBrowserClient content_browser_client;
ContentBrowserClient* old_client =
SetBrowserClientForTesting(&content_browser_client);
InstallTestHelper("/service_worker/fetch_in_install.js", SERVICE_WORKER_OK);
SetBrowserClientForTesting(old_client);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerBrowserTest, FetchPageWithSaveData) {
const char kPageUrl[] = "/service_worker/handle_fetch.html";
const char kWorkerUrl[] = "/service_worker/add_save_data_to_title.js";
MockContentBrowserClient content_browser_client;
content_browser_client.set_data_saver_enabled(true);
ContentBrowserClient* old_client =
SetBrowserClientForTesting(&content_browser_client);
shell()->web_contents()->GetRenderViewHost()->OnWebkitPreferencesChanged();
scoped_refptr<WorkerActivatedObserver> observer =
new WorkerActivatedObserver(wrapper());
observer->Init();
public_context()->RegisterServiceWorker(
embedded_test_server()->GetURL(kPageUrl),
embedded_test_server()->GetURL(kWorkerUrl),
base::Bind(&ExpectResultAndRun, true, base::Bind(&base::DoNothing)));
observer->Wait();
const base::string16 title1 = base::ASCIIToUTF16("save-data=on");
TitleWatcher title_watcher1(shell()->web_contents(), title1);
NavigateToURL(shell(), embedded_test_server()->GetURL(kPageUrl));
EXPECT_EQ(title1, title_watcher1.WaitAndGetTitle());
SetBrowserClientForTesting(old_client);
shell()->Close();
base::RunLoop run_loop;
public_context()->UnregisterServiceWorker(
embedded_test_server()->GetURL(kPageUrl),
base::Bind(&ExpectResultAndRun, true, run_loop.QuitClosure()));
run_loop.Run();
}
// Tests that when data saver is enabled and a cross-origin fetch by a webpage
// is intercepted by a serviceworker, and the serviceworker does a fetch, the
// preflight request does not have save-data in Access-Control-Request-Headers.
IN_PROC_BROWSER_TEST_F(ServiceWorkerBrowserTest, CrossOriginFetchWithSaveData) {
const char kPageUrl[] = "/service_worker/fetch_cross_origin.html";
const char kWorkerUrl[] = "/service_worker/fetch_event_pass_through.js";
net::EmbeddedTestServer cross_origin_server;
cross_origin_server.ServeFilesFromSourceDirectory("content/test/data");
cross_origin_server.RegisterRequestHandler(
base::Bind(&VerifySaveDataNotInAccessControlRequestHeader));
ASSERT_TRUE(cross_origin_server.Start());
MockContentBrowserClient content_browser_client;
content_browser_client.set_data_saver_enabled(true);
ContentBrowserClient* old_client =
SetBrowserClientForTesting(&content_browser_client);
shell()->web_contents()->GetRenderViewHost()->OnWebkitPreferencesChanged();
scoped_refptr<WorkerActivatedObserver> observer =
new WorkerActivatedObserver(wrapper());
observer->Init();
public_context()->RegisterServiceWorker(
embedded_test_server()->GetURL(kPageUrl),
embedded_test_server()->GetURL(kWorkerUrl),
base::Bind(&ExpectResultAndRun, true, base::Bind(&base::DoNothing)));
observer->Wait();
const base::string16 title = base::ASCIIToUTF16("PASS");
TitleWatcher title_watcher(shell()->web_contents(), title);
NavigateToURL(shell(),
embedded_test_server()->GetURL(base::StringPrintf(
"%s?%s", kPageUrl,
cross_origin_server.GetURL("/cross_origin_request.html")
.spec()
.c_str())));
EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
SetBrowserClientForTesting(old_client);
shell()->Close();
base::RunLoop run_loop;
public_context()->UnregisterServiceWorker(
embedded_test_server()->GetURL(kPageUrl),
base::Bind(&ExpectResultAndRun, true, run_loop.QuitClosure()));
run_loop.Run();
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerBrowserTest,
FetchPageWithSaveDataPassThroughOnFetch) {
const char kPageUrl[] = "/service_worker/pass_through_fetch.html";
const char kWorkerUrl[] = "/service_worker/fetch_event_pass_through.js";
MockContentBrowserClient content_browser_client;
content_browser_client.set_data_saver_enabled(true);
ContentBrowserClient* old_client =
SetBrowserClientForTesting(&content_browser_client);
shell()->web_contents()->GetRenderViewHost()->OnWebkitPreferencesChanged();
scoped_refptr<WorkerActivatedObserver> observer =
new WorkerActivatedObserver(wrapper());
observer->Init();
public_context()->RegisterServiceWorker(
embedded_test_server()->GetURL(kPageUrl),
embedded_test_server()->GetURL(kWorkerUrl),
base::Bind(&ExpectResultAndRun, true, base::Bind(&base::DoNothing)));
observer->Wait();
embedded_test_server()->RegisterRequestHandler(
base::Bind(&VerifySaveDataHeaderInRequest));
NavigateToURLBlockUntilNavigationsComplete(
shell(), embedded_test_server()->GetURL(kPageUrl), 1);
SetBrowserClientForTesting(old_client);
shell()->Close();
base::RunLoop run_loop;
public_context()->UnregisterServiceWorker(
embedded_test_server()->GetURL(kPageUrl),
base::Bind(&ExpectResultAndRun, true, run_loop.QuitClosure()));
run_loop.Run();
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerBrowserTest, Reload) {
const char kPageUrl[] = "/service_worker/reload.html";
const char kWorkerUrl[] = "/service_worker/fetch_event_reload.js";
scoped_refptr<WorkerActivatedObserver> observer =
new WorkerActivatedObserver(wrapper());
observer->Init();
public_context()->RegisterServiceWorker(
embedded_test_server()->GetURL(kPageUrl),
embedded_test_server()->GetURL(kWorkerUrl),
base::Bind(&ExpectResultAndRun, true, base::Bind(&base::DoNothing)));
observer->Wait();
const base::string16 title1 = base::ASCIIToUTF16("reload=false");
TitleWatcher title_watcher1(shell()->web_contents(), title1);
NavigateToURL(shell(), embedded_test_server()->GetURL(kPageUrl));
EXPECT_EQ(title1, title_watcher1.WaitAndGetTitle());
const base::string16 title2 = base::ASCIIToUTF16("reload=true");
TitleWatcher title_watcher2(shell()->web_contents(), title2);
ReloadBlockUntilNavigationsComplete(shell(), 1);
EXPECT_EQ(title2, title_watcher2.WaitAndGetTitle());
shell()->Close();
base::RunLoop run_loop;
public_context()->UnregisterServiceWorker(
embedded_test_server()->GetURL(kPageUrl),
base::Bind(&ExpectResultAndRun, true, run_loop.QuitClosure()));
run_loop.Run();
}
// Flaky on Win/Mac: http://crbug.com/533631
#if defined(OS_WIN) || defined(OS_MACOSX)
#define MAYBE_ResponseFromHTTPSServiceWorkerIsMarkedAsSecure DISABLED_ResponseFromHTTPSServiceWorkerIsMarkedAsSecure
#else
#define MAYBE_ResponseFromHTTPSServiceWorkerIsMarkedAsSecure ResponseFromHTTPSServiceWorkerIsMarkedAsSecure
#endif
IN_PROC_BROWSER_TEST_F(ServiceWorkerBrowserTest,
MAYBE_ResponseFromHTTPSServiceWorkerIsMarkedAsSecure) {
const char kPageUrl[] = "/service_worker/fetch_event_blob.html";
const char kWorkerUrl[] = "/service_worker/fetch_event_blob.js";
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.ServeFilesFromSourceDirectory("content/test/data");
ASSERT_TRUE(https_server.Start());
scoped_refptr<WorkerActivatedObserver> observer =
new WorkerActivatedObserver(wrapper());
observer->Init();
public_context()->RegisterServiceWorker(
https_server.GetURL(kPageUrl),
https_server.GetURL(kWorkerUrl),
base::Bind(&ExpectResultAndRun, true, base::Bind(&base::DoNothing)));
observer->Wait();
const base::string16 title = base::ASCIIToUTF16("Title");
TitleWatcher title_watcher(shell()->web_contents(), title);
NavigateToURL(shell(), https_server.GetURL(kPageUrl));
EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
EXPECT_FALSE(static_cast<WebContentsImpl*>(shell()->web_contents())
->DisplayedInsecureContent());
NavigationEntry* entry =
shell()->web_contents()->GetController().GetVisibleEntry();
EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, entry->GetSSL().security_style);
shell()->Close();
base::RunLoop run_loop;
public_context()->UnregisterServiceWorker(
https_server.GetURL(kPageUrl),
base::Bind(&ExpectResultAndRun, true, run_loop.QuitClosure()));
run_loop.Run();
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerBrowserTest,
ResponseFromHTTPServiceWorkerIsNotMarkedAsSecure) {
const char kPageUrl[] = "/service_worker/fetch_event_blob.html";
const char kWorkerUrl[] = "/service_worker/fetch_event_blob.js";
scoped_refptr<WorkerActivatedObserver> observer =
new WorkerActivatedObserver(wrapper());
observer->Init();
public_context()->RegisterServiceWorker(
embedded_test_server()->GetURL(kPageUrl),
embedded_test_server()->GetURL(kWorkerUrl),
base::Bind(&ExpectResultAndRun, true, base::Bind(&base::DoNothing)));
observer->Wait();
const base::string16 title = base::ASCIIToUTF16("Title");
TitleWatcher title_watcher(shell()->web_contents(), title);
NavigateToURL(shell(), embedded_test_server()->GetURL(kPageUrl));
EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
EXPECT_FALSE(static_cast<WebContentsImpl*>(shell()->web_contents())
->DisplayedInsecureContent());
NavigationEntry* entry =
shell()->web_contents()->GetController().GetVisibleEntry();
EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED, entry->GetSSL().security_style);
shell()->Close();
base::RunLoop run_loop;
public_context()->UnregisterServiceWorker(
embedded_test_server()->GetURL(kPageUrl),
base::Bind(&ExpectResultAndRun, true, run_loop.QuitClosure()));
run_loop.Run();
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerBrowserTest, ImportsBustMemcache) {
const char kScopeUrl[] = "/service_worker/imports_bust_memcache_scope/";
const char kPageUrl[] = "/service_worker/imports_bust_memcache.html";
const char kScriptUrl[] = "/service_worker/worker_with_one_import.js";
const char kImportUrl[] = "/service_worker/long_lived_import.js";
const base::string16 kOKTitle(base::ASCIIToUTF16("OK"));
const base::string16 kFailTitle(base::ASCIIToUTF16("FAIL"));
RunOnIOThread(
base::Bind(&CreateLongLivedResourceInterceptors,
embedded_test_server()->GetURL(kScriptUrl),
embedded_test_server()->GetURL(kImportUrl)));
TitleWatcher title_watcher(shell()->web_contents(), kOKTitle);
title_watcher.AlsoWaitForTitle(kFailTitle);
NavigateToURL(shell(), embedded_test_server()->GetURL(kPageUrl));
base::string16 title = title_watcher.WaitAndGetTitle();
EXPECT_EQ(kOKTitle, title);
// Verify the number of resources in the implicit script cache is correct.
const int kExpectedNumResources = 2;
int num_resources = 0;
RunOnIOThread(
base::Bind(&CountScriptResources,
base::Unretained(wrapper()),
embedded_test_server()->GetURL(kScopeUrl),
&num_resources));
EXPECT_EQ(kExpectedNumResources, num_resources);
}
class ServiceWorkerBlackBoxBrowserTest : public ServiceWorkerBrowserTest {
public:
using self = ServiceWorkerBlackBoxBrowserTest;
void FindRegistrationOnIO(const GURL& document_url,
ServiceWorkerStatusCode* status,
const base::Closure& continuation) {
wrapper()->FindReadyRegistrationForDocument(
document_url,
base::Bind(&ServiceWorkerBlackBoxBrowserTest::FindRegistrationOnIO2,
base::Unretained(this), status, continuation));
}
void FindRegistrationOnIO2(
ServiceWorkerStatusCode* out_status,
const base::Closure& continuation,
ServiceWorkerStatusCode status,
scoped_refptr<ServiceWorkerRegistration> registration) {
*out_status = status;
if (!registration.get())
EXPECT_NE(SERVICE_WORKER_OK, status);
continuation.Run();
}
};
static int CountRenderProcessHosts() {
int result = 0;
for (RenderProcessHost::iterator iter(RenderProcessHost::AllHostsIterator());
!iter.IsAtEnd();
iter.Advance()) {
result++;
}
return result;
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerBlackBoxBrowserTest, Registration) {
// Close the only window to be sure we're not re-using its RenderProcessHost.
shell()->Close();
EXPECT_EQ(0, CountRenderProcessHosts());
const char kWorkerUrl[] = "/service_worker/fetch_event.js";
const char kScope[] = "/service_worker/";
// Unregistering nothing should return false.
{
base::RunLoop run_loop;
public_context()->UnregisterServiceWorker(
embedded_test_server()->GetURL("/"),
base::Bind(&ExpectResultAndRun, false, run_loop.QuitClosure()));
run_loop.Run();
}
// If we use a worker URL that doesn't exist, registration fails.
{
base::RunLoop run_loop;
public_context()->RegisterServiceWorker(
embedded_test_server()->GetURL(kScope),
embedded_test_server()->GetURL("/does/not/exist"),
base::Bind(&ExpectResultAndRun, false, run_loop.QuitClosure()));
run_loop.Run();
}
EXPECT_EQ(0, CountRenderProcessHosts());
// Register returns when the promise would be resolved.
{
base::RunLoop run_loop;
public_context()->RegisterServiceWorker(
embedded_test_server()->GetURL(kScope),
embedded_test_server()->GetURL(kWorkerUrl),
base::Bind(&ExpectResultAndRun, true, run_loop.QuitClosure()));
run_loop.Run();
}
EXPECT_EQ(1, CountRenderProcessHosts());
// Registering again should succeed, although the algo still
// might not be complete.
{
base::RunLoop run_loop;
public_context()->RegisterServiceWorker(
embedded_test_server()->GetURL(kScope),
embedded_test_server()->GetURL(kWorkerUrl),
base::Bind(&ExpectResultAndRun, true, run_loop.QuitClosure()));
run_loop.Run();
}
// The registration algo might not be far enough along to have
// stored the registration data, so it may not be findable
// at this point.
// Unregistering something should return true.
{
base::RunLoop run_loop;
public_context()->UnregisterServiceWorker(
embedded_test_server()->GetURL(kScope),
base::Bind(&ExpectResultAndRun, true, run_loop.QuitClosure()));
run_loop.Run();
}
EXPECT_GE(1, CountRenderProcessHosts()) << "Unregistering doesn't stop the "
"workers eagerly, so their RPHs "
"can still be running.";
// Should not be able to find it.
{
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
RunOnIOThread(base::Bind(
&ServiceWorkerBlackBoxBrowserTest::FindRegistrationOnIO,
base::Unretained(this),
embedded_test_server()->GetURL("/service_worker/empty.html"), &status));
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND, status);
}
}
#if defined(ANDROID)
#define MAYBE_CrossSiteTransfer DISABLED_CrossSiteTransfer
#else
#define MAYBE_CrossSiteTransfer CrossSiteTransfer
#endif
IN_PROC_BROWSER_TEST_F(ServiceWorkerBrowserTest, MAYBE_CrossSiteTransfer) {
// The first page registers a service worker.
const char kRegisterPageUrl[] = "/service_worker/cross_site_xfer.html";
const base::string16 kOKTitle1(base::ASCIIToUTF16("OK_1"));
const base::string16 kFailTitle1(base::ASCIIToUTF16("FAIL_1"));
content::TitleWatcher title_watcher1(shell()->web_contents(), kOKTitle1);
title_watcher1.AlsoWaitForTitle(kFailTitle1);
NavigateToURL(shell(), embedded_test_server()->GetURL(kRegisterPageUrl));
ASSERT_EQ(kOKTitle1, title_watcher1.WaitAndGetTitle());
// Force process swapping behavior.
ShellContentBrowserClient::SetSwapProcessesForRedirect(true);
// The second pages loads via the serviceworker including a subresource.
const char kConfirmPageUrl[] =
"/service_worker/cross_site_xfer_scope/"
"cross_site_xfer_confirm_via_serviceworker.html";
const base::string16 kOKTitle2(base::ASCIIToUTF16("OK_2"));
const base::string16 kFailTitle2(base::ASCIIToUTF16("FAIL_2"));
content::TitleWatcher title_watcher2(shell()->web_contents(), kOKTitle2);
title_watcher2.AlsoWaitForTitle(kFailTitle2);
NavigateToURL(shell(), embedded_test_server()->GetURL(kConfirmPageUrl));
EXPECT_EQ(kOKTitle2, title_watcher2.WaitAndGetTitle());
}
class ServiceWorkerVersionBrowserV8CacheTest
: public ServiceWorkerVersionBrowserTest,
public ServiceWorkerVersion::Listener {
public:
using self = ServiceWorkerVersionBrowserV8CacheTest;
~ServiceWorkerVersionBrowserV8CacheTest() override {
if (version_)
version_->RemoveListener(this);
}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(switches::kV8CacheOptions, "code");
}
void SetUpRegistrationAndListenerOnIOThread(const std::string& worker_url) {
SetUpRegistrationOnIOThread(worker_url);
version_->AddListener(this);
}
protected:
// ServiceWorkerVersion::Listener overrides
void OnCachedMetadataUpdated(ServiceWorkerVersion* version) override {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
cache_updated_closure_);
}
base::Closure cache_updated_closure_;
};
IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserV8CacheTest, Restart) {
RunOnIOThread(base::Bind(&self::SetUpRegistrationAndListenerOnIOThread,
base::Unretained(this),
"/service_worker/worker.js"));
base::RunLoop cached_metadata_run_loop;
cache_updated_closure_ = cached_metadata_run_loop.QuitClosure();
// Start a worker.
StartWorker(SERVICE_WORKER_OK);
// Wait for the matadata is stored. This run loop should finish when
// OnCachedMetadataUpdated() is called.
cached_metadata_run_loop.Run();
// Activate the worker.
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
base::RunLoop activate_run_loop;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&self::ActivateOnIOThread, base::Unretained(this),
activate_run_loop.QuitClosure(), &status));
activate_run_loop.Run();
ASSERT_EQ(SERVICE_WORKER_OK, status);
// Stop the worker.
StopWorker(SERVICE_WORKER_OK);
// Restart the worker.
StartWorker(SERVICE_WORKER_OK);
// Stop the worker.
StopWorker(SERVICE_WORKER_OK);
}
class CacheStorageSideDataSizeChecker
: public base::RefCountedThreadSafe<CacheStorageSideDataSizeChecker> {
public:
static int GetSize(CacheStorageContextImpl* cache_storage_context,
storage::FileSystemContext* file_system_context,
const GURL& origin,
const std::string& cache_name,
const GURL& url) {
scoped_refptr<CacheStorageSideDataSizeChecker> checker(
new CacheStorageSideDataSizeChecker(cache_storage_context,
file_system_context, origin,
cache_name, url));
return checker->GetSizeImpl();
}
private:
using self = CacheStorageSideDataSizeChecker;
friend class base::RefCountedThreadSafe<self>;
CacheStorageSideDataSizeChecker(
CacheStorageContextImpl* cache_storage_context,
storage::FileSystemContext* file_system_context,
const GURL& origin,
const std::string& cache_name,
const GURL& url)
: cache_storage_context_(cache_storage_context),
file_system_context_(file_system_context),
origin_(origin),
cache_name_(cache_name),
url_(url) {}
~CacheStorageSideDataSizeChecker() {}
int GetSizeImpl() {
int result = 0;
RunOnIOThread(base::Bind(&self::OpenCacheOnIOThread, this, &result));
return result;
}
void OpenCacheOnIOThread(int* result, const base::Closure& continuation) {
cache_storage_context_->cache_manager()->OpenCache(
origin_, cache_name_, base::Bind(&self::OnCacheStorageOpenCallback,
this, result, continuation));
}
void OnCacheStorageOpenCallback(
int* result,
const base::Closure& continuation,
std::unique_ptr<CacheStorageCacheHandle> cache_handle,
CacheStorageError error) {
ASSERT_EQ(CACHE_STORAGE_OK, error);
std::unique_ptr<ServiceWorkerFetchRequest> scoped_request(
new ServiceWorkerFetchRequest());
scoped_request->url = url_;
CacheStorageCache* cache = cache_handle->value();
cache->Match(
std::move(scoped_request), CacheStorageCacheQueryParams(),
base::Bind(&self::OnCacheStorageCacheMatchCallback, this, result,
continuation, base::Passed(std::move(cache_handle))));
}
void OnCacheStorageCacheMatchCallback(
int* result,
const base::Closure& continuation,
std::unique_ptr<CacheStorageCacheHandle> cache_handle,
CacheStorageError error,
std::unique_ptr<ServiceWorkerResponse> response,
std::unique_ptr<storage::BlobDataHandle> blob_data_handle) {
ASSERT_EQ(CACHE_STORAGE_OK, error);
blob_data_handle_ = std::move(blob_data_handle);
blob_reader_ = blob_data_handle_->CreateReader(
file_system_context_,
BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE).get());
const storage::BlobReader::Status status = blob_reader_->CalculateSize(
base::Bind(&self::OnBlobReaderCalculateSizeCallback, this, result,
continuation));
ASSERT_NE(storage::BlobReader::Status::NET_ERROR, status);
if (status == storage::BlobReader::Status::DONE)
OnBlobReaderCalculateSizeCallback(result, continuation, net::OK);
}
void OnBlobReaderCalculateSizeCallback(int* result,
const base::Closure& continuation,
int size_result) {
ASSERT_EQ(net::OK, size_result);
if (!blob_reader_->has_side_data()) {
continuation.Run();
return;
}
const storage::BlobReader::Status status = blob_reader_->ReadSideData(
base::Bind(&self::OnBlobReaderReadSideDataCallback, this, result,
continuation));
ASSERT_NE(storage::BlobReader::Status::NET_ERROR, status);
if (status == storage::BlobReader::Status::DONE) {
OnBlobReaderReadSideDataCallback(result, continuation,
storage::BlobReader::Status::DONE);
}
}
void OnBlobReaderReadSideDataCallback(int* result,
const base::Closure& continuation,
storage::BlobReader::Status status) {
ASSERT_NE(storage::BlobReader::Status::NET_ERROR, status);
*result = blob_reader_->side_data()->size();
continuation.Run();
}
CacheStorageContextImpl* cache_storage_context_;
storage::FileSystemContext* file_system_context_;
const GURL origin_;
const std::string cache_name_;
const GURL url_;
std::unique_ptr<storage::BlobDataHandle> blob_data_handle_;
std::unique_ptr<storage::BlobReader> blob_reader_;
DISALLOW_COPY_AND_ASSIGN(CacheStorageSideDataSizeChecker);
};
class ServiceWorkerV8CacheStrategiesTest : public ServiceWorkerBrowserTest {
public:
ServiceWorkerV8CacheStrategiesTest() {}
~ServiceWorkerV8CacheStrategiesTest() override {}
protected:
void CheckStrategyIsNone() {
RegisterAndActivateServiceWorker();
NavigateToTestPage();
WaitUntilSideDataSizeIs(0);
NavigateToTestPage();
WaitUntilSideDataSizeIs(0);
NavigateToTestPage();
WaitUntilSideDataSizeIs(0);
}
void CheckStrategyIsNormal() {
RegisterAndActivateServiceWorker();
NavigateToTestPage();
// fetch_event_response_via_cache.js returns |cloned_response| for the first
// load. So the V8 code cache should not be stored to the CacheStorage.
WaitUntilSideDataSizeIs(0);
NavigateToTestPage();
// V8ScriptRunner::setCacheTimeStamp() stores 12 byte data (tag +
// timestamp).
WaitUntilSideDataSizeIs(kV8CacheTimeStampDataSize);
NavigateToTestPage();
// The V8 code cache must be stored to the CacheStorage which must be bigger
// than 12 byte.
WaitUntilSideDataSizeIsBiggerThan(kV8CacheTimeStampDataSize);
}
void CheckStrategyIsAggressive() {
RegisterAndActivateServiceWorker();
NavigateToTestPage();
// fetch_event_response_via_cache.js returns |cloned_response| for the first
// load. So the V8 code cache should not be stored to the CacheStorage.
WaitUntilSideDataSizeIs(0);
NavigateToTestPage();
// The V8 code cache must be stored to the CacheStorage which must be bigger
// than 12 byte.
WaitUntilSideDataSizeIsBiggerThan(kV8CacheTimeStampDataSize);
NavigateToTestPage();
WaitUntilSideDataSizeIsBiggerThan(kV8CacheTimeStampDataSize);
}
private:
static const std::string kPageUrl;
static const std::string kWorkerUrl;
static const std::string kScriptUrl;
static const int kV8CacheTimeStampDataSize;
void RegisterAndActivateServiceWorker() {
scoped_refptr<WorkerActivatedObserver> observer =
new WorkerActivatedObserver(wrapper());
observer->Init();
public_context()->RegisterServiceWorker(
embedded_test_server()->GetURL(kPageUrl),
embedded_test_server()->GetURL(kWorkerUrl),
base::Bind(&ExpectResultAndRun, true, base::Bind(&base::DoNothing)));
observer->Wait();
}
void NavigateToTestPage() {
const base::string16 title =
base::ASCIIToUTF16("Title was changed by the script.");
TitleWatcher title_watcher(shell()->web_contents(), title);
NavigateToURL(shell(), embedded_test_server()->GetURL(kPageUrl));
EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
}
int GetSideDataSize() {
StoragePartition* partition = BrowserContext::GetDefaultStoragePartition(
shell()->web_contents()->GetBrowserContext());
return CacheStorageSideDataSizeChecker::GetSize(
static_cast<CacheStorageContextImpl*>(
partition->GetCacheStorageContext()),
partition->GetFileSystemContext(), embedded_test_server()->base_url(),
std::string("cache_name"), embedded_test_server()->GetURL(kScriptUrl));
}
void WaitUntilSideDataSizeIs(int expected_size) {
while (true) {
if (GetSideDataSize() == expected_size)
return;
}
}
void WaitUntilSideDataSizeIsBiggerThan(int minimum_size) {
while (true) {
if (GetSideDataSize() > minimum_size)
return;
}
}
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerV8CacheStrategiesTest);
};
const std::string ServiceWorkerV8CacheStrategiesTest::kPageUrl =
"/service_worker/v8_cache_test.html";
const std::string ServiceWorkerV8CacheStrategiesTest::kWorkerUrl =
"/service_worker/fetch_event_response_via_cache.js";
const std::string ServiceWorkerV8CacheStrategiesTest::kScriptUrl =
"/service_worker/v8_cache_test.js";
// V8ScriptRunner::setCacheTimeStamp() stores 12 byte data (tag + timestamp).
const int ServiceWorkerV8CacheStrategiesTest::kV8CacheTimeStampDataSize =
sizeof(unsigned) + sizeof(double);
IN_PROC_BROWSER_TEST_F(ServiceWorkerV8CacheStrategiesTest,
V8CacheOnCacheStorage) {
// The strategy is "aggressive" on default.
CheckStrategyIsAggressive();
}
class ServiceWorkerV8CacheStrategiesNoneTest
: public ServiceWorkerV8CacheStrategiesTest {
public:
ServiceWorkerV8CacheStrategiesNoneTest() {}
~ServiceWorkerV8CacheStrategiesNoneTest() override {}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(switches::kV8CacheStrategiesForCacheStorage,
"none");
}
private:
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerV8CacheStrategiesNoneTest);
};
IN_PROC_BROWSER_TEST_F(ServiceWorkerV8CacheStrategiesNoneTest,
V8CacheOnCacheStorage) {
CheckStrategyIsNone();
}
class ServiceWorkerV8CacheStrategiesNormalTest
: public ServiceWorkerV8CacheStrategiesTest {
public:
ServiceWorkerV8CacheStrategiesNormalTest() {}
~ServiceWorkerV8CacheStrategiesNormalTest() override {}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(switches::kV8CacheStrategiesForCacheStorage,
"normal");
}
private:
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerV8CacheStrategiesNormalTest);
};
IN_PROC_BROWSER_TEST_F(ServiceWorkerV8CacheStrategiesNormalTest,
V8CacheOnCacheStorage) {
CheckStrategyIsNormal();
}
class ServiceWorkerV8CacheStrategiesAggressiveTest
: public ServiceWorkerV8CacheStrategiesTest {
public:
ServiceWorkerV8CacheStrategiesAggressiveTest() {}
~ServiceWorkerV8CacheStrategiesAggressiveTest() override {}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(switches::kV8CacheStrategiesForCacheStorage,
"aggressive");
}
private:
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerV8CacheStrategiesAggressiveTest);
};
IN_PROC_BROWSER_TEST_F(ServiceWorkerV8CacheStrategiesAggressiveTest,
V8CacheOnCacheStorage) {
CheckStrategyIsAggressive();
}
// ServiceWorkerDisableWebSecurityTests check the behavior when the web security
// is disabled. If '--disable-web-security' flag is set, we don't check the
// origin equality in Blink. So the Service Worker related APIs should succeed
// even if it is thouching other origin Service Workers.
class ServiceWorkerDisableWebSecurityTest : public ServiceWorkerBrowserTest {
public:
ServiceWorkerDisableWebSecurityTest() {}
~ServiceWorkerDisableWebSecurityTest() override {}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kDisableWebSecurity);
}
void SetUpOnMainThread() override {
cross_origin_server_.ServeFilesFromSourceDirectory("content/test/data");
ASSERT_TRUE(cross_origin_server_.Start());
ServiceWorkerBrowserTest::SetUpOnMainThread();
}
void RegisterServiceWorkerOnCrossOriginServer(const std::string& scope,
const std::string& script) {
scoped_refptr<WorkerActivatedObserver> observer =
new WorkerActivatedObserver(wrapper());
observer->Init();
public_context()->RegisterServiceWorker(
cross_origin_server_.GetURL(scope), cross_origin_server_.GetURL(script),
base::Bind(&ExpectResultAndRun, true, base::Bind(&base::DoNothing)));
observer->Wait();
}
void RunTestWithCrossOriginURL(const std::string& test_page,
const std::string& cross_origin_url) {
const base::string16 title = base::ASCIIToUTF16("PASS");
TitleWatcher title_watcher(shell()->web_contents(), title);
NavigateToURL(shell(),
embedded_test_server()->GetURL(
test_page + "?" +
cross_origin_server_.GetURL(cross_origin_url).spec()));
EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
}
private:
net::EmbeddedTestServer cross_origin_server_;
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerDisableWebSecurityTest);
};
IN_PROC_BROWSER_TEST_F(ServiceWorkerDisableWebSecurityTest,
GetRegistrationNoCrash) {
const char kPageUrl[] =
"/service_worker/disable_web_security_get_registration.html";
const char kScopeUrl[] = "/service_worker/";
RunTestWithCrossOriginURL(kPageUrl, kScopeUrl);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerDisableWebSecurityTest, RegisterNoCrash) {
const char kPageUrl[] = "/service_worker/disable_web_security_register.html";
const char kScopeUrl[] = "/service_worker/";
RunTestWithCrossOriginURL(kPageUrl, kScopeUrl);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerDisableWebSecurityTest, UnregisterNoCrash) {
const char kPageUrl[] =
"/service_worker/disable_web_security_unregister.html";
const char kScopeUrl[] = "/service_worker/scope/";
const char kWorkerUrl[] = "/service_worker/fetch_event_blob.js";
RegisterServiceWorkerOnCrossOriginServer(kScopeUrl, kWorkerUrl);
RunTestWithCrossOriginURL(kPageUrl, kScopeUrl);
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerDisableWebSecurityTest, UpdateNoCrash) {
const char kPageUrl[] = "/service_worker/disable_web_security_update.html";
const char kScopeUrl[] = "/service_worker/scope/";
const char kWorkerUrl[] = "/service_worker/fetch_event_blob.js";
RegisterServiceWorkerOnCrossOriginServer(kScopeUrl, kWorkerUrl);
RunTestWithCrossOriginURL(kPageUrl, kScopeUrl);
}
} // namespace content