// 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 "base/bind.h" #include "base/callback.h" #include "base/command_line.h" #include "base/run_loop.h" #include "content/browser/service_worker/embedded_worker_instance.h" #include "content/browser/service_worker/embedded_worker_registry.h" #include "content/browser/service_worker/service_worker_context_core.h" #include "content/browser/service_worker/service_worker_context_wrapper.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/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/render_process_host.h" #include "content/public/browser/storage_partition.h" #include "content/public/browser/web_contents.h" #include "content/public/common/content_switches.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 "net/test/embedded_test_server/embedded_test_server.h" namespace content { namespace { struct FetchResult { ServiceWorkerStatusCode status; ServiceWorkerFetchEventResult result; ServiceWorkerResponse response; }; void RunAndQuit(const base::Closure& closure, const base::Closure& quit, base::MessageLoopProxy* original_message_loop) { closure.Run(); original_message_loop->PostTask(FROM_HERE, quit); } void RunOnIOThread(const base::Closure& closure) { base::RunLoop run_loop; BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&RunAndQuit, closure, run_loop.QuitClosure(), base::MessageLoopProxy::current())); run_loop.Run(); } void RunOnIOThread( const base::Callback& closure) { base::RunLoop run_loop; base::Closure quit_on_original_thread = base::Bind(base::IgnoreResult(&base::MessageLoopProxy::PostTask), base::MessageLoopProxy::current().get(), FROM_HERE, run_loop.QuitClosure()); BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(closure, quit_on_original_thread)); run_loop.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 ReceiveFetchResult(BrowserThread::ID run_quit_thread, const base::Closure& quit, FetchResult* out_result, ServiceWorkerStatusCode actual_status, ServiceWorkerFetchEventResult actual_result, const ServiceWorkerResponse& actual_response) { out_result->status = actual_status; out_result->result = actual_result; out_result->response = actual_response; if (!quit.is_null()) BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, quit); } ServiceWorkerVersion::FetchCallback CreateResponseReceiver( BrowserThread::ID run_quit_thread, const base::Closure& quit, FetchResult* result) { return base::Bind(&ReceiveFetchResult, run_quit_thread, quit, result); } } // namespace class ServiceWorkerBrowserTest : public ContentBrowserTest { protected: typedef ServiceWorkerBrowserTest self; virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { command_line->AppendSwitch(switches::kEnableServiceWorker); } virtual void SetUpOnMainThread() OVERRIDE { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); StoragePartition* partition = BrowserContext::GetDefaultStoragePartition( shell()->web_contents()->GetBrowserContext()); wrapper_ = static_cast( 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, this)); } virtual void TearDownOnMainThread() OVERRIDE { RunOnIOThread(base::Bind(&self::TearDownOnIOThread, this)); wrapper_ = NULL; } virtual void SetUpOnIOThread() {} virtual void TearDownOnIOThread() {} ServiceWorkerContextWrapper* wrapper() { return wrapper_.get(); } ServiceWorkerContext* public_context() { return wrapper(); } void AssociateRendererProcessToWorker(EmbeddedWorkerInstance* worker) { worker->AddProcessReference( shell()->web_contents()->GetRenderProcessHost()->GetID()); } private: scoped_refptr wrapper_; }; class EmbeddedWorkerBrowserTest : public ServiceWorkerBrowserTest, public EmbeddedWorkerInstance::Listener { public: typedef EmbeddedWorkerBrowserTest self; EmbeddedWorkerBrowserTest() : last_worker_status_(EmbeddedWorkerInstance::STOPPED) {} virtual ~EmbeddedWorkerBrowserTest() {} virtual void TearDownOnIOThread() OVERRIDE { if (worker_) { worker_->RemoveListener(this); worker_.reset(); } } void StartOnIOThread() { ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); worker_ = wrapper()->context()->embedded_worker_registry()->CreateWorker(); EXPECT_EQ(EmbeddedWorkerInstance::STOPPED, worker_->status()); worker_->AddListener(this); AssociateRendererProcessToWorker(worker_.get()); const int64 service_worker_version_id = 33L; const GURL scope = embedded_test_server()->GetURL("/*"); const GURL script_url = embedded_test_server()->GetURL( "/service_worker/worker.js"); std::vector processes; processes.push_back( shell()->web_contents()->GetRenderProcessHost()->GetID()); worker_->Start( service_worker_version_id, scope, script_url, processes, base::Bind(&EmbeddedWorkerBrowserTest::StartOnIOThread2, this)); } void StartOnIOThread2(ServiceWorkerStatusCode status) { last_worker_status_ = worker_->status(); EXPECT_EQ(SERVICE_WORKER_OK, status); EXPECT_EQ(EmbeddedWorkerInstance::STARTING, last_worker_status_); if (status != SERVICE_WORKER_OK && !done_closure_.is_null()) done_closure_.Run(); } void StopOnIOThread() { ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); EXPECT_EQ(EmbeddedWorkerInstance::RUNNING, worker_->status()); ServiceWorkerStatusCode status = worker_->Stop(); last_worker_status_ = worker_->status(); EXPECT_EQ(SERVICE_WORKER_OK, status); EXPECT_EQ(EmbeddedWorkerInstance::STOPPING, last_worker_status_); if (status != SERVICE_WORKER_OK && !done_closure_.is_null()) done_closure_.Run(); } protected: // EmbeddedWorkerInstance::Observer overrides: virtual void OnStarted() OVERRIDE { ASSERT_TRUE(worker_ != NULL); ASSERT_FALSE(done_closure_.is_null()); last_worker_status_ = worker_->status(); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, done_closure_); } virtual void OnStopped() OVERRIDE { ASSERT_TRUE(worker_ != NULL); ASSERT_FALSE(done_closure_.is_null()); last_worker_status_ = worker_->status(); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, done_closure_); } virtual void OnReportException(const base::string16& error_message, int line_number, int column_number, const GURL& source_url) OVERRIDE {} virtual void OnReportConsoleMessage(int source_identifier, int message_level, const base::string16& message, int line_number, const GURL& source_url) OVERRIDE {} virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { return false; } scoped_ptr worker_; EmbeddedWorkerInstance::Status last_worker_status_; // Called by EmbeddedWorkerInstance::Observer overrides so that // test code can wait for the worker status notifications. base::Closure done_closure_; }; class ServiceWorkerVersionBrowserTest : public ServiceWorkerBrowserTest { public: typedef ServiceWorkerVersionBrowserTest self; virtual ~ServiceWorkerVersionBrowserTest() {} virtual void TearDownOnIOThread() OVERRIDE { registration_ = NULL; version_ = NULL; } void InstallTestHelper(const std::string& worker_url, ServiceWorkerStatusCode expected_status) { RunOnIOThread(base::Bind(&self::SetUpRegistrationOnIOThread, 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, 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, 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, this, worker_url)); version_->SetStatus(ServiceWorkerVersion::INSTALLED); ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; base::RunLoop run_loop; BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind( &self::ActivateOnIOThread, this, run_loop.QuitClosure(), &status)); run_loop.Run(); ASSERT_EQ(expected_status, status); } void FetchOnRegisteredWorker(ServiceWorkerFetchEventResult* result, ServiceWorkerResponse* response) { 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, this, fetch_run_loop.QuitClosure(), &fetch_result)); fetch_run_loop.Run(); *result = fetch_result.result; *response = fetch_result.response; ASSERT_EQ(SERVICE_WORKER_OK, fetch_result.status); } void FetchTestHelper(const std::string& worker_url, ServiceWorkerFetchEventResult* result, ServiceWorkerResponse* response) { RunOnIOThread( base::Bind(&self::SetUpRegistrationOnIOThread, this, worker_url)); FetchOnRegisteredWorker(result, response); } void SetUpRegistrationOnIOThread(const std::string& worker_url) { registration_ = new ServiceWorkerRegistration( embedded_test_server()->GetURL("/*"), embedded_test_server()->GetURL(worker_url), wrapper()->context()->storage()->NewRegistrationId(), wrapper()->context()->AsWeakPtr()); version_ = new ServiceWorkerVersion( registration_, wrapper()->context()->storage()->NewVersionId(), wrapper()->context()->AsWeakPtr()); AssociateRendererProcessToWorker(version_->embedded_worker()); } void StartOnIOThread(const base::Closure& done, ServiceWorkerStatusCode* result) { ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); version_->StartWorker(CreateReceiver(BrowserThread::UI, done, result)); } void InstallOnIOThread(const base::Closure& done, ServiceWorkerStatusCode* result) { ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); version_->DispatchInstallEvent( -1, CreateReceiver(BrowserThread::UI, done, result)); } void ActivateOnIOThread(const base::Closure& done, ServiceWorkerStatusCode* result) { ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); version_->SetStatus(ServiceWorkerVersion::INSTALLED); version_->DispatchActivateEvent( CreateReceiver(BrowserThread::UI, done, result)); } void FetchOnIOThread(const base::Closure& done, FetchResult* result) { ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); ServiceWorkerFetchRequest request( embedded_test_server()->GetURL("/service_worker/empty.html"), "GET", std::map()); version_->SetStatus(ServiceWorkerVersion::ACTIVE); version_->DispatchFetchEvent( request, CreateResponseReceiver(BrowserThread::UI, done, result)); } void StopOnIOThread(const base::Closure& done, ServiceWorkerStatusCode* result) { ASSERT_TRUE(version_); version_->StopWorker(CreateReceiver(BrowserThread::UI, done, result)); } void SyncEventOnIOThread(const base::Closure& done, ServiceWorkerStatusCode* result) { ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); version_->SetStatus(ServiceWorkerVersion::ACTIVE); version_->DispatchSyncEvent( CreateReceiver(BrowserThread::UI, done, result)); } protected: scoped_refptr registration_; scoped_refptr version_; }; IN_PROC_BROWSER_TEST_F(EmbeddedWorkerBrowserTest, StartAndStop) { // Start a worker and wait until OnStarted() is called. base::RunLoop start_run_loop; done_closure_ = start_run_loop.QuitClosure(); BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&self::StartOnIOThread, this)); start_run_loop.Run(); ASSERT_EQ(EmbeddedWorkerInstance::RUNNING, last_worker_status_); // Stop a worker and wait until OnStopped() is called. base::RunLoop stop_run_loop; done_closure_ = stop_run_loop.QuitClosure(); BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&self::StopOnIOThread, this)); stop_run_loop.Run(); ASSERT_EQ(EmbeddedWorkerInstance::STOPPED, last_worker_status_); } IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, StartAndStop) { RunOnIOThread(base::Bind(&self::SetUpRegistrationOnIOThread, 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, 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, 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, this, "/service_worker/nonexistent.js")); // Start a worker for nonexistent URL. ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; base::RunLoop start_run_loop; BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&self::StartOnIOThread, this, start_run_loop.QuitClosure(), &status)); start_run_loop.Run(); ASSERT_EQ(SERVICE_WORKER_ERROR_START_WORKER_FAILED, status); } 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, Activate_NoEventListener) { ActivateTestHelper("/service_worker/worker.js", SERVICE_WORKER_OK); ASSERT_EQ(ServiceWorkerVersion::ACTIVE, version_->status()); } IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, Activate_Rejected) { ActivateTestHelper("/service_worker/worker_activate_rejected.js", SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED); } IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, InstallWithWaitUntil_Rejected) { InstallTestHelper("/service_worker/worker_install_rejected.js", SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED); } IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, FetchEvent_Response) { ServiceWorkerFetchEventResult result; ServiceWorkerResponse response; FetchTestHelper("/service_worker/fetch_event.js", &result, &response); ASSERT_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, result); EXPECT_EQ(200, response.status_code); EXPECT_EQ("OK", response.status_text); EXPECT_EQ("GET", response.method); std::map expected_headers; EXPECT_EQ(expected_headers, response.headers); } IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, FetchEvent_FallbackToNative) { ServiceWorkerFetchEventResult result; ServiceWorkerResponse response; FetchTestHelper( "/service_worker/fetch_event_fallback.js", &result, &response); ASSERT_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK, result); } IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, FetchEvent_Rejected) { ServiceWorkerFetchEventResult result; ServiceWorkerResponse response; FetchTestHelper("/service_worker/fetch_event_error.js", &result, &response); ASSERT_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK, result); } IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, SyncAbortedWithoutFlag) { RunOnIOThread(base::Bind( &self::SetUpRegistrationOnIOThread, this, "/service_worker/sync.js")); // Run the sync event. ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; base::RunLoop sync_run_loop; BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&self::SyncEventOnIOThread, this, sync_run_loop.QuitClosure(), &status)); sync_run_loop.Run(); ASSERT_EQ(SERVICE_WORKER_ERROR_ABORT, status); } IN_PROC_BROWSER_TEST_F(ServiceWorkerVersionBrowserTest, SyncEventHandled) { CommandLine* command_line = CommandLine::ForCurrentProcess(); command_line->AppendSwitch(switches::kEnableServiceWorkerSync); RunOnIOThread(base::Bind( &self::SetUpRegistrationOnIOThread, this, "/service_worker/sync.js")); ServiceWorkerFetchEventResult result; ServiceWorkerResponse response; // Should 404 before sync event. FetchOnRegisteredWorker(&result, &response); EXPECT_EQ(404, response.status_code); // Run the sync event. ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; base::RunLoop sync_run_loop; BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&self::SyncEventOnIOThread, this, sync_run_loop.QuitClosure(), &status)); sync_run_loop.Run(); ASSERT_EQ(SERVICE_WORKER_OK, status); // Should 200 after sync event. FetchOnRegisteredWorker(&result, &response); EXPECT_EQ(200, response.status_code); } class ServiceWorkerBlackBoxBrowserTest : public ServiceWorkerBrowserTest { public: typedef ServiceWorkerBlackBoxBrowserTest self; static void ExpectResultAndRun(bool expected, const base::Closure& continuation, bool actual) { EXPECT_EQ(expected, actual); continuation.Run(); } int RenderProcessID() { return shell()->web_contents()->GetRenderProcessHost()->GetID(); } void FindRegistrationOnIO(const GURL& document_url, ServiceWorkerStatusCode* status, GURL* script_url, const base::Closure& continuation) { wrapper()->context()->storage()->FindRegistrationForDocument( document_url, base::Bind(&ServiceWorkerBlackBoxBrowserTest::FindRegistrationOnIO2, this, status, script_url, continuation)); } void FindRegistrationOnIO2( ServiceWorkerStatusCode* out_status, GURL* script_url, const base::Closure& continuation, ServiceWorkerStatusCode status, const scoped_refptr& registration) { *out_status = status; if (registration) { *script_url = registration->script_url(); } else { EXPECT_NE(SERVICE_WORKER_OK, status); } continuation.Run(); } }; IN_PROC_BROWSER_TEST_F(ServiceWorkerBlackBoxBrowserTest, Registration) { const std::string kWorkerUrl = "/service_worker/fetch_event.js"; // Unregistering nothing should return true. { base::RunLoop run_loop; public_context()->UnregisterServiceWorker( embedded_test_server()->GetURL("/*"), base::Bind(&ServiceWorkerBlackBoxBrowserTest::ExpectResultAndRun, true, run_loop.QuitClosure())); run_loop.Run(); } // Register returns when the promise would be resolved. { base::RunLoop run_loop; public_context()->RegisterServiceWorker( embedded_test_server()->GetURL("/*"), embedded_test_server()->GetURL(kWorkerUrl), base::Bind(&ServiceWorkerBlackBoxBrowserTest::ExpectResultAndRun, true, run_loop.QuitClosure())); run_loop.Run(); } // Registering again should succeed, although the algo still // might not be complete. { base::RunLoop run_loop; public_context()->RegisterServiceWorker( embedded_test_server()->GetURL("/*"), embedded_test_server()->GetURL(kWorkerUrl), base::Bind(&ServiceWorkerBlackBoxBrowserTest::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("/*"), base::Bind(&ServiceWorkerBlackBoxBrowserTest::ExpectResultAndRun, true, run_loop.QuitClosure())); run_loop.Run(); } // Should not be able to find it. { ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; GURL script_url; RunOnIOThread( base::Bind(&ServiceWorkerBlackBoxBrowserTest::FindRegistrationOnIO, this, embedded_test_server()->GetURL("/service_worker/empty.html"), &status, &script_url)); EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND, status); } } } // namespace content