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
55namespace WebCore {
56using namespace Inspector;
57
58WTF_MAKE_ISO_ALLOCATED_IMPL(WorkerGlobalScope);
59
60WorkerGlobalScope::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
92WorkerGlobalScope::~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
105String WorkerGlobalScope::origin() const
106{
107 auto* securityOrigin = this->securityOrigin();
108 return securityOrigin ? securityOrigin->toString() : emptyString();
109}
110
111void 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
133void WorkerGlobalScope::removeAllEventListeners()
134{
135 EventTarget::removeAllEventListeners();
136 m_performance->removeAllEventListeners();
137 m_performance->removeAllObservers();
138}
139
140bool WorkerGlobalScope::isSecureContext() const
141{
142 return securityOrigin() && securityOrigin()->isPotentiallyTrustworthy();
143}
144
145void WorkerGlobalScope::applyContentSecurityPolicyResponseHeaders(const ContentSecurityPolicyResponseHeaders& contentSecurityPolicyResponseHeaders)
146{
147 contentSecurityPolicy()->didReceiveHeaders(contentSecurityPolicyResponseHeaders, String { });
148}
149
150URL 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
160String WorkerGlobalScope::userAgent(const URL&) const
161{
162 return m_userAgent;
163}
164
165void WorkerGlobalScope::disableEval(const String& errorMessage)
166{
167 m_script->disableEval(errorMessage);
168}
169
170void WorkerGlobalScope::disableWebAssembly(const String& errorMessage)
171{
172 m_script->disableWebAssembly(errorMessage);
173}
174
175SocketProvider* WorkerGlobalScope::socketProvider()
176{
177 return m_socketProvider.get();
178}
179
180#if ENABLE(INDEXED_DATABASE)
181
182IDBClient::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
191void 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
201WorkerLocation& WorkerGlobalScope::location() const
202{
203 if (!m_location)
204 m_location = WorkerLocation::create(m_url);
205 return *m_location;
206}
207
208void 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
225WorkerNavigator& WorkerGlobalScope::navigator()
226{
227 if (!m_navigator)
228 m_navigator = WorkerNavigator::create(*this, m_userAgent, m_isOnline);
229 return *m_navigator;
230}
231
232void WorkerGlobalScope::setIsOnline(bool isOnline)
233{
234 m_isOnline = isOnline;
235 if (m_navigator)
236 m_navigator->setIsOnline(isOnline);
237}
238
239void WorkerGlobalScope::postTask(Task&& task)
240{
241 thread().runLoop().postTask(WTFMove(task));
242}
243
244ExceptionOr<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
257void WorkerGlobalScope::clearTimeout(int timeoutId)
258{
259 DOMTimer::removeById(*this, timeoutId);
260}
261
262ExceptionOr<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
275void WorkerGlobalScope::clearInterval(int timeoutId)
276{
277 DOMTimer::removeById(*this, timeoutId);
278}
279
280ExceptionOr<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
331EventTarget* WorkerGlobalScope::errorEventTarget()
332{
333 return this;
334}
335
336void 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
341void 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
351void 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
356void 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
371bool WorkerGlobalScope::isContextThread() const
372{
373 return thread().thread() == &Thread::current();
374}
375
376bool WorkerGlobalScope::isJSExecutionForbidden() const
377{
378 return m_script->isExecutionForbidden();
379}
380
381WorkerEventQueue& WorkerGlobalScope::eventQueue() const
382{
383 return m_eventQueue;
384}
385
386#if ENABLE(WEB_CRYPTO)
387
388bool 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
407bool 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
427Crypto& WorkerGlobalScope::crypto()
428{
429 if (!m_crypto)
430 m_crypto = Crypto::create(this);
431 return *m_crypto;
432}
433
434Performance& WorkerGlobalScope::performance() const
435{
436 return *m_performance;
437}
438
439WorkerCacheStorageConnection& WorkerGlobalScope::cacheStorageConnection()
440{
441 if (!m_cacheStorageConnection)
442 m_cacheStorageConnection = WorkerCacheStorageConnection::create(*this);
443 return *m_cacheStorageConnection;
444}
445
446void WorkerGlobalScope::createImageBitmap(ImageBitmap::Source&& source, ImageBitmapOptions&& options, ImageBitmap::Promise&& promise)
447{
448 ImageBitmap::createPromise(*this, WTFMove(source), WTFMove(options), WTFMove(promise));
449}
450
451void 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