blob: 86c40a446ecc960060ff87f8904f00bb142d4f82 [file] [log] [blame]
// Copyright 2013 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 "content/browser/service_worker/service_worker_dispatcher_host.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/run_loop.h"
#include "content/browser/browser_thread_impl.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_test_helper.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/common/service_worker/embedded_worker_messages.h"
#include "content/common/service_worker/service_worker_messages.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/mock_resource_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/test/test_content_browser_client.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
static void SaveStatusCallback(bool* called,
ServiceWorkerStatusCode* out,
ServiceWorkerStatusCode status) {
*called = true;
*out = status;
}
}
static const int kRenderProcessId = 1;
static const int kRenderFrameId = 1;
class TestingServiceWorkerDispatcherHost : public ServiceWorkerDispatcherHost {
public:
TestingServiceWorkerDispatcherHost(
int process_id,
ServiceWorkerContextWrapper* context_wrapper,
ResourceContext* resource_context,
EmbeddedWorkerTestHelper* helper)
: ServiceWorkerDispatcherHost(process_id, NULL, resource_context),
bad_messages_received_count_(0),
helper_(helper) {
Init(context_wrapper);
}
bool Send(IPC::Message* message) override { return helper_->Send(message); }
IPC::TestSink* ipc_sink() { return helper_->ipc_sink(); }
void BadMessageReceived() override { ++bad_messages_received_count_; }
int bad_messages_received_count_;
protected:
EmbeddedWorkerTestHelper* helper_;
~TestingServiceWorkerDispatcherHost() override {}
};
class ServiceWorkerDispatcherHostTest : public testing::Test {
protected:
ServiceWorkerDispatcherHostTest()
: browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {}
void SetUp() override {
helper_.reset(
new EmbeddedWorkerTestHelper(base::FilePath(), kRenderProcessId));
dispatcher_host_ = new TestingServiceWorkerDispatcherHost(
kRenderProcessId, context_wrapper(), &resource_context_, helper_.get());
}
void TearDown() override { helper_.reset(); }
ServiceWorkerContextCore* context() { return helper_->context(); }
ServiceWorkerContextWrapper* context_wrapper() {
return helper_->context_wrapper();
}
void SendRegister(int64 provider_id, GURL pattern, GURL worker_url) {
dispatcher_host_->OnMessageReceived(
ServiceWorkerHostMsg_RegisterServiceWorker(
-1, -1, provider_id, pattern, worker_url));
base::RunLoop().RunUntilIdle();
}
void Register(int64 provider_id,
GURL pattern,
GURL worker_url,
uint32 expected_message) {
SendRegister(provider_id, pattern, worker_url);
EXPECT_TRUE(dispatcher_host_->ipc_sink()->GetUniqueMessageMatching(
expected_message));
dispatcher_host_->ipc_sink()->ClearMessages();
}
void SendUnregister(int64 provider_id, GURL pattern) {
dispatcher_host_->OnMessageReceived(
ServiceWorkerHostMsg_UnregisterServiceWorker(
-1, -1, provider_id, pattern));
base::RunLoop().RunUntilIdle();
}
void Unregister(int64 provider_id, GURL pattern, uint32 expected_message) {
SendUnregister(provider_id, pattern);
EXPECT_TRUE(dispatcher_host_->ipc_sink()->GetUniqueMessageMatching(
expected_message));
dispatcher_host_->ipc_sink()->ClearMessages();
}
void SendGetRegistration(int64 provider_id, GURL document_url) {
dispatcher_host_->OnMessageReceived(
ServiceWorkerHostMsg_GetRegistration(
-1, -1, provider_id, document_url));
base::RunLoop().RunUntilIdle();
}
void GetRegistration(int64 provider_id,
GURL document_url,
uint32 expected_message) {
SendGetRegistration(provider_id, document_url);
EXPECT_TRUE(dispatcher_host_->ipc_sink()->GetUniqueMessageMatching(
expected_message));
dispatcher_host_->ipc_sink()->ClearMessages();
}
ServiceWorkerProviderHost* CreateServiceWorkerProviderHost(int provider_id) {
return new ServiceWorkerProviderHost(
kRenderProcessId, kRenderFrameId, provider_id,
SERVICE_WORKER_PROVIDER_FOR_WINDOW, context()->AsWeakPtr(),
dispatcher_host_.get());
}
TestBrowserThreadBundle browser_thread_bundle_;
content::MockResourceContext resource_context_;
scoped_ptr<EmbeddedWorkerTestHelper> helper_;
scoped_refptr<TestingServiceWorkerDispatcherHost> dispatcher_host_;
};
class ServiceWorkerTestContentBrowserClient : public TestContentBrowserClient {
public:
ServiceWorkerTestContentBrowserClient() {}
bool AllowServiceWorker(const GURL& scope,
const GURL& first_party,
content::ResourceContext* context,
int render_process_id,
int render_frame_id) override {
return false;
}
};
TEST_F(ServiceWorkerDispatcherHostTest,
Register_ContentSettingsDisallowsServiceWorker) {
ServiceWorkerTestContentBrowserClient test_browser_client;
ContentBrowserClient* old_browser_client =
SetBrowserClientForTesting(&test_browser_client);
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("https://www.example.com/foo"));
context()->AddProviderHost(host.Pass());
Register(kProviderId,
GURL("https://www.example.com/"),
GURL("https://www.example.com/bar"),
ServiceWorkerMsg_ServiceWorkerRegistrationError::ID);
Unregister(kProviderId,
GURL("https://www.example.com/"),
ServiceWorkerMsg_ServiceWorkerUnregistrationError::ID);
GetRegistration(kProviderId,
GURL("https://www.example.com/"),
ServiceWorkerMsg_ServiceWorkerGetRegistrationError::ID);
SetBrowserClientForTesting(old_browser_client);
}
TEST_F(ServiceWorkerDispatcherHostTest, Register_HTTPS) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("https://www.example.com/foo"));
context()->AddProviderHost(host.Pass());
Register(kProviderId,
GURL("https://www.example.com/"),
GURL("https://www.example.com/bar"),
ServiceWorkerMsg_ServiceWorkerRegistered::ID);
}
TEST_F(ServiceWorkerDispatcherHostTest, Register_NonSecureTransportLocalhost) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("http://127.0.0.3:81/foo"));
context()->AddProviderHost(host.Pass());
Register(kProviderId,
GURL("http://127.0.0.3:81/bar"),
GURL("http://127.0.0.3:81/baz"),
ServiceWorkerMsg_ServiceWorkerRegistered::ID);
}
TEST_F(ServiceWorkerDispatcherHostTest, Register_InvalidScopeShouldFail) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("https://www.example.com/foo"));
context()->AddProviderHost(host.Pass());
SendRegister(kProviderId, GURL(""),
GURL("https://www.example.com/bar/hoge.js"));
EXPECT_EQ(1, dispatcher_host_->bad_messages_received_count_);
}
TEST_F(ServiceWorkerDispatcherHostTest, Register_InvalidScriptShouldFail) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("https://www.example.com/foo"));
context()->AddProviderHost(host.Pass());
SendRegister(kProviderId, GURL("https://www.example.com/bar/"), GURL(""));
EXPECT_EQ(1, dispatcher_host_->bad_messages_received_count_);
}
TEST_F(ServiceWorkerDispatcherHostTest, Register_NonSecureOriginShouldFail) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("http://www.example.com/foo"));
context()->AddProviderHost(host.Pass());
SendRegister(kProviderId,
GURL("http://www.example.com/"),
GURL("http://www.example.com/bar"));
EXPECT_EQ(1, dispatcher_host_->bad_messages_received_count_);
}
TEST_F(ServiceWorkerDispatcherHostTest, Register_CrossOriginShouldFail) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("https://www.example.com/foo"));
context()->AddProviderHost(host.Pass());
// Script has a different host
SendRegister(kProviderId,
GURL("https://www.example.com/"),
GURL("https://foo.example.com/bar"));
EXPECT_EQ(1, dispatcher_host_->bad_messages_received_count_);
// Scope has a different host
SendRegister(kProviderId,
GURL("https://foo.example.com/"),
GURL("https://www.example.com/bar"));
EXPECT_EQ(2, dispatcher_host_->bad_messages_received_count_);
// Script has a different port
SendRegister(kProviderId,
GURL("https://www.example.com/"),
GURL("https://www.example.com:8080/bar"));
EXPECT_EQ(3, dispatcher_host_->bad_messages_received_count_);
// Scope has a different transport
SendRegister(kProviderId,
GURL("wss://www.example.com/"),
GURL("https://www.example.com/bar"));
EXPECT_EQ(4, dispatcher_host_->bad_messages_received_count_);
// Script and scope have a different host but match each other
SendRegister(kProviderId,
GURL("https://foo.example.com/"),
GURL("https://foo.example.com/bar"));
EXPECT_EQ(5, dispatcher_host_->bad_messages_received_count_);
// Script and scope URLs are invalid
SendRegister(kProviderId,
GURL(),
GURL("h@ttps://@"));
EXPECT_EQ(6, dispatcher_host_->bad_messages_received_count_);
}
TEST_F(ServiceWorkerDispatcherHostTest,
Register_FileSystemDocumentShouldFail) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("filesystem:https://www.example.com/temporary/a"));
context()->AddProviderHost(host.Pass());
SendRegister(kProviderId,
GURL("filesystem:https://www.example.com/temporary/"),
GURL("https://www.example.com/temporary/bar"));
EXPECT_EQ(1, dispatcher_host_->bad_messages_received_count_);
SendRegister(kProviderId,
GURL("https://www.example.com/temporary/"),
GURL("filesystem:https://www.example.com/temporary/bar"));
EXPECT_EQ(2, dispatcher_host_->bad_messages_received_count_);
SendRegister(kProviderId,
GURL("filesystem:https://www.example.com/temporary/"),
GURL("filesystem:https://www.example.com/temporary/bar"));
EXPECT_EQ(3, dispatcher_host_->bad_messages_received_count_);
}
TEST_F(ServiceWorkerDispatcherHostTest,
Register_FileSystemScriptOrScopeShouldFail) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("https://www.example.com/temporary/"));
context()->AddProviderHost(host.Pass());
SendRegister(kProviderId,
GURL("filesystem:https://www.example.com/temporary/"),
GURL("https://www.example.com/temporary/bar"));
EXPECT_EQ(1, dispatcher_host_->bad_messages_received_count_);
SendRegister(kProviderId,
GURL("https://www.example.com/temporary/"),
GURL("filesystem:https://www.example.com/temporary/bar"));
EXPECT_EQ(2, dispatcher_host_->bad_messages_received_count_);
SendRegister(kProviderId,
GURL("filesystem:https://www.example.com/temporary/"),
GURL("filesystem:https://www.example.com/temporary/bar"));
EXPECT_EQ(3, dispatcher_host_->bad_messages_received_count_);
}
TEST_F(ServiceWorkerDispatcherHostTest, Unregister_HTTPS) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("https://www.example.com/foo"));
context()->AddProviderHost(host.Pass());
Unregister(kProviderId,
GURL("https://www.example.com/"),
ServiceWorkerMsg_ServiceWorkerUnregistered::ID);
}
TEST_F(ServiceWorkerDispatcherHostTest,
Unregister_NotSecureTransportLocalhost) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("http://localhost/foo"));
context()->AddProviderHost(host.Pass());
Unregister(kProviderId,
GURL("http://localhost/"),
ServiceWorkerMsg_ServiceWorkerUnregistered::ID);
}
TEST_F(ServiceWorkerDispatcherHostTest, Unregister_CrossOriginShouldFail) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("https://www.example.com/foo"));
context()->AddProviderHost(host.Pass());
SendUnregister(kProviderId, GURL("https://foo.example.com/"));
EXPECT_EQ(1, dispatcher_host_->bad_messages_received_count_);
}
TEST_F(ServiceWorkerDispatcherHostTest, Unregister_InvalidScopeShouldFail) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("https://www.example.com/foo"));
context()->AddProviderHost(host.Pass());
SendUnregister(kProviderId, GURL(""));
EXPECT_EQ(1, dispatcher_host_->bad_messages_received_count_);
}
TEST_F(ServiceWorkerDispatcherHostTest, Unregister_NonSecureOriginShouldFail) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("http://www.example.com/foo"));
context()->AddProviderHost(host.Pass());
SendUnregister(kProviderId, GURL("http://www.example.com/"));
EXPECT_EQ(1, dispatcher_host_->bad_messages_received_count_);
}
TEST_F(ServiceWorkerDispatcherHostTest, EarlyContextDeletion) {
helper_->ShutdownContext();
// Let the shutdown reach the simulated IO thread.
base::RunLoop().RunUntilIdle();
Register(-1,
GURL(),
GURL(),
ServiceWorkerMsg_ServiceWorkerRegistrationError::ID);
}
TEST_F(ServiceWorkerDispatcherHostTest, ProviderCreatedAndDestroyed) {
const int kProviderId = 1001; // Test with a value != kRenderProcessId.
dispatcher_host_->OnMessageReceived(ServiceWorkerHostMsg_ProviderCreated(
kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW));
EXPECT_TRUE(context()->GetProviderHost(kRenderProcessId, kProviderId));
// Two with the same ID should be seen as a bad message.
dispatcher_host_->OnMessageReceived(ServiceWorkerHostMsg_ProviderCreated(
kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW));
EXPECT_EQ(1, dispatcher_host_->bad_messages_received_count_);
dispatcher_host_->OnMessageReceived(
ServiceWorkerHostMsg_ProviderDestroyed(kProviderId));
EXPECT_FALSE(context()->GetProviderHost(kRenderProcessId, kProviderId));
// Destroying an ID that does not exist warrants a bad message.
dispatcher_host_->OnMessageReceived(
ServiceWorkerHostMsg_ProviderDestroyed(kProviderId));
EXPECT_EQ(2, dispatcher_host_->bad_messages_received_count_);
// Deletion of the dispatcher_host should cause providers for that
// process to get deleted as well.
dispatcher_host_->OnMessageReceived(ServiceWorkerHostMsg_ProviderCreated(
kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW));
EXPECT_TRUE(context()->GetProviderHost(kRenderProcessId, kProviderId));
EXPECT_TRUE(dispatcher_host_->HasOneRef());
dispatcher_host_ = NULL;
EXPECT_FALSE(context()->GetProviderHost(kRenderProcessId, kProviderId));
}
TEST_F(ServiceWorkerDispatcherHostTest, GetRegistration_SameOrigin) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("https://www.example.com/foo"));
context()->AddProviderHost(host.Pass());
GetRegistration(kProviderId,
GURL("https://www.example.com/"),
ServiceWorkerMsg_DidGetRegistration::ID);
}
TEST_F(ServiceWorkerDispatcherHostTest, GetRegistration_CrossOriginShouldFail) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("https://www.example.com/foo"));
context()->AddProviderHost(host.Pass());
SendGetRegistration(kProviderId, GURL("https://foo.example.com/"));
EXPECT_EQ(1, dispatcher_host_->bad_messages_received_count_);
}
TEST_F(ServiceWorkerDispatcherHostTest,
GetRegistration_InvalidScopeShouldFail) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("https://www.example.com/foo"));
context()->AddProviderHost(host.Pass());
SendGetRegistration(kProviderId, GURL(""));
EXPECT_EQ(1, dispatcher_host_->bad_messages_received_count_);
}
TEST_F(ServiceWorkerDispatcherHostTest,
GetRegistration_NonSecureOriginShouldFail) {
const int64 kProviderId = 99; // Dummy value
scoped_ptr<ServiceWorkerProviderHost> host(
CreateServiceWorkerProviderHost(kProviderId));
host->SetDocumentUrl(GURL("http://www.example.com/foo"));
context()->AddProviderHost(host.Pass());
SendGetRegistration(kProviderId, GURL("http://www.example.com/"));
EXPECT_EQ(1, dispatcher_host_->bad_messages_received_count_);
}
TEST_F(ServiceWorkerDispatcherHostTest, GetRegistration_EarlyContextDeletion) {
helper_->ShutdownContext();
// Let the shutdown reach the simulated IO thread.
base::RunLoop().RunUntilIdle();
GetRegistration(-1,
GURL(),
ServiceWorkerMsg_ServiceWorkerGetRegistrationError::ID);
}
TEST_F(ServiceWorkerDispatcherHostTest, CleanupOnRendererCrash) {
// Add a provider and worker.
const int64 kProviderId = 99; // Dummy value
dispatcher_host_->OnMessageReceived(ServiceWorkerHostMsg_ProviderCreated(
kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW));
GURL pattern = GURL("http://www.example.com/");
scoped_refptr<ServiceWorkerRegistration> registration(
new ServiceWorkerRegistration(pattern,
1L,
helper_->context()->AsWeakPtr()));
scoped_refptr<ServiceWorkerVersion> version(
new ServiceWorkerVersion(registration.get(),
GURL("http://www.example.com/service_worker.js"),
1L,
helper_->context()->AsWeakPtr()));
// Make the registration findable via storage functions.
helper_->context()->storage()->LazyInitialize(base::Bind(&base::DoNothing));
base::RunLoop().RunUntilIdle();
bool called = false;
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_ABORT;
helper_->context()->storage()->StoreRegistration(
registration.get(),
version.get(),
base::Bind(&SaveStatusCallback, &called, &status));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(called);
ASSERT_EQ(SERVICE_WORKER_OK, status);
helper_->SimulateAddProcessToPattern(pattern, kRenderProcessId);
// Start up the worker.
status = SERVICE_WORKER_ERROR_ABORT;
version->StartWorker(base::Bind(&SaveStatusCallback, &called, &status));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(SERVICE_WORKER_OK, status);
EXPECT_TRUE(context()->GetProviderHost(kRenderProcessId, kProviderId));
EXPECT_EQ(ServiceWorkerVersion::RUNNING, version->running_status());
// Simulate the render process crashing.
dispatcher_host_->OnFilterRemoved();
// The dispatcher host should clean up the state from the process.
EXPECT_FALSE(context()->GetProviderHost(kRenderProcessId, kProviderId));
EXPECT_EQ(ServiceWorkerVersion::STOPPED, version->running_status());
// We should be able to hook up a new dispatcher host although the old object
// is not yet destroyed. This is what the browser does when reusing a crashed
// render process.
scoped_refptr<TestingServiceWorkerDispatcherHost> new_dispatcher_host(
new TestingServiceWorkerDispatcherHost(kRenderProcessId,
context_wrapper(),
&resource_context_,
helper_.get()));
// To show the new dispatcher can operate, simulate provider creation. Since
// the old dispatcher cleaned up the old provider host, the new one won't
// complain.
new_dispatcher_host->OnMessageReceived(ServiceWorkerHostMsg_ProviderCreated(
kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW));
EXPECT_EQ(0, new_dispatcher_host->bad_messages_received_count_);
}
} // namespace content