239static Identifier jsValueToModuleKey(JSGlobalObject* lexicalGlobalObject, JSValue value)
240{
241 if (value.isSymbol())
242 return Identifier::fromUid(jsCast<Symbol*>(value)->privateName());
243 ASSERT(value.isString());
244 return asString(value)->toIdentifier(lexicalGlobalObject);
245}
246
247JSC::JSValue WorkerOrWorkletScriptController::evaluateModule(JSC::JSModuleRecord& moduleRecord)
248{
249 auto& globalObject = *m_globalScopeWrapper.get();
250 VM& vm = globalObject.vm();
251 JSLockHolder lock { vm };
252 return moduleRecord.evaluate(&globalObject);
253}
254
255MessageQueueWaitResult WorkerOrWorkletScriptController::loadModuleSynchronously(LoadableModuleScript& moduleScript, const ScriptSourceCode& sourceCode)
256{
257 if (isExecutionForbidden())
258 return MessageQueueTerminated;
259
260 initScriptIfNeeded();
261
262 auto& globalObject = *m_globalScopeWrapper.get();
263 VM& vm = globalObject.vm();
264 JSLockHolder lock { vm };
265
266 RefPtr<LoadableModuleScript> moduleScriptRef;
267 {
268 auto& promise = JSExecState::loadModule(globalObject, sourceCode.jsSourceCode(), JSC::JSScriptFetcher::create(vm, { &moduleScript }));
269
270 moduleScriptRef = &moduleScript;
271
272 auto& fulfillHandler = *JSNativeStdFunction::create(vm, &globalObject, 1, String(), [moduleScriptRef](JSGlobalObject* globalObject, CallFrame* callFrame) -> JSC::EncodedJSValue {
273 // dataLogLn("FULFILLED");
274 VM& vm = globalObject->vm();
275 auto scope = DECLARE_THROW_SCOPE(vm);
276 Identifier moduleKey = jsValueToModuleKey(globalObject, callFrame->argument(0));
277 RETURN_IF_EXCEPTION(scope, { });
278 moduleScriptRef->notifyLoadCompleted(*moduleKey.impl());
279 return JSValue::encode(jsUndefined());
280 });
281
282 auto& rejectHandler = *JSNativeStdFunction::create(vm, &globalObject, 1, String(), [moduleScriptRef](JSGlobalObject* globalObject, CallFrame* callFrame) {
283 // dataLogLn("REJECTED");
284 VM& vm = globalObject->vm();
285 JSValue errorValue = callFrame->argument(0);
286 if (errorValue.isObject()) {
287 auto* object = JSC::asObject(errorValue);
288 if (JSValue failureKindValue = object->getDirect(vm, static_cast<JSVMClientData&>(*vm.clientData).builtinNames().failureKindPrivateName())) {
289 // This is host propagated error in the module loader pipeline.
290 switch (static_cast<ModuleFetchFailureKind>(failureKindValue.asInt32())) {
291 case ModuleFetchFailureKind::WasErrored:
292 moduleScriptRef->notifyLoadFailed(LoadableScript::Error {
293 LoadableScript::ErrorType::CachedScript,
294 WTF::nullopt
295 });
296 break;
297 case ModuleFetchFailureKind::WasCanceled:
298 moduleScriptRef->notifyLoadWasCanceled();
299 break;
300 }
301 return JSValue::encode(jsUndefined());
302 }
303 }
304
305 auto scope = DECLARE_CATCH_SCOPE(vm);
306 moduleScriptRef->notifyLoadFailed(LoadableScript::Error {
307 LoadableScript::ErrorType::CachedScript,
308 LoadableScript::ConsoleMessage {
309 MessageSource::JS,
310 MessageLevel::Error,
311 retrieveErrorMessage(*globalObject, vm, errorValue, scope),
312 }
313 });
314 return JSValue::encode(jsUndefined());
315 });
316
317 promise.then(&globalObject, &fulfillHandler, &rejectHandler);
318 }
319 m_globalScope->eventLoop().performMicrotaskCheckpoint();
320
321 // Drive RunLoop until we get either of "Worker is terminated", "Loading is done", or "Loading is failed".
322 WorkerRunLoop& runLoop = m_globalScope->workerOrWorkletThread()->runLoop();
323
324 // We do not want to receive messages that are not related to asynchronous resource loading.
325 // Otherwise, a worker discards some messages from the main thread here in a racy way.
326 // For example, the main thread can postMessage just after creating a Worker. In that case, postMessage's
327 // task is queued in WorkerRunLoop before start running module scripts. This task should not be discarded
328 // in the following driving of the RunLoop which mainly attempt to collect initial load of module scripts.
329 String taskMode = "loadModulesInWorkerOrWorkletMode"_s;
330 MessageQueueWaitResult result = MessageQueueMessageReceived;
331 while ((!moduleScriptRef->isLoaded() && !moduleScriptRef->wasCanceled()) && result != MessageQueueTerminated) {
332 result = runLoop.runInMode(m_globalScope, taskMode);
333 if (result != MessageQueueTerminated)
334 m_globalScope->eventLoop().performMicrotaskCheckpoint();
335 }
336
337 // FIXME: Currently we are not offering cancelling.
338 // if (!loader->done() && result == MessageQueueTerminated)
339 // loader->cancel();
340
341 return result;
342}
343
344void WorkerOrWorkletScriptController::linkAndEvaluateModule(LoadableModuleScript& moduleScript, const ScriptSourceCode& sourceCode, String* returnedExceptionMessage)
345{
346 if (isExecutionForbidden())
347 return;
348
349 initScriptIfNeeded();
350
351 auto& globalObject = *m_globalScopeWrapper.get();
352 VM& vm = globalObject.vm();
353 JSLockHolder lock { vm };
354
355 NakedPtr<JSC::Exception> returnedException;
356 JSExecState::linkAndEvaluateModule(globalObject, Identifier::fromUid(vm, moduleScript.moduleKey()), jsUndefined(), returnedException);
357 if ((returnedException && isTerminatedExecutionException(vm, returnedException)) || isTerminatingExecution()) {
358 forbidExecution();
359 return;
360 }
361
362 if (returnedException) {
363 if (m_globalScope->canIncludeErrorDetails(sourceCode.cachedScript(), sourceCode.url().string())) {
364 // FIXME: It's not great that this can run arbitrary code to string-ify the value of the exception.
365 // Do we need to do anything to handle that properly, if it, say, raises another exception?
366 if (returnedExceptionMessage)
367 *returnedExceptionMessage = returnedException->value().toWTFString(&globalObject);
368 } else {
369 // Overwrite the detailed error with a generic error.
370 String genericErrorMessage { "Script error."_s };
371 if (returnedExceptionMessage)
372 *returnedExceptionMessage = genericErrorMessage;
373 returnedException = JSC::Exception::create(vm, createError(&globalObject, genericErrorMessage));
374 }
375 }
376}
377