Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / modules / serviceworkers / ServiceWorkerContainerTest.cpp
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.
4
5 #include "config.h"
6 #include "modules/serviceworkers/ServiceWorkerContainer.h"
7
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>
27 #include <v8.h>
28
29 namespace blink {
30 namespace {
31
32 // Promise-related test support.
33
34 struct StubScriptFunction {
35 public:
36     StubScriptFunction()
37         : m_callCount(0)
38     {
39     }
40
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)
44     {
45         return ScriptFunctionImpl::createFunction(scriptState, *this);
46     }
47
48     size_t callCount() { return m_callCount; }
49     ScriptValue arg() { return m_arg; }
50
51 private:
52     size_t m_callCount;
53     ScriptValue m_arg;
54
55     class ScriptFunctionImpl : public ScriptFunction {
56     public:
57         static v8::Handle<v8::Function> createFunction(ScriptState* scriptState, StubScriptFunction& owner)
58         {
59             ScriptFunctionImpl* self = new ScriptFunctionImpl(scriptState, owner);
60             return self->bindToV8Function();
61         }
62
63     private:
64         ScriptFunctionImpl(ScriptState* scriptState, StubScriptFunction& owner)
65             : ScriptFunction(scriptState)
66             , m_owner(owner)
67         {
68         }
69
70         virtual ScriptValue call(ScriptValue arg) override
71         {
72             m_owner.m_arg = arg;
73             m_owner.m_callCount++;
74             return ScriptValue();
75         }
76
77         StubScriptFunction& m_owner;
78     };
79 };
80
81 class ScriptValueTest {
82 public:
83     virtual ~ScriptValueTest() { }
84     virtual void operator()(ScriptValue) const = 0;
85 };
86
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)
90 {
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());
98 }
99
100 // DOM-related test support.
101
102 // Matches a ScriptValue and a DOMException with a specific name and message.
103 class ExpectDOMException : public ScriptValueTest {
104 public:
105     ExpectDOMException(const String& expectedName, const String& expectedMessage)
106         : m_expectedName(expectedName)
107         , m_expectedMessage(expectedMessage)
108     {
109     }
110
111     virtual ~ExpectDOMException() override { }
112
113     virtual void operator()(ScriptValue value) const override
114     {
115         DOMException* exception = V8DOMException::toImplWithTypeCheck(value.isolate(), value.v8Value());
116         EXPECT_TRUE(exception) << "the value should be a DOMException";
117         if (!exception)
118             return;
119         EXPECT_EQ(m_expectedName, exception->name());
120         EXPECT_EQ(m_expectedMessage, exception->message());
121     }
122
123 private:
124     String m_expectedName;
125     String m_expectedMessage;
126 };
127
128 // Service Worker-specific tests.
129
130 class NotReachedWebServiceWorkerProvider : public WebServiceWorkerProvider {
131 public:
132     virtual ~NotReachedWebServiceWorkerProvider() override { }
133
134     virtual void registerServiceWorker(const WebURL& pattern, const WebURL& scriptURL, WebServiceWorkerRegistrationCallbacks* callbacks) override
135     {
136         ADD_FAILURE() << "the provider should not be called to register a Service Worker";
137         delete callbacks;
138     }
139 };
140
141 class ServiceWorkerContainerTest : public ::testing::Test {
142 protected:
143     ServiceWorkerContainerTest()
144         : m_page(DummyPageHolder::create())
145     {
146     }
147
148     ~ServiceWorkerContainerTest()
149     {
150         m_page.clear();
151         V8GCController::collectGarbage(isolate());
152     }
153
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()); }
157
158     void provide(PassOwnPtr<WebServiceWorkerProvider> provider)
159     {
160         m_page->document().DocumentSupplementable::provideSupplement(ServiceWorkerContainerClient::supplementName(), ServiceWorkerContainerClient::create(provider));
161     }
162
163     void setPageURL(const String& url)
164     {
165         // For URL completion.
166         m_page->document().setBaseURLOverride(KURL(KURL(), url));
167
168         // The basis for security checks.
169         m_page->document().setSecurityOrigin(SecurityOrigin::createFromString(url));
170     }
171
172     void testRegisterRejected(const String& scriptURL, const String& scope, const ScriptValueTest& valueTest)
173     {
174         // When the registration is rejected, a register call must not reach
175         // the provider.
176         provide(adoptPtr(new NotReachedWebServiceWorkerProvider()));
177
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);
184
185         container->willBeDetachedFromFrame();
186     }
187
188     void testGetRegistrationRejected(const String& documentURL, const ScriptValueTest& valueTest)
189     {
190         provide(adoptPtr(new NotReachedWebServiceWorkerProvider()));
191
192         ServiceWorkerContainer* container = ServiceWorkerContainer::create(executionContext());
193         ScriptState::Scope scriptScope(scriptState());
194         ScriptPromise promise = container->getRegistration(scriptState(), documentURL);
195         expectRejected(scriptState(), promise, valueTest);
196
197         container->willBeDetachedFromFrame();
198     }
199
200 private:
201     OwnPtr<DummyPageHolder> m_page;
202 };
203
204 TEST_F(ServiceWorkerContainerTest, Register_NonSecureOriginIsRejected)
205 {
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"));
211 }
212
213 TEST_F(ServiceWorkerContainerTest, Register_CrossOriginScriptIsRejected)
214 {
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."));
220 }
221
222 TEST_F(ServiceWorkerContainerTest, Register_CrossOriginScopeIsRejected)
223 {
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."));
229 }
230
231 TEST_F(ServiceWorkerContainerTest, Register_DifferentDirectoryThanScript)
232 {
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."));
238 }
239
240 TEST_F(ServiceWorkerContainerTest, GetRegistration_NonSecureOriginIsRejected)
241 {
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"));
246 }
247
248 TEST_F(ServiceWorkerContainerTest, GetRegistration_CrossOriginURLIsRejected)
249 {
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."));
254 }
255
256 class StubWebServiceWorkerProvider {
257 public:
258     StubWebServiceWorkerProvider()
259         : m_registerCallCount(0)
260         , m_getRegistrationCallCount(0)
261     {
262     }
263
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()
269     {
270         return adoptPtr(new WebServiceWorkerProviderImpl(*this));
271     }
272
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; }
278
279 private:
280     class WebServiceWorkerProviderImpl : public WebServiceWorkerProvider {
281     public:
282         WebServiceWorkerProviderImpl(StubWebServiceWorkerProvider& owner)
283             : m_owner(owner)
284         {
285         }
286
287         virtual ~WebServiceWorkerProviderImpl() override { }
288
289         virtual void registerServiceWorker(const WebURL& pattern, const WebURL& scriptURL, WebServiceWorkerRegistrationCallbacks* callbacks) override
290         {
291             m_owner.m_registerCallCount++;
292             m_owner.m_registerScope = pattern;
293             m_owner.m_registerScriptURL = scriptURL;
294             m_registrationCallbacksToDelete.append(adoptPtr(callbacks));
295         }
296
297         virtual void getRegistration(const WebURL& documentURL, WebServiceWorkerGetRegistrationCallbacks* callbacks) override
298         {
299             m_owner.m_getRegistrationCallCount++;
300             m_owner.m_getRegistrationURL = documentURL;
301             m_getRegistrationCallbacksToDelete.append(adoptPtr(callbacks));
302         }
303
304     private:
305         StubWebServiceWorkerProvider& m_owner;
306         Vector<OwnPtr<WebServiceWorkerRegistrationCallbacks> > m_registrationCallbacksToDelete;
307         Vector<OwnPtr<WebServiceWorkerGetRegistrationCallbacks> > m_getRegistrationCallbacksToDelete;
308     };
309
310 private:
311     size_t m_registerCallCount;
312     WebURL m_registerScope;
313     WebURL m_registerScriptURL;
314     size_t m_getRegistrationCallCount;
315     WebURL m_getRegistrationURL;
316 };
317
318 TEST_F(ServiceWorkerContainerTest, RegisterUnregister_NonHttpsSecureOriginDelegatesToProvider)
319 {
320     setPageURL("http://localhost/x/index.html");
321
322     StubWebServiceWorkerProvider stubProvider;
323     provide(stubProvider.provider());
324
325     ServiceWorkerContainer* container = ServiceWorkerContainer::create(executionContext());
326
327     // register
328     {
329         ScriptState::Scope scriptScope(scriptState());
330         RegistrationOptions options;
331         options.setScope("y/");
332         container->registerServiceWorker(scriptState(), "/x/y/worker.js", options);
333
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());
337     }
338
339     container->willBeDetachedFromFrame();
340 }
341
342 TEST_F(ServiceWorkerContainerTest, GetRegistration_OmittedDocumentURLDefaultsToPageURL)
343 {
344     setPageURL("http://localhost/x/index.html");
345
346     StubWebServiceWorkerProvider stubProvider;
347     provide(stubProvider.provider());
348
349     ServiceWorkerContainer* container = ServiceWorkerContainer::create(executionContext());
350
351     {
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());
356     }
357
358     container->willBeDetachedFromFrame();
359 }
360
361 } // namespace
362 } // namespace blink