1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 #include "modules/serviceworkers/ServiceWorkerContainer.h"
8 #include "bindings/core/v8/Dictionary.h"
9 #include "bindings/core/v8/ScriptFunction.h"
10 #include "bindings/core/v8/ScriptPromise.h"
11 #include "bindings/core/v8/ScriptState.h"
12 #include "bindings/core/v8/V8DOMException.h"
13 #include "bindings/core/v8/V8GCController.h"
14 #include "core/dom/DOMException.h"
15 #include "core/dom/Document.h"
16 #include "core/dom/ExecutionContext.h"
17 #include "core/testing/DummyPageHolder.h"
18 #include "modules/serviceworkers/ServiceWorkerContainerClient.h"
19 #include "platform/weborigin/KURL.h"
20 #include "platform/weborigin/SecurityOrigin.h"
21 #include "public/platform/WebServiceWorkerProvider.h"
22 #include "public/platform/WebURL.h"
23 #include "wtf/OwnPtr.h"
24 #include "wtf/PassOwnPtr.h"
25 #include "wtf/text/WTFString.h"
26 #include <gtest/gtest.h>
32 // Promise-related test support.
34 struct StubScriptFunction {
41 // The returned ScriptFunction can outlive the StubScriptFunction,
42 // but it should not be called after the StubScriptFunction dies.
43 v8::Handle<v8::Function> function(ScriptState* scriptState)
45 return ScriptFunctionImpl::createFunction(scriptState, *this);
48 size_t callCount() { return m_callCount; }
49 ScriptValue arg() { return m_arg; }
55 class ScriptFunctionImpl : public ScriptFunction {
57 static v8::Handle<v8::Function> createFunction(ScriptState* scriptState, StubScriptFunction& owner)
59 ScriptFunctionImpl* self = new ScriptFunctionImpl(scriptState, owner);
60 return self->bindToV8Function();
64 ScriptFunctionImpl(ScriptState* scriptState, StubScriptFunction& owner)
65 : ScriptFunction(scriptState)
70 virtual ScriptValue call(ScriptValue arg) OVERRIDE
73 m_owner.m_callCount++;
77 StubScriptFunction& m_owner;
81 class ScriptValueTest {
83 virtual ~ScriptValueTest() { }
84 virtual void operator()(ScriptValue) const = 0;
87 // Runs microtasks and expects |promise| to be rejected. Calls
88 // |valueTest| with the value passed to |reject|, if any.
89 void expectRejected(ScriptState* scriptState, ScriptPromise& promise, const ScriptValueTest& valueTest)
91 StubScriptFunction resolved, rejected;
92 promise.then(resolved.function(scriptState), rejected.function(scriptState));
93 promise.isolate()->RunMicrotasks();
94 EXPECT_EQ(0ul, resolved.callCount());
95 EXPECT_EQ(1ul, rejected.callCount());
96 if (rejected.callCount())
97 valueTest(rejected.arg());
100 // DOM-related test support.
102 // Matches a ScriptValue and a DOMException with a specific name and message.
103 class ExpectDOMException : public ScriptValueTest {
105 ExpectDOMException(const String& expectedName, const String& expectedMessage)
106 : m_expectedName(expectedName)
107 , m_expectedMessage(expectedMessage)
111 virtual ~ExpectDOMException() OVERRIDE { }
113 virtual void operator()(ScriptValue value) const OVERRIDE
115 DOMException* exception = V8DOMException::toImplWithTypeCheck(value.isolate(), value.v8Value());
116 EXPECT_TRUE(exception) << "the value should be a DOMException";
119 EXPECT_EQ(m_expectedName, exception->name());
120 EXPECT_EQ(m_expectedMessage, exception->message());
124 String m_expectedName;
125 String m_expectedMessage;
128 // Service Worker-specific tests.
130 class NotReachedWebServiceWorkerProvider : public WebServiceWorkerProvider {
132 virtual ~NotReachedWebServiceWorkerProvider() OVERRIDE { }
134 virtual void registerServiceWorker(const WebURL& pattern, const WebURL& scriptURL, WebServiceWorkerRegistrationCallbacks* callbacks) OVERRIDE
136 ADD_FAILURE() << "the provider should not be called to register a Service Worker";
141 class ServiceWorkerContainerTest : public ::testing::Test {
143 ServiceWorkerContainerTest()
144 : m_page(DummyPageHolder::create())
148 ~ServiceWorkerContainerTest()
151 V8GCController::collectGarbage(isolate());
154 ExecutionContext* executionContext() { return &(m_page->document()); }
155 v8::Isolate* isolate() { return v8::Isolate::GetCurrent(); }
156 ScriptState* scriptState() { return ScriptState::forMainWorld(m_page->document().frame()); }
158 void provide(PassOwnPtr<WebServiceWorkerProvider> provider)
160 m_page->document().DocumentSupplementable::provideSupplement(ServiceWorkerContainerClient::supplementName(), ServiceWorkerContainerClient::create(provider));
163 void setPageURL(const String& url)
165 // For URL completion.
166 m_page->document().setBaseURLOverride(KURL(KURL(), url));
168 // The basis for security checks.
169 m_page->document().setSecurityOrigin(SecurityOrigin::createFromString(url));
172 void testRegisterRejected(const String& scriptURL, const String& scope, const ScriptValueTest& valueTest)
174 // When the registration is rejected, a register call must not reach
176 provide(adoptPtr(new NotReachedWebServiceWorkerProvider()));
178 ServiceWorkerContainer* container = ServiceWorkerContainer::create(executionContext());
179 ScriptState::Scope scriptScope(scriptState());
180 RegistrationOptionList* options = RegistrationOptionList::create();
181 options->setScope(scope);
182 ScriptPromise promise = container->registerServiceWorker(scriptState(), scriptURL, *options);
183 expectRejected(scriptState(), promise, valueTest);
185 container->willBeDetachedFromFrame();
188 void testGetRegistrationRejected(const String& documentURL, const ScriptValueTest& valueTest)
190 provide(adoptPtr(new NotReachedWebServiceWorkerProvider()));
192 ServiceWorkerContainer* container = ServiceWorkerContainer::create(executionContext());
193 ScriptState::Scope scriptScope(scriptState());
194 ScriptPromise promise = container->getRegistration(scriptState(), documentURL);
195 expectRejected(scriptState(), promise, valueTest);
197 container->willBeDetachedFromFrame();
201 OwnPtr<DummyPageHolder> m_page;
204 TEST_F(ServiceWorkerContainerTest, Register_NonSecureOriginIsRejected)
206 setPageURL("http://www.example.com/");
207 testRegisterRejected(
208 "http://www.example.com/worker.js",
209 "http://www.example.com/",
210 ExpectDOMException("NotSupportedError", "Only secure origins are allowed. http://goo.gl/lq4gCo"));
213 TEST_F(ServiceWorkerContainerTest, Register_CrossOriginScriptIsRejected)
215 setPageURL("https://www.example.com");
216 testRegisterRejected(
217 "https://www.example.com:8080/", // Differs by port
218 "https://www.example.com/",
219 ExpectDOMException("SecurityError", "The origin of the script must match the current origin."));
222 TEST_F(ServiceWorkerContainerTest, Register_CrossOriginScopeIsRejected)
224 setPageURL("https://www.example.com");
225 testRegisterRejected(
226 "https://www.example.com",
227 "wss://www.example.com/", // Differs by protocol
228 ExpectDOMException("SecurityError", "The scope must match the current origin."));
231 TEST_F(ServiceWorkerContainerTest, GetRegistration_NonSecureOriginIsRejected)
233 setPageURL("http://www.example.com/");
234 testGetRegistrationRejected(
235 "http://www.example.com/",
236 ExpectDOMException("NotSupportedError", "Only secure origins are allowed. http://goo.gl/lq4gCo"));
239 TEST_F(ServiceWorkerContainerTest, GetRegistration_CrossOriginURLIsRejected)
241 setPageURL("https://www.example.com/");
242 testGetRegistrationRejected(
243 "https://foo.example.com/", // Differs by host
244 ExpectDOMException("SecurityError", "The documentURL must match the current origin."));
247 class StubWebServiceWorkerProvider {
249 StubWebServiceWorkerProvider()
250 : m_registerCallCount(0)
251 , m_getRegistrationCallCount(0)
255 // Creates a WebServiceWorkerProvider. This can outlive the
256 // StubWebServiceWorkerProvider, but |registerServiceWorker| and
257 // other methods must not be called after the
258 // StubWebServiceWorkerProvider dies.
259 PassOwnPtr<WebServiceWorkerProvider> provider()
261 return adoptPtr(new WebServiceWorkerProviderImpl(*this));
264 size_t registerCallCount() { return m_registerCallCount; }
265 const WebURL& registerScope() { return m_registerScope; }
266 const WebURL& registerScriptURL() { return m_registerScriptURL; }
267 size_t getRegistrationCallCount() { return m_getRegistrationCallCount; }
268 const WebURL& getRegistrationURL() { return m_getRegistrationURL; }
271 class WebServiceWorkerProviderImpl : public WebServiceWorkerProvider {
273 WebServiceWorkerProviderImpl(StubWebServiceWorkerProvider& owner)
278 virtual ~WebServiceWorkerProviderImpl() OVERRIDE { }
280 virtual void registerServiceWorker(const WebURL& pattern, const WebURL& scriptURL, WebServiceWorkerRegistrationCallbacks* callbacks) OVERRIDE
282 m_owner.m_registerCallCount++;
283 m_owner.m_registerScope = pattern;
284 m_owner.m_registerScriptURL = scriptURL;
285 m_registrationCallbacksToDelete.append(adoptPtr(callbacks));
288 virtual void getRegistration(const WebURL& documentURL, WebServiceWorkerGetRegistrationCallbacks* callbacks) OVERRIDE
290 m_owner.m_getRegistrationCallCount++;
291 m_owner.m_getRegistrationURL = documentURL;
292 m_getRegistrationCallbacksToDelete.append(adoptPtr(callbacks));
296 StubWebServiceWorkerProvider& m_owner;
297 Vector<OwnPtr<WebServiceWorkerRegistrationCallbacks> > m_registrationCallbacksToDelete;
298 Vector<OwnPtr<WebServiceWorkerGetRegistrationCallbacks> > m_getRegistrationCallbacksToDelete;
302 size_t m_registerCallCount;
303 WebURL m_registerScope;
304 WebURL m_registerScriptURL;
305 size_t m_getRegistrationCallCount;
306 WebURL m_getRegistrationURL;
309 TEST_F(ServiceWorkerContainerTest, RegisterUnregister_NonHttpsSecureOriginDelegatesToProvider)
311 setPageURL("http://localhost/x/index.html");
313 StubWebServiceWorkerProvider stubProvider;
314 provide(stubProvider.provider());
316 ServiceWorkerContainer* container = ServiceWorkerContainer::create(executionContext());
320 ScriptState::Scope scriptScope(scriptState());
321 RegistrationOptionList* options = RegistrationOptionList::create();
322 options->setScope("y/");
323 container->registerServiceWorker(scriptState(), "/z/worker.js", *options);
325 EXPECT_EQ(1ul, stubProvider.registerCallCount());
326 EXPECT_EQ(WebURL(KURL(KURL(), "http://localhost/x/y/")), stubProvider.registerScope());
327 EXPECT_EQ(WebURL(KURL(KURL(), "http://localhost/z/worker.js")), stubProvider.registerScriptURL());
330 container->willBeDetachedFromFrame();
333 TEST_F(ServiceWorkerContainerTest, GetRegistration_OmittedDocumentURLDefaultsToPageURL)
335 setPageURL("http://localhost/x/index.html");
337 StubWebServiceWorkerProvider stubProvider;
338 provide(stubProvider.provider());
340 ServiceWorkerContainer* container = ServiceWorkerContainer::create(executionContext());
343 ScriptState::Scope scriptScope(scriptState());
344 container->getRegistration(scriptState(), "");
345 EXPECT_EQ(1ul, stubProvider.getRegistrationCallCount());
346 EXPECT_EQ(WebURL(KURL(KURL(), "http://localhost/x/index.html")), stubProvider.getRegistrationURL());
349 container->willBeDetachedFromFrame();