OLD | NEW |
---|---|
(Empty) | |
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 using namespace WebCore; | |
horo
2014/07/22 13:00:48
s/WebCore/blink/
https://groups.google.com/a/chro
| |
30 | |
31 namespace { | |
32 | |
33 // Promise-related test support. | |
34 | |
35 struct StubScriptFunction { | |
36 public: | |
37 StubScriptFunction() | |
38 : m_callCount(0) | |
39 { | |
40 } | |
41 | |
42 // The returned ScriptFunction can outlive the StubScriptFunction, | |
43 // but it should not be called after the StubScriptFunction dies. | |
44 PassOwnPtr<ScriptFunction> function(v8::Isolate* isolate) | |
45 { | |
46 return adoptPtr(new ScriptFunctionImpl(isolate, *this)); | |
47 } | |
48 | |
49 size_t callCount() { return m_callCount; } | |
50 ScriptValue arg() { return m_arg; } | |
51 | |
52 private: | |
53 size_t m_callCount; | |
54 ScriptValue m_arg; | |
55 | |
56 class ScriptFunctionImpl : public ScriptFunction { | |
57 public: | |
58 ScriptFunctionImpl(v8::Isolate* isolate, StubScriptFunction& owner) | |
59 : ScriptFunction(isolate) | |
60 , m_owner(owner) | |
61 { | |
62 } | |
63 | |
64 virtual ScriptValue call(ScriptValue arg) OVERRIDE | |
65 { | |
66 m_owner.m_arg = arg; | |
67 m_owner.m_callCount++; | |
68 return ScriptValue(); | |
69 } | |
70 | |
71 private: | |
72 StubScriptFunction& m_owner; | |
73 }; | |
74 }; | |
75 | |
76 class ScriptValueTest { | |
77 public: | |
78 virtual ~ScriptValueTest() { } | |
79 virtual void operator()(ScriptValue) const = 0; | |
80 }; | |
81 | |
82 // Runs microtasks and expects |promise| to be rejected. Calls | |
83 // |valueTest| with the value passed to |reject|, if any. | |
84 void expectRejected(ScriptPromise& promise, const ScriptValueTest& valueTest) | |
85 { | |
86 StubScriptFunction resolved, rejected; | |
87 promise.then(resolved.function(promise.isolate()), rejected.function(promise .isolate())); | |
88 promise.isolate()->RunMicrotasks(); | |
89 EXPECT_EQ(0ul, resolved.callCount()); | |
90 EXPECT_EQ(1ul, rejected.callCount()); | |
91 if (rejected.callCount()) | |
92 valueTest(rejected.arg()); | |
93 } | |
94 | |
95 // DOM-related test support. | |
96 | |
97 // Matches a ScriptValue and a DOMException with a specific name and message. | |
98 class ExpectDOMException : public ScriptValueTest { | |
99 public: | |
100 ExpectDOMException(const String& expectedName, const String& expectedMessage ) | |
101 : m_expectedName(expectedName) | |
102 , m_expectedMessage(expectedMessage) | |
103 { | |
104 } | |
105 | |
106 virtual ~ExpectDOMException() OVERRIDE { } | |
107 | |
108 virtual void operator()(ScriptValue value) const OVERRIDE | |
109 { | |
110 DOMException* exception = V8DOMException::toNativeWithTypeCheck(value.is olate(), value.v8Value()); | |
111 EXPECT_TRUE(exception) << "the value should be a DOMException"; | |
112 if (!exception) | |
113 return; | |
114 EXPECT_EQ(m_expectedName, exception->name()); | |
115 EXPECT_EQ(m_expectedMessage, exception->message()); | |
116 } | |
117 | |
118 private: | |
119 String m_expectedName; | |
120 String m_expectedMessage; | |
121 }; | |
122 | |
123 // Service Worker-specific tests. | |
124 | |
125 class NotReachedWebServiceWorkerProvider : public WebServiceWorkerProvider { | |
126 public: | |
127 virtual ~NotReachedWebServiceWorkerProvider() OVERRIDE { } | |
128 | |
129 virtual void registerServiceWorker(const WebURL& pattern, const WebURL& scri ptURL, WebServiceWorkerCallbacks* callbacks) OVERRIDE | |
130 { | |
131 ADD_FAILURE() << "the provider should not be called to register a Servic e Worker"; | |
132 delete callbacks; | |
133 } | |
134 | |
135 virtual void unregisterServiceWorker(const WebURL& pattern, WebServiceWorker Callbacks* callbacks) OVERRIDE | |
136 { | |
137 ADD_FAILURE() << "the provider should not be called to unregister a Serv ice Worker"; | |
138 delete callbacks; | |
139 } | |
140 }; | |
141 | |
142 class ServiceWorkerContainerTest : public ::testing::Test { | |
143 protected: | |
144 ServiceWorkerContainerTest() | |
145 : m_page(DummyPageHolder::create()) | |
146 { | |
147 } | |
148 | |
149 ~ServiceWorkerContainerTest() | |
150 { | |
151 m_page.clear(); | |
152 V8GCController::collectGarbage(isolate()); | |
153 } | |
154 | |
155 ExecutionContext* executionContext() { return &(m_page->document()); } | |
156 v8::Isolate* isolate() { return v8::Isolate::GetCurrent(); } | |
157 ScriptState* scriptState() { return ScriptState::forMainWorld(m_page->docume nt().frame()); } | |
158 | |
159 void provide(PassOwnPtr<WebServiceWorkerProvider> provider) | |
160 { | |
161 m_page->document().DocumentSupplementable::provideSupplement(ServiceWork erContainerClient::supplementName(), ServiceWorkerContainerClient::create(provid er)); | |
162 } | |
163 | |
164 void setPageURL(const String& url) | |
165 { | |
166 // For URL completion. | |
167 m_page->document().setBaseURLOverride(KURL(KURL(), url)); | |
168 | |
169 // The basis for security checks. | |
170 m_page->document().setSecurityOrigin(SecurityOrigin::createFromString(ur l)); | |
171 } | |
172 | |
173 void testRegisterRejected(const String& scriptURL, const String& scope, cons t ScriptValueTest& valueTest) | |
174 { | |
175 // When the registration is rejected, a register call must not reach | |
176 // the provider. | |
177 provide(adoptPtr(new NotReachedWebServiceWorkerProvider())); | |
178 | |
179 RefPtrWillBeRawPtr<ServiceWorkerContainer> container = ServiceWorkerCont ainer::create(executionContext()); | |
180 ScriptState::Scope scriptScope(scriptState()); | |
181 Dictionary options = Dictionary::createEmpty(isolate()); | |
182 EXPECT_TRUE(options.set("scope", scope)); | |
183 ScriptPromise promise = container->registerServiceWorker(scriptState(), scriptURL, options); | |
184 expectRejected(promise, valueTest); | |
185 | |
186 container->willBeDetachedFromFrame(); | |
187 } | |
188 | |
189 void testUnregisterRejected(const String& scope, const ScriptValueTest& valu eTest) | |
190 { | |
191 provide(adoptPtr(new NotReachedWebServiceWorkerProvider())); | |
192 | |
193 RefPtrWillBeRawPtr<ServiceWorkerContainer> container = ServiceWorkerCont ainer::create(executionContext()); | |
194 ScriptState::Scope scriptScope(scriptState()); | |
195 Dictionary options = Dictionary::createEmpty(isolate()); | |
196 EXPECT_TRUE(options.set("scope", scope)); | |
197 ScriptPromise promise = container->unregisterServiceWorker(scriptState() , scope); | |
198 expectRejected(promise, valueTest); | |
199 | |
200 container->willBeDetachedFromFrame(); | |
201 } | |
202 | |
203 private: | |
204 OwnPtr<DummyPageHolder> m_page; | |
205 }; | |
206 | |
207 TEST_F(ServiceWorkerContainerTest, Register_NonSecureOriginIsRejected) | |
208 { | |
209 setPageURL("http://www.example.com/"); | |
210 testRegisterRejected( | |
211 "http://www.example.com/worker.js", | |
212 "http://www.example.com/*", | |
213 ExpectDOMException("SecurityError", "Service Workers are only supported over secure origins.")); | |
214 } | |
215 | |
216 TEST_F(ServiceWorkerContainerTest, Register_CrossOriginScriptIsRejected) | |
217 { | |
218 setPageURL("https://www.example.com"); | |
219 testRegisterRejected( | |
220 "https://www.example.com:8080/", // Differs by port | |
221 "https://www.example.com/*", | |
222 ExpectDOMException("SecurityError", "The origin of the script must match the current origin.")); | |
223 } | |
224 | |
225 TEST_F(ServiceWorkerContainerTest, Register_CrossOriginScopeIsRejected) | |
226 { | |
227 setPageURL("https://www.example.com"); | |
228 testRegisterRejected( | |
229 "https://www.example.com", | |
230 "wss://www.example.com/*", // Differs by protocol | |
231 ExpectDOMException("SecurityError", "The scope must match the current or igin.")); | |
232 } | |
233 | |
234 TEST_F(ServiceWorkerContainerTest, Unregister_NonSecureOriginIsRejected) | |
235 { | |
236 setPageURL("http://www.example.com"); | |
237 testUnregisterRejected( | |
238 "*", | |
239 ExpectDOMException("SecurityError", "Service Workers are only supported over secure origins.")); | |
240 } | |
241 | |
242 TEST_F(ServiceWorkerContainerTest, Unregister_CrossOriginScopeIsRejected) | |
243 { | |
244 setPageURL("https://www.example.com"); | |
245 testUnregisterRejected( | |
246 "https://example.com/*", // Differs by host | |
247 ExpectDOMException("SecurityError", "The scope must match the current or igin.")); | |
248 } | |
249 | |
250 class StubWebServiceWorkerProvider { | |
251 public: | |
252 StubWebServiceWorkerProvider() | |
253 : m_registerCallCount(0) | |
254 , m_unregisterCallCount(0) | |
255 { | |
256 } | |
257 | |
258 // Creates a WebServiceWorkerProvider. This can outlive the | |
259 // StubWebServiceWorkerProvider, but |registerServiceWorker| and | |
260 // other methods must not be called after the | |
261 // StubWebServiceWorkerProvider dies. | |
262 PassOwnPtr<WebServiceWorkerProvider> provider() | |
263 { | |
264 return adoptPtr(new WebServiceWorkerProviderImpl(*this)); | |
265 } | |
266 | |
267 size_t registerCallCount() { return m_registerCallCount; } | |
268 const WebURL& registerScope() { return m_registerScope; } | |
269 const WebURL& registerScriptURL() { return m_registerScriptURL; } | |
270 size_t unregisterCallCount() { return m_unregisterCallCount; } | |
271 const WebURL& unregisterScope() { return m_unregisterScope; } | |
272 | |
273 private: | |
274 class WebServiceWorkerProviderImpl : public WebServiceWorkerProvider { | |
275 public: | |
276 WebServiceWorkerProviderImpl(StubWebServiceWorkerProvider& owner) | |
277 : m_owner(owner) | |
278 { | |
279 } | |
280 | |
281 virtual ~WebServiceWorkerProviderImpl() OVERRIDE { } | |
282 | |
283 virtual void registerServiceWorker(const WebURL& pattern, const WebURL& scriptURL, WebServiceWorkerCallbacks* callbacks) OVERRIDE | |
284 { | |
285 m_owner.m_registerCallCount++; | |
286 m_owner.m_registerScope = pattern; | |
287 m_owner.m_registerScriptURL = scriptURL; | |
288 m_callbacksToDelete.append(adoptPtr(callbacks)); | |
289 } | |
290 | |
291 virtual void unregisterServiceWorker(const WebURL& pattern, WebServiceWo rkerCallbacks* callbacks) OVERRIDE | |
292 { | |
293 m_owner.m_unregisterCallCount++; | |
294 m_owner.m_unregisterScope = pattern; | |
295 m_callbacksToDelete.append(adoptPtr(callbacks)); | |
296 } | |
297 | |
298 private: | |
299 StubWebServiceWorkerProvider& m_owner; | |
300 Vector<OwnPtr<WebServiceWorkerCallbacks> > m_callbacksToDelete; | |
301 }; | |
302 | |
303 private: | |
304 size_t m_registerCallCount; | |
305 WebURL m_registerScope; | |
306 WebURL m_registerScriptURL; | |
307 size_t m_unregisterCallCount; | |
308 WebURL m_unregisterScope; | |
309 }; | |
310 | |
311 TEST_F(ServiceWorkerContainerTest, RegisterUnregister_NonHttpsSecureOriginDelega tesToProvider) | |
312 { | |
313 setPageURL("http://localhost/x/index.html"); | |
314 | |
315 StubWebServiceWorkerProvider stubProvider; | |
316 provide(stubProvider.provider()); | |
317 | |
318 RefPtrWillBeRawPtr<ServiceWorkerContainer> container = ServiceWorkerContaine r::create(executionContext()); | |
319 | |
320 // register | |
321 { | |
322 ScriptState::Scope scriptScope(scriptState()); | |
323 Dictionary options = Dictionary::createEmpty(isolate()); | |
324 EXPECT_TRUE(options.set("scope", "y/*")); | |
325 container->registerServiceWorker(scriptState(), "/z/worker.js", options) ; | |
326 | |
327 EXPECT_EQ(1ul, stubProvider.registerCallCount()); | |
328 EXPECT_EQ(WebURL(KURL(KURL(), "http://localhost/x/y/*")), stubProvider.r egisterScope()); | |
329 EXPECT_EQ(WebURL(KURL(KURL(), "http://localhost/z/worker.js")), stubProv ider.registerScriptURL()); | |
330 } | |
331 | |
332 // unregister | |
333 { | |
334 ScriptState::Scope scriptScope(scriptState()); | |
335 container->unregisterServiceWorker(scriptState(), "y/*"); | |
336 | |
337 EXPECT_EQ(1ul, stubProvider.unregisterCallCount()); | |
338 EXPECT_EQ(WebURL(KURL(KURL(), "http://localhost/x/y/*")), stubProvider.u nregisterScope()); | |
339 } | |
340 | |
341 container->willBeDetachedFromFrame(); | |
342 } | |
343 | |
344 } // namespace | |
OLD | NEW |