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 blink; |
| 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 |