1 | /* |
2 | * Copyright (C) 2008-2017 Apple Inc. All Rights Reserved. |
3 | * Copyright (C) 2009, 2011 Google Inc. All Rights Reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions |
7 | * are met: |
8 | * 1. Redistributions of source code must retain the above copyright |
9 | * notice, this list of conditions and the following disclaimer. |
10 | * 2. Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * |
14 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
15 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
17 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
18 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
19 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
20 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
21 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
22 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
24 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
25 | * |
26 | */ |
27 | |
28 | #include "config.h" |
29 | #include "WorkerGlobalScope.h" |
30 | |
31 | #include "ContentSecurityPolicy.h" |
32 | #include "Crypto.h" |
33 | #include "IDBConnectionProxy.h" |
34 | #include "ImageBitmapOptions.h" |
35 | #include "InspectorInstrumentation.h" |
36 | #include "Microtasks.h" |
37 | #include "Performance.h" |
38 | #include "ScheduledAction.h" |
39 | #include "ScriptSourceCode.h" |
40 | #include "SecurityOrigin.h" |
41 | #include "SecurityOriginPolicy.h" |
42 | #include "ServiceWorkerGlobalScope.h" |
43 | #include "SocketProvider.h" |
44 | #include "WorkerInspectorController.h" |
45 | #include "WorkerLoaderProxy.h" |
46 | #include "WorkerLocation.h" |
47 | #include "WorkerNavigator.h" |
48 | #include "WorkerReportingProxy.h" |
49 | #include "WorkerScriptLoader.h" |
50 | #include "WorkerThread.h" |
51 | #include <JavaScriptCore/ScriptArguments.h> |
52 | #include <JavaScriptCore/ScriptCallStack.h> |
53 | #include <wtf/IsoMallocInlines.h> |
54 | |
55 | namespace WebCore { |
56 | using namespace Inspector; |
57 | |
58 | WTF_MAKE_ISO_ALLOCATED_IMPL(WorkerGlobalScope); |
59 | |
60 | WorkerGlobalScope::WorkerGlobalScope(const URL& url, Ref<SecurityOrigin>&& origin, const String& identifier, const String& userAgent, bool isOnline, WorkerThread& thread, bool shouldBypassMainWorldContentSecurityPolicy, Ref<SecurityOrigin>&& topOrigin, MonotonicTime timeOrigin, IDBClient::IDBConnectionProxy* connectionProxy, SocketProvider* socketProvider, PAL::SessionID sessionID) |
61 | : m_url(url) |
62 | , m_identifier(identifier) |
63 | , m_userAgent(userAgent) |
64 | , m_thread(thread) |
65 | , m_script(std::make_unique<WorkerScriptController>(this)) |
66 | , m_inspectorController(std::make_unique<WorkerInspectorController>(*this)) |
67 | , m_microtaskQueue(std::make_unique<MicrotaskQueue>()) |
68 | , m_isOnline(isOnline) |
69 | , m_shouldBypassMainWorldContentSecurityPolicy(shouldBypassMainWorldContentSecurityPolicy) |
70 | , m_eventQueue(*this) |
71 | , m_topOrigin(WTFMove(topOrigin)) |
72 | #if ENABLE(INDEXED_DATABASE) |
73 | , m_connectionProxy(connectionProxy) |
74 | #endif |
75 | , m_socketProvider(socketProvider) |
76 | , m_performance(Performance::create(this, timeOrigin)) |
77 | , m_sessionID(sessionID) |
78 | { |
79 | #if !ENABLE(INDEXED_DATABASE) |
80 | UNUSED_PARAM(connectionProxy); |
81 | #endif |
82 | |
83 | if (m_topOrigin->hasUniversalAccess()) |
84 | origin->grantUniversalAccess(); |
85 | if (m_topOrigin->needsStorageAccessFromFileURLsQuirk()) |
86 | origin->grantStorageAccessFromFileURLsQuirk(); |
87 | |
88 | setSecurityOriginPolicy(SecurityOriginPolicy::create(WTFMove(origin))); |
89 | setContentSecurityPolicy(std::make_unique<ContentSecurityPolicy>(URL { m_url }, *this)); |
90 | } |
91 | |
92 | WorkerGlobalScope::~WorkerGlobalScope() |
93 | { |
94 | ASSERT(thread().thread() == &Thread::current()); |
95 | // We need to remove from the contexts map very early in the destructor so that calling postTask() on this WorkerGlobalScope from another thread is safe. |
96 | removeFromContextsMap(); |
97 | |
98 | m_performance = nullptr; |
99 | m_crypto = nullptr; |
100 | |
101 | // Notify proxy that we are going away. This can free the WorkerThread object, so do not access it after this. |
102 | thread().workerReportingProxy().workerGlobalScopeDestroyed(); |
103 | } |
104 | |
105 | String WorkerGlobalScope::origin() const |
106 | { |
107 | auto* securityOrigin = this->securityOrigin(); |
108 | return securityOrigin ? securityOrigin->toString() : emptyString(); |
109 | } |
110 | |
111 | void WorkerGlobalScope::prepareForTermination() |
112 | { |
113 | #if ENABLE(INDEXED_DATABASE) |
114 | stopIndexedDatabase(); |
115 | #endif |
116 | |
117 | stopActiveDOMObjects(); |
118 | |
119 | if (m_cacheStorageConnection) |
120 | m_cacheStorageConnection->clearPendingRequests(); |
121 | |
122 | m_inspectorController->workerTerminating(); |
123 | |
124 | // Event listeners would keep DOMWrapperWorld objects alive for too long. Also, they have references to JS objects, |
125 | // which become dangling once Heap is destroyed. |
126 | removeAllEventListeners(); |
127 | |
128 | // MicrotaskQueue and RejectedPromiseTracker reference Heap. |
129 | m_microtaskQueue = nullptr; |
130 | removeRejectedPromiseTracker(); |
131 | } |
132 | |
133 | void WorkerGlobalScope::removeAllEventListeners() |
134 | { |
135 | EventTarget::removeAllEventListeners(); |
136 | m_performance->removeAllEventListeners(); |
137 | m_performance->removeAllObservers(); |
138 | } |
139 | |
140 | bool WorkerGlobalScope::isSecureContext() const |
141 | { |
142 | return securityOrigin() && securityOrigin()->isPotentiallyTrustworthy(); |
143 | } |
144 | |
145 | void WorkerGlobalScope::(const ContentSecurityPolicyResponseHeaders& ) |
146 | { |
147 | contentSecurityPolicy()->didReceiveHeaders(contentSecurityPolicyResponseHeaders, String { }); |
148 | } |
149 | |
150 | URL WorkerGlobalScope::completeURL(const String& url) const |
151 | { |
152 | // Always return a null URL when passed a null string. |
153 | // FIXME: Should we change the URL constructor to have this behavior? |
154 | if (url.isNull()) |
155 | return URL(); |
156 | // Always use UTF-8 in Workers. |
157 | return URL(m_url, url); |
158 | } |
159 | |
160 | String WorkerGlobalScope::userAgent(const URL&) const |
161 | { |
162 | return m_userAgent; |
163 | } |
164 | |
165 | void WorkerGlobalScope::disableEval(const String& errorMessage) |
166 | { |
167 | m_script->disableEval(errorMessage); |
168 | } |
169 | |
170 | void WorkerGlobalScope::disableWebAssembly(const String& errorMessage) |
171 | { |
172 | m_script->disableWebAssembly(errorMessage); |
173 | } |
174 | |
175 | SocketProvider* WorkerGlobalScope::socketProvider() |
176 | { |
177 | return m_socketProvider.get(); |
178 | } |
179 | |
180 | #if ENABLE(INDEXED_DATABASE) |
181 | |
182 | IDBClient::IDBConnectionProxy* WorkerGlobalScope::idbConnectionProxy() |
183 | { |
184 | #if ENABLE(INDEXED_DATABASE_IN_WORKERS) |
185 | return m_connectionProxy.get(); |
186 | #else |
187 | return nullptr; |
188 | #endif |
189 | } |
190 | |
191 | void WorkerGlobalScope::stopIndexedDatabase() |
192 | { |
193 | #if ENABLE(INDEXED_DATABASE_IN_WORKERS) |
194 | if (m_connectionProxy) |
195 | m_connectionProxy->forgetActivityForCurrentThread(); |
196 | #endif |
197 | } |
198 | |
199 | #endif // ENABLE(INDEXED_DATABASE) |
200 | |
201 | WorkerLocation& WorkerGlobalScope::location() const |
202 | { |
203 | if (!m_location) |
204 | m_location = WorkerLocation::create(m_url); |
205 | return *m_location; |
206 | } |
207 | |
208 | void WorkerGlobalScope::close() |
209 | { |
210 | if (m_closing) |
211 | return; |
212 | |
213 | // Let current script run to completion but prevent future script evaluations. |
214 | // After m_closing is set, all the tasks in the queue continue to be fetched but only |
215 | // tasks with isCleanupTask()==true will be executed. |
216 | m_closing = true; |
217 | postTask({ ScriptExecutionContext::Task::CleanupTask, [] (ScriptExecutionContext& context) { |
218 | ASSERT_WITH_SECURITY_IMPLICATION(is<WorkerGlobalScope>(context)); |
219 | WorkerGlobalScope& workerGlobalScope = downcast<WorkerGlobalScope>(context); |
220 | // Notify parent that this context is closed. Parent is responsible for calling WorkerThread::stop(). |
221 | workerGlobalScope.thread().workerReportingProxy().workerGlobalScopeClosed(); |
222 | } }); |
223 | } |
224 | |
225 | WorkerNavigator& WorkerGlobalScope::navigator() |
226 | { |
227 | if (!m_navigator) |
228 | m_navigator = WorkerNavigator::create(*this, m_userAgent, m_isOnline); |
229 | return *m_navigator; |
230 | } |
231 | |
232 | void WorkerGlobalScope::setIsOnline(bool isOnline) |
233 | { |
234 | m_isOnline = isOnline; |
235 | if (m_navigator) |
236 | m_navigator->setIsOnline(isOnline); |
237 | } |
238 | |
239 | void WorkerGlobalScope::postTask(Task&& task) |
240 | { |
241 | thread().runLoop().postTask(WTFMove(task)); |
242 | } |
243 | |
244 | ExceptionOr<int> WorkerGlobalScope::setTimeout(JSC::ExecState& state, std::unique_ptr<ScheduledAction> action, int timeout, Vector<JSC::Strong<JSC::Unknown>>&& arguments) |
245 | { |
246 | // FIXME: Should this check really happen here? Or should it happen when code is about to eval? |
247 | if (action->type() == ScheduledAction::Type::Code) { |
248 | if (!contentSecurityPolicy()->allowEval(&state)) |
249 | return 0; |
250 | } |
251 | |
252 | action->addArguments(WTFMove(arguments)); |
253 | |
254 | return DOMTimer::install(*this, WTFMove(action), Seconds::fromMilliseconds(timeout), true); |
255 | } |
256 | |
257 | void WorkerGlobalScope::clearTimeout(int timeoutId) |
258 | { |
259 | DOMTimer::removeById(*this, timeoutId); |
260 | } |
261 | |
262 | ExceptionOr<int> WorkerGlobalScope::setInterval(JSC::ExecState& state, std::unique_ptr<ScheduledAction> action, int timeout, Vector<JSC::Strong<JSC::Unknown>>&& arguments) |
263 | { |
264 | // FIXME: Should this check really happen here? Or should it happen when code is about to eval? |
265 | if (action->type() == ScheduledAction::Type::Code) { |
266 | if (!contentSecurityPolicy()->allowEval(&state)) |
267 | return 0; |
268 | } |
269 | |
270 | action->addArguments(WTFMove(arguments)); |
271 | |
272 | return DOMTimer::install(*this, WTFMove(action), Seconds::fromMilliseconds(timeout), false); |
273 | } |
274 | |
275 | void WorkerGlobalScope::clearInterval(int timeoutId) |
276 | { |
277 | DOMTimer::removeById(*this, timeoutId); |
278 | } |
279 | |
280 | ExceptionOr<void> WorkerGlobalScope::importScripts(const Vector<String>& urls) |
281 | { |
282 | ASSERT(contentSecurityPolicy()); |
283 | |
284 | Vector<URL> completedURLs; |
285 | completedURLs.reserveInitialCapacity(urls.size()); |
286 | for (auto& entry : urls) { |
287 | URL url = completeURL(entry); |
288 | if (!url.isValid()) |
289 | return Exception { SyntaxError }; |
290 | completedURLs.uncheckedAppend(WTFMove(url)); |
291 | } |
292 | |
293 | FetchOptions::Cache cachePolicy = FetchOptions::Cache::Default; |
294 | |
295 | #if ENABLE(SERVICE_WORKER) |
296 | bool isServiceWorkerGlobalScope = is<ServiceWorkerGlobalScope>(*this); |
297 | if (isServiceWorkerGlobalScope) { |
298 | // FIXME: We need to add support for the 'imported scripts updated' flag as per: |
299 | // https://w3c.github.io/ServiceWorker/#importscripts |
300 | auto& serviceWorkerGlobalScope = downcast<ServiceWorkerGlobalScope>(*this); |
301 | auto& registration = serviceWorkerGlobalScope.registration(); |
302 | if (registration.updateViaCache() == ServiceWorkerUpdateViaCache::None || registration.needsUpdate()) |
303 | cachePolicy = FetchOptions::Cache::NoCache; |
304 | } |
305 | #endif |
306 | |
307 | for (auto& url : completedURLs) { |
308 | // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved. |
309 | bool shouldBypassMainWorldContentSecurityPolicy = this->shouldBypassMainWorldContentSecurityPolicy(); |
310 | if (!shouldBypassMainWorldContentSecurityPolicy && !contentSecurityPolicy()->allowScriptFromSource(url)) |
311 | return Exception { NetworkError }; |
312 | |
313 | auto scriptLoader = WorkerScriptLoader::create(); |
314 | auto cspEnforcement = shouldBypassMainWorldContentSecurityPolicy ? ContentSecurityPolicyEnforcement::DoNotEnforce : ContentSecurityPolicyEnforcement::EnforceScriptSrcDirective; |
315 | if (auto exception = scriptLoader->loadSynchronously(this, url, FetchOptions::Mode::NoCors, cachePolicy, cspEnforcement, resourceRequestIdentifier())) |
316 | return WTFMove(*exception); |
317 | |
318 | InspectorInstrumentation::scriptImported(*this, scriptLoader->identifier(), scriptLoader->script()); |
319 | |
320 | NakedPtr<JSC::Exception> exception; |
321 | m_script->evaluate(ScriptSourceCode(scriptLoader->script(), URL(scriptLoader->responseURL())), exception); |
322 | if (exception) { |
323 | m_script->setException(exception); |
324 | return { }; |
325 | } |
326 | } |
327 | |
328 | return { }; |
329 | } |
330 | |
331 | EventTarget* WorkerGlobalScope::errorEventTarget() |
332 | { |
333 | return this; |
334 | } |
335 | |
336 | void WorkerGlobalScope::logExceptionToConsole(const String& errorMessage, const String& sourceURL, int lineNumber, int columnNumber, RefPtr<ScriptCallStack>&&) |
337 | { |
338 | thread().workerReportingProxy().postExceptionToWorkerObject(errorMessage, lineNumber, columnNumber, sourceURL); |
339 | } |
340 | |
341 | void WorkerGlobalScope::addConsoleMessage(std::unique_ptr<Inspector::ConsoleMessage>&& message) |
342 | { |
343 | if (!isContextThread()) { |
344 | postTask(AddConsoleMessageTask(message->source(), message->level(), message->message())); |
345 | return; |
346 | } |
347 | |
348 | InspectorInstrumentation::addMessageToConsole(*this, WTFMove(message)); |
349 | } |
350 | |
351 | void WorkerGlobalScope::addConsoleMessage(MessageSource source, MessageLevel level, const String& message, unsigned long requestIdentifier) |
352 | { |
353 | addMessage(source, level, message, { }, 0, 0, nullptr, nullptr, requestIdentifier); |
354 | } |
355 | |
356 | void WorkerGlobalScope::addMessage(MessageSource source, MessageLevel level, const String& messageText, const String& sourceURL, unsigned lineNumber, unsigned columnNumber, RefPtr<ScriptCallStack>&& callStack, JSC::ExecState* state, unsigned long requestIdentifier) |
357 | { |
358 | if (!isContextThread()) { |
359 | postTask(AddConsoleMessageTask(source, level, messageText)); |
360 | return; |
361 | } |
362 | |
363 | std::unique_ptr<Inspector::ConsoleMessage> message; |
364 | if (callStack) |
365 | message = std::make_unique<Inspector::ConsoleMessage>(source, MessageType::Log, level, messageText, callStack.releaseNonNull(), requestIdentifier); |
366 | else |
367 | message = std::make_unique<Inspector::ConsoleMessage>(source, MessageType::Log, level, messageText, sourceURL, lineNumber, columnNumber, state, requestIdentifier); |
368 | InspectorInstrumentation::addMessageToConsole(*this, WTFMove(message)); |
369 | } |
370 | |
371 | bool WorkerGlobalScope::isContextThread() const |
372 | { |
373 | return thread().thread() == &Thread::current(); |
374 | } |
375 | |
376 | bool WorkerGlobalScope::isJSExecutionForbidden() const |
377 | { |
378 | return m_script->isExecutionForbidden(); |
379 | } |
380 | |
381 | WorkerEventQueue& WorkerGlobalScope::eventQueue() const |
382 | { |
383 | return m_eventQueue; |
384 | } |
385 | |
386 | #if ENABLE(WEB_CRYPTO) |
387 | |
388 | bool WorkerGlobalScope::wrapCryptoKey(const Vector<uint8_t>& key, Vector<uint8_t>& wrappedKey) |
389 | { |
390 | bool result = false; |
391 | bool done = false; |
392 | m_thread.workerLoaderProxy().postTaskToLoader([&result, &key, &wrappedKey, &done, workerGlobalScope = this](ScriptExecutionContext& context) { |
393 | result = context.wrapCryptoKey(key, wrappedKey); |
394 | done = true; |
395 | workerGlobalScope->postTask([](ScriptExecutionContext& context) { |
396 | ASSERT_UNUSED(context, context.isWorkerGlobalScope()); |
397 | }); |
398 | }); |
399 | |
400 | auto waitResult = MessageQueueMessageReceived; |
401 | while (!done && waitResult != MessageQueueTerminated) |
402 | waitResult = m_thread.runLoop().runInMode(this, WorkerRunLoop::defaultMode()); |
403 | |
404 | return result; |
405 | } |
406 | |
407 | bool WorkerGlobalScope::unwrapCryptoKey(const Vector<uint8_t>& wrappedKey, Vector<uint8_t>& key) |
408 | { |
409 | bool result = false, done = false; |
410 | m_thread.workerLoaderProxy().postTaskToLoader([&result, &wrappedKey, &key, &done, workerGlobalScope = this](ScriptExecutionContext& context) { |
411 | result = context.unwrapCryptoKey(wrappedKey, key); |
412 | done = true; |
413 | workerGlobalScope->postTask([](ScriptExecutionContext& context) { |
414 | ASSERT_UNUSED(context, context.isWorkerGlobalScope()); |
415 | }); |
416 | }); |
417 | |
418 | auto waitResult = MessageQueueMessageReceived; |
419 | while (!done && waitResult != MessageQueueTerminated) |
420 | waitResult = m_thread.runLoop().runInMode(this, WorkerRunLoop::defaultMode()); |
421 | |
422 | return result; |
423 | } |
424 | |
425 | #endif // ENABLE(WEB_CRYPTO) |
426 | |
427 | Crypto& WorkerGlobalScope::crypto() |
428 | { |
429 | if (!m_crypto) |
430 | m_crypto = Crypto::create(this); |
431 | return *m_crypto; |
432 | } |
433 | |
434 | Performance& WorkerGlobalScope::performance() const |
435 | { |
436 | return *m_performance; |
437 | } |
438 | |
439 | WorkerCacheStorageConnection& WorkerGlobalScope::cacheStorageConnection() |
440 | { |
441 | if (!m_cacheStorageConnection) |
442 | m_cacheStorageConnection = WorkerCacheStorageConnection::create(*this); |
443 | return *m_cacheStorageConnection; |
444 | } |
445 | |
446 | void WorkerGlobalScope::createImageBitmap(ImageBitmap::Source&& source, ImageBitmapOptions&& options, ImageBitmap::Promise&& promise) |
447 | { |
448 | ImageBitmap::createPromise(*this, WTFMove(source), WTFMove(options), WTFMove(promise)); |
449 | } |
450 | |
451 | void WorkerGlobalScope::createImageBitmap(ImageBitmap::Source&& source, int sx, int sy, int sw, int sh, ImageBitmapOptions&& options, ImageBitmap::Promise&& promise) |
452 | { |
453 | ImageBitmap::createPromise(*this, WTFMove(source), WTFMove(options), sx, sy, sw, sh, WTFMove(promise)); |
454 | } |
455 | |
456 | } // namespace WebCore |
457 | |