Chromium Code Reviews| 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 |