| 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 | |
| 48 | namespace WebCore { |
| 49 | using namespace JSC; |
| 50 | |
| 51 | WorkerScriptController::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 | |
| 60 | WorkerScriptController::~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 | |
| 72 | void 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 | |
| 124 | void 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 | |
| 137 | void 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 | |
| 171 | void 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 | |
| 179 | void 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 | |
| 194 | bool WorkerScriptController::isTerminatingExecution() const |
| 195 | { |
| 196 | // See comments in scheduleExecutionTermination regarding mutex usage. |
| 197 | LockHolder locker(m_scheduledTerminationMutex); |
| 198 | return m_isTerminatingExecution; |
| 199 | } |
| 200 | |
| 201 | void WorkerScriptController::forbidExecution() |
| 202 | { |
| 203 | ASSERT(m_workerGlobalScope->isContextThread()); |
| 204 | m_executionForbidden = true; |
| 205 | } |
| 206 | |
| 207 | bool WorkerScriptController::isExecutionForbidden() const |
| 208 | { |
| 209 | ASSERT(m_workerGlobalScope->isContextThread()); |
| 210 | return m_executionForbidden; |
| 211 | } |
| 212 | |
| 213 | void WorkerScriptController::disableEval(const String& errorMessage) |
| 214 | { |
| 215 | initScriptIfNeeded(); |
| 216 | JSLockHolder lock{vm()}; |
| 217 | |
| 218 | m_workerGlobalScopeWrapper->setEvalEnabled(false, errorMessage); |
| 219 | } |
| 220 | |
| 221 | void WorkerScriptController::disableWebAssembly(const String& errorMessage) |
| 222 | { |
| 223 | initScriptIfNeeded(); |
| 224 | JSLockHolder lock{vm()}; |
| 225 | |
| 226 | m_workerGlobalScopeWrapper->setWebAssemblyEnabled(false, errorMessage); |
| 227 | } |
| 228 | |
| 229 | void WorkerScriptController::releaseHeapAccess() |
| 230 | { |
| 231 | m_vm->heap.releaseAccess(); |
| 232 | } |
| 233 | |
| 234 | void WorkerScriptController::acquireHeapAccess() |
| 235 | { |
| 236 | m_vm->heap.acquireAccess(); |
| 237 | } |
| 238 | |
| 239 | void 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 | |
| 252 | void 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 | |
| 265 | void WorkerScriptController::attachDebugger(JSC::Debugger* debugger) |
| 266 | { |
| 267 | initScriptIfNeeded(); |
| 268 | debugger->attach(m_workerGlobalScopeWrapper->globalObject()); |
| 269 | } |
| 270 | |
| 271 | void WorkerScriptController::detachDebugger(JSC::Debugger* debugger) |
| 272 | { |
| 273 | debugger->detach(m_workerGlobalScopeWrapper->globalObject(), JSC::Debugger::TerminatingDebuggingSession); |
| 274 | } |
| 275 | |
| 276 | } // namespace WebCore |
| 277 | |