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 RegistrationOptions options;
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, Register_DifferentDirectoryThanScript)
233 setPageURL("https://www.example.com/");
234 testRegisterRejected(
235 "https://www.example.com/js/worker.js",
236 "https://www.example.com/",
237 ExpectDOMException("SecurityError", "The scope must be under the directory of the script URL."));
240 TEST_F(ServiceWorkerContainerTest, GetRegistration_NonSecureOriginIsRejected)
242 setPageURL("http://www.example.com/");
243 testGetRegistrationRejected(
244 "http://www.example.com/",
245 ExpectDOMException("NotSupportedError", "Only secure origins are allowed. http://goo.gl/lq4gCo"));
248 TEST_F(ServiceWorkerContainerTest, GetRegistration_CrossOriginURLIsRejected)
250 setPageURL("https://www.example.com/");
251 testGetRegistrationRejected(
252 "https://foo.example.com/", // Differs by host
253 ExpectDOMException("SecurityError", "The documentURL must match the current origin."));
256 class StubWebServiceWorkerProvider {
258 StubWebServiceWorkerProvider()
259 : m_registerCallCount(0)
260 , m_getRegistrationCallCount(0)
264 // Creates a WebServiceWorkerProvider. This can outlive the
265 // StubWebServiceWorkerProvider, but |registerServiceWorker| and
266 // other methods must not be called after the
267 // StubWebServiceWorkerProvider dies.
268 PassOwnPtr<WebServiceWorkerProvider> provider()
270 return adoptPtr(new WebServiceWorkerProviderImpl(*this));
273 size_t registerCallCount() { return m_registerCallCount; }
274 const WebURL& registerScope() { return m_registerScope; }
275 const WebURL& registerScriptURL() { return m_registerScriptURL; }
276 size_t getRegistrationCallCount() { return m_getRegistrationCallCount; }
277 const WebURL& getRegistrationURL() { return m_getRegistrationURL; }
280 class WebServiceWorkerProviderImpl : public WebServiceWorkerProvider {
282 WebServiceWorkerProviderImpl(StubWebServiceWorkerProvider& owner)
287 virtual ~WebServiceWorkerProviderImpl() override { }
289 virtual void registerServiceWorker(const WebURL& pattern, const WebURL& scriptURL, WebServiceWorkerRegistrationCallbacks* callbacks) override
291 m_owner.m_registerCallCount++;
292 m_owner.m_registerScope = pattern;
293 m_owner.m_registerScriptURL = scriptURL;
294 m_registrationCallbacksToDelete.append(adoptPtr(callbacks));
297 virtual void getRegistration(const WebURL& documentURL, WebServiceWorkerGetRegistrationCallbacks* callbacks) override
299 m_owner.m_getRegistrationCallCount++;
300 m_owner.m_getRegistrationURL = documentURL;
301 m_getRegistrationCallbacksToDelete.append(adoptPtr(callbacks));
305 StubWebServiceWorkerProvider& m_owner;
306 Vector<OwnPtr<WebServiceWorkerRegistrationCallbacks> > m_registrationCallbacksToDelete;
307 Vector<OwnPtr<WebServiceWorkerGetRegistrationCallbacks> > m_getRegistrationCallbacksToDelete;
311 size_t m_registerCallCount;
312 WebURL m_registerScope;
313 WebURL m_registerScriptURL;
314 size_t m_getRegistrationCallCount;
315 WebURL m_getRegistrationURL;
318 TEST_F(ServiceWorkerContainerTest, RegisterUnregister_NonHttpsSecureOriginDelegatesToProvider)
320 setPageURL("http://localhost/x/index.html");
322 StubWebServiceWorkerProvider stubProvider;
323 provide(stubProvider.provider());
325 ServiceWorkerContainer* container = ServiceWorkerContainer::create(executionContext());
329 ScriptState::Scope scriptScope(scriptState());
330 RegistrationOptions options;
331 options.setScope("y/");
332 container->registerServiceWorker(scriptState(), "/x/y/worker.js", options);
334 EXPECT_EQ(1ul, stubProvider.registerCallCount());
335 EXPECT_EQ(WebURL(KURL(KURL(), "http://localhost/x/y/")), stubProvider.registerScope());
336 EXPECT_EQ(WebURL(KURL(KURL(), "http://localhost/x/y/worker.js")), stubProvider.registerScriptURL());
339 container->willBeDetachedFromFrame();
342 TEST_F(ServiceWorkerContainerTest, GetRegistration_OmittedDocumentURLDefaultsToPageURL)
344 setPageURL("http://localhost/x/index.html");
346 StubWebServiceWorkerProvider stubProvider;
347 provide(stubProvider.provider());
349 ServiceWorkerContainer* container = ServiceWorkerContainer::create(executionContext());
352 ScriptState::Scope scriptScope(scriptState());
353 container->getRegistration(scriptState(), "");
354 EXPECT_EQ(1ul, stubProvider.getRegistrationCallCount());
355 EXPECT_EQ(WebURL(KURL(KURL(), "http://localhost/x/index.html")), stubProvider.getRegistrationURL());
358 container->willBeDetachedFromFrame();