1/*
2 * Copyright (C) 2008-2017 Apple Inc. All Rights Reserved.
3 * Copyright (C) 2011, 2012 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#include "config.h"
28#include "WorkerScriptController.h"
29
30#include "JSDOMBinding.h"
31#include "JSDedicatedWorkerGlobalScope.h"
32#include "JSEventTarget.h"
33#include "JSExecState.h"
34#include "JSServiceWorkerGlobalScope.h"
35#include "ScriptSourceCode.h"
36#include "WebCoreJSClientData.h"
37#include "WorkerConsoleClient.h"
38#include "WorkerGlobalScope.h"
39#include <JavaScriptCore/Completion.h>
40#include <JavaScriptCore/Exception.h>
41#include <JavaScriptCore/ExceptionHelpers.h>
42#include <JavaScriptCore/GCActivityCallback.h>
43#include <JavaScriptCore/JSCInlines.h>
44#include <JavaScriptCore/JSLock.h>
45#include <JavaScriptCore/PromiseDeferredTimer.h>
46#include <JavaScriptCore/StrongInlines.h>
47
48namespace WebCore {
49using namespace JSC;
50
51WorkerScriptController::WorkerScriptController(WorkerGlobalScope* workerGlobalScope)
52 : m_vm(VM::create())
53 , m_workerGlobalScope(workerGlobalScope)
54 , m_workerGlobalScopeWrapper(*m_vm)
55{
56 m_vm->heap.acquireAccess(); // It's not clear that we have good discipline for heap access, so turn it on permanently.
57 JSVMClientData::initNormalWorld(m_vm.get());
58}
59
60WorkerScriptController::~WorkerScriptController()
61{
62 JSLockHolder lock(vm());
63 if (m_workerGlobalScopeWrapper) {
64 m_workerGlobalScopeWrapper->clearDOMGuardedObjects();
65 m_workerGlobalScopeWrapper->setConsoleClient(nullptr);
66 m_consoleClient = nullptr;
67 }
68 m_workerGlobalScopeWrapper.clear();
69 m_vm = nullptr;
70}
71
72void WorkerScriptController::initScript()
73{
74 ASSERT(!m_workerGlobalScopeWrapper);
75
76 JSLockHolder lock(m_vm.get());
77
78 // Explicitly protect the global object's prototype so it isn't collected
79 // when we allocate the global object. (Once the global object is fully
80 // constructed, it can mark its own prototype.)
81 if (m_workerGlobalScope->isDedicatedWorkerGlobalScope()) {
82 Structure* dedicatedContextPrototypeStructure = JSDedicatedWorkerGlobalScopePrototype::createStructure(*m_vm, nullptr, jsNull());
83 Strong<JSDedicatedWorkerGlobalScopePrototype> dedicatedContextPrototype(*m_vm, JSDedicatedWorkerGlobalScopePrototype::create(*m_vm, nullptr, dedicatedContextPrototypeStructure));
84 Structure* structure = JSDedicatedWorkerGlobalScope::createStructure(*m_vm, nullptr, dedicatedContextPrototype.get());
85 auto* proxyStructure = JSProxy::createStructure(*m_vm, nullptr, jsNull(), PureForwardingProxyType);
86 auto* proxy = JSProxy::create(*m_vm, proxyStructure);
87
88 m_workerGlobalScopeWrapper.set(*m_vm, JSDedicatedWorkerGlobalScope::create(*m_vm, structure, static_cast<DedicatedWorkerGlobalScope&>(*m_workerGlobalScope), proxy));
89 dedicatedContextPrototypeStructure->setGlobalObject(*m_vm, m_workerGlobalScopeWrapper.get());
90 ASSERT(structure->globalObject() == m_workerGlobalScopeWrapper);
91 ASSERT(m_workerGlobalScopeWrapper->structure(*m_vm)->globalObject() == m_workerGlobalScopeWrapper);
92 dedicatedContextPrototype->structure(*m_vm)->setGlobalObject(*m_vm, m_workerGlobalScopeWrapper.get());
93 dedicatedContextPrototype->structure(*m_vm)->setPrototypeWithoutTransition(*m_vm, JSWorkerGlobalScope::prototype(*m_vm, *m_workerGlobalScopeWrapper.get()));
94
95 proxy->setTarget(*m_vm, m_workerGlobalScopeWrapper.get());
96 proxy->structure(*m_vm)->setGlobalObject(*m_vm, m_workerGlobalScopeWrapper.get());
97#if ENABLE(SERVICE_WORKER)
98 } else if (m_workerGlobalScope->isServiceWorkerGlobalScope()) {
99 Structure* contextPrototypeStructure = JSServiceWorkerGlobalScopePrototype::createStructure(*m_vm, nullptr, jsNull());
100 Strong<JSServiceWorkerGlobalScopePrototype> contextPrototype(*m_vm, JSServiceWorkerGlobalScopePrototype::create(*m_vm, nullptr, contextPrototypeStructure));
101 Structure* structure = JSServiceWorkerGlobalScope::createStructure(*m_vm, nullptr, contextPrototype.get());
102 auto* proxyStructure = JSProxy::createStructure(*m_vm, nullptr, jsNull(), PureForwardingProxyType);
103 auto* proxy = JSProxy::create(*m_vm, proxyStructure);
104
105 m_workerGlobalScopeWrapper.set(*m_vm, JSServiceWorkerGlobalScope::create(*m_vm, structure, static_cast<ServiceWorkerGlobalScope&>(*m_workerGlobalScope), proxy));
106 contextPrototypeStructure->setGlobalObject(*m_vm, m_workerGlobalScopeWrapper.get());
107 ASSERT(structure->globalObject() == m_workerGlobalScopeWrapper);
108 ASSERT(m_workerGlobalScopeWrapper->structure()->globalObject() == m_workerGlobalScopeWrapper);
109 contextPrototype->structure(*m_vm)->setGlobalObject(*m_vm, m_workerGlobalScopeWrapper.get());
110 contextPrototype->structure(*m_vm)->setPrototypeWithoutTransition(*m_vm, JSWorkerGlobalScope::prototype(*m_vm, *m_workerGlobalScopeWrapper.get()));
111
112 proxy->setTarget(*m_vm, m_workerGlobalScopeWrapper.get());
113 proxy->structure(*m_vm)->setGlobalObject(*m_vm, m_workerGlobalScopeWrapper.get());
114#endif
115 }
116
117 ASSERT(m_workerGlobalScopeWrapper->globalObject() == m_workerGlobalScopeWrapper);
118 ASSERT(asObject(m_workerGlobalScopeWrapper->getPrototypeDirect(*m_vm))->globalObject() == m_workerGlobalScopeWrapper);
119
120 m_consoleClient = std::make_unique<WorkerConsoleClient>(*m_workerGlobalScope);
121 m_workerGlobalScopeWrapper->setConsoleClient(m_consoleClient.get());
122}
123
124void WorkerScriptController::evaluate(const ScriptSourceCode& sourceCode, String* returnedExceptionMessage)
125{
126 if (isExecutionForbidden())
127 return;
128
129 NakedPtr<JSC::Exception> exception;
130 evaluate(sourceCode, exception, returnedExceptionMessage);
131 if (exception) {
132 JSLockHolder lock(vm());
133 reportException(m_workerGlobalScopeWrapper->globalExec(), exception);
134 }
135}
136
137void WorkerScriptController::evaluate(const ScriptSourceCode& sourceCode, NakedPtr<JSC::Exception>& returnedException, String* returnedExceptionMessage)
138{
139 if (isExecutionForbidden())
140 return;
141
142 initScriptIfNeeded();
143
144 auto& state = *m_workerGlobalScopeWrapper->globalExec();
145 VM& vm = state.vm();
146 JSLockHolder lock { vm };
147
148 JSExecState::profiledEvaluate(&state, JSC::ProfilingReason::Other, sourceCode.jsSourceCode(), m_workerGlobalScopeWrapper->globalThis(), returnedException);
149
150 if ((returnedException && isTerminatedExecutionException(vm, returnedException)) || isTerminatingExecution()) {
151 forbidExecution();
152 return;
153 }
154
155 if (returnedException) {
156 if (m_workerGlobalScope->canIncludeErrorDetails(sourceCode.cachedScript(), sourceCode.url().string())) {
157 // FIXME: It's not great that this can run arbitrary code to string-ify the value of the exception.
158 // Do we need to do anything to handle that properly, if it, say, raises another exception?
159 if (returnedExceptionMessage)
160 *returnedExceptionMessage = returnedException->value().toWTFString(&state);
161 } else {
162 // Overwrite the detailed error with a generic error.
163 String genericErrorMessage { "Script error."_s };
164 if (returnedExceptionMessage)
165 *returnedExceptionMessage = genericErrorMessage;
166 returnedException = JSC::Exception::create(vm, createError(&state, genericErrorMessage));
167 }
168 }
169}
170
171void WorkerScriptController::setException(JSC::Exception* exception)
172{
173 JSC::ExecState* exec = m_workerGlobalScopeWrapper->globalExec();
174 VM& vm = exec->vm();
175 auto scope = DECLARE_THROW_SCOPE(vm);
176 throwException(exec, scope, exception);
177}
178
179void WorkerScriptController::scheduleExecutionTermination()
180{
181 if (m_isTerminatingExecution)
182 return;
183
184 {
185 // The mutex provides a memory barrier to ensure that once
186 // termination is scheduled, isTerminatingExecution() will
187 // accurately reflect that state when called from another thread.
188 LockHolder locker(m_scheduledTerminationMutex);
189 m_isTerminatingExecution = true;
190 }
191 m_vm->notifyNeedTermination();
192}
193
194bool WorkerScriptController::isTerminatingExecution() const
195{
196 // See comments in scheduleExecutionTermination regarding mutex usage.
197 LockHolder locker(m_scheduledTerminationMutex);
198 return m_isTerminatingExecution;
199}
200
201void WorkerScriptController::forbidExecution()
202{
203 ASSERT(m_workerGlobalScope->isContextThread());
204 m_executionForbidden = true;
205}
206
207bool WorkerScriptController::isExecutionForbidden() const
208{
209 ASSERT(m_workerGlobalScope->isContextThread());
210 return m_executionForbidden;
211}
212
213void WorkerScriptController::disableEval(const String& errorMessage)
214{
215 initScriptIfNeeded();
216 JSLockHolder lock{vm()};
217
218 m_workerGlobalScopeWrapper->setEvalEnabled(false, errorMessage);
219}
220
221void WorkerScriptController::disableWebAssembly(const String& errorMessage)
222{
223 initScriptIfNeeded();
224 JSLockHolder lock{vm()};
225
226 m_workerGlobalScopeWrapper->setWebAssemblyEnabled(false, errorMessage);
227}
228
229void WorkerScriptController::releaseHeapAccess()
230{
231 m_vm->heap.releaseAccess();
232}
233
234void WorkerScriptController::acquireHeapAccess()
235{
236 m_vm->heap.acquireAccess();
237}
238
239void WorkerScriptController::addTimerSetNotification(JSC::JSRunLoopTimer::TimerNotificationCallback callback)
240{
241 auto processTimer = [&] (JSRunLoopTimer* timer) {
242 if (!timer)
243 return;
244 timer->addTimerSetNotification(callback);
245 };
246
247 processTimer(m_vm->heap.fullActivityCallback());
248 processTimer(m_vm->heap.edenActivityCallback());
249 processTimer(m_vm->promiseDeferredTimer.get());
250}
251
252void WorkerScriptController::removeTimerSetNotification(JSC::JSRunLoopTimer::TimerNotificationCallback callback)
253{
254 auto processTimer = [&] (JSRunLoopTimer* timer) {
255 if (!timer)
256 return;
257 timer->removeTimerSetNotification(callback);
258 };
259
260 processTimer(m_vm->heap.fullActivityCallback());
261 processTimer(m_vm->heap.edenActivityCallback());
262 processTimer(m_vm->promiseDeferredTimer.get());
263}
264
265void WorkerScriptController::attachDebugger(JSC::Debugger* debugger)
266{
267 initScriptIfNeeded();
268 debugger->attach(m_workerGlobalScopeWrapper->globalObject());
269}
270
271void WorkerScriptController::detachDebugger(JSC::Debugger* debugger)
272{
273 debugger->detach(m_workerGlobalScopeWrapper->globalObject(), JSC::Debugger::TerminatingDebuggingSession);
274}
275
276} // namespace WebCore
277