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(WorkerScriptFetcher& scriptFetcher, 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 auto protector = makeRef(scriptFetcher);
267 {
268 auto& promise = JSExecState::loadModule(globalObject, sourceCode.jsSourceCode(), JSC::JSScriptFetcher::create(vm, { &scriptFetcher }));
269
270 auto& fulfillHandler = *JSNativeStdFunction::create(vm, &globalObject, 1, String(), [protector](JSGlobalObject* globalObject, CallFrame* callFrame) -> JSC::EncodedJSValue {
271 VM& vm = globalObject->vm();
272 JSLockHolder lock { vm };
273 auto scope = DECLARE_THROW_SCOPE(vm);
274 Identifier moduleKey = jsValueToModuleKey(globalObject, callFrame->argument(0));
275 RETURN_IF_EXCEPTION(scope, { });
276 protector->notifyLoadCompleted(*moduleKey.impl());
277 return JSValue::encode(jsUndefined());
278 });
279
280 auto& rejectHandler = *JSNativeStdFunction::create(vm, &globalObject, 1, String(), [protector](JSGlobalObject* globalObject, CallFrame* callFrame) {
281 VM& vm = globalObject->vm();
282 JSLockHolder lock { vm };
283 JSValue errorValue = callFrame->argument(0);
284 if (errorValue.isObject()) {
285 auto* object = JSC::asObject(errorValue);
286 if (JSValue failureKindValue = object->getDirect(vm, static_cast<JSVMClientData&>(*vm.clientData).builtinNames().failureKindPrivateName())) {
287 // This is host propagated error in the module loader pipeline.
288 switch (static_cast<ModuleFetchFailureKind>(failureKindValue.asInt32())) {
289 case ModuleFetchFailureKind::WasErrored:
290 protector->notifyLoadFailed(LoadableScript::Error {
291 LoadableScript::ErrorType::CachedScript,
292 WTF::nullopt
293 });
294 break;
295 case ModuleFetchFailureKind::WasCanceled:
296 protector->notifyLoadWasCanceled();
297 break;
298 }
299 return JSValue::encode(jsUndefined());
300 }
301 }
302
303 auto scope = DECLARE_CATCH_SCOPE(vm);
304 protector->notifyLoadFailed(LoadableScript::Error {
305 LoadableScript::ErrorType::CachedScript,
306 LoadableScript::ConsoleMessage {
307 MessageSource::JS,
308 MessageLevel::Error,
309 retrieveErrorMessage(*globalObject, vm, errorValue, scope),
310 }
311 });
312 return JSValue::encode(jsUndefined());
313 });
314
315 promise.then(&globalObject, &fulfillHandler, &rejectHandler);
316 }
317 m_globalScope->eventLoop().performMicrotaskCheckpoint();
318
319 // Drive RunLoop until we get either of "Worker is terminated", "Loading is done", or "Loading is failed".
320 WorkerRunLoop& runLoop = m_globalScope->workerOrWorkletThread()->runLoop();
321
322 // We do not want to receive messages that are not related to asynchronous resource loading.
323 // Otherwise, a worker discards some messages from the main thread here in a racy way.
324 // For example, the main thread can postMessage just after creating a Worker. In that case, postMessage's
325 // task is queued in WorkerRunLoop before start running module scripts. This task should not be discarded
326 // in the following driving of the RunLoop which mainly attempt to collect initial load of module scripts.
327 String taskMode = "loadModulesInWorkerOrWorkletMode"_s;
328 MessageQueueWaitResult result = MessageQueueMessageReceived;
329 while ((!protector->isLoaded() && !protector->wasCanceled()) && result != MessageQueueTerminated) {
330 result = runLoop.runInMode(m_globalScope, taskMode);
331 if (result != MessageQueueTerminated)
332 m_globalScope->eventLoop().performMicrotaskCheckpoint();
333 }
334
335 return result;
336}
337
338void WorkerOrWorkletScriptController::linkAndEvaluateModule(WorkerScriptFetcher& scriptFetcher, const ScriptSourceCode& sourceCode, String* returnedExceptionMessage)
339{
340 if (isExecutionForbidden())
341 return;
342
343 initScriptIfNeeded();
344
345 auto& globalObject = *m_globalScopeWrapper.get();
346 VM& vm = globalObject.vm();
347 JSLockHolder lock { vm };
348
349 NakedPtr<JSC::Exception> returnedException;
350 JSExecState::linkAndEvaluateModule(globalObject, Identifier::fromUid(vm, scriptFetcher.moduleKey()), jsUndefined(), returnedException);
351 if ((returnedException && isTerminatedExecutionException(vm, returnedException)) || isTerminatingExecution()) {
352 forbidExecution();
353 return;
354 }
355
356 if (returnedException) {
357 if (m_globalScope->canIncludeErrorDetails(sourceCode.cachedScript(), sourceCode.url().string())) {
358 // FIXME: It's not great that this can run arbitrary code to string-ify the value of the exception.
359 // Do we need to do anything to handle that properly, if it, say, raises another exception?
360 if (returnedExceptionMessage)
361 *returnedExceptionMessage = returnedException->value().toWTFString(&globalObject);
362 } else {
363 // Overwrite the detailed error with a generic error.
364 String genericErrorMessage { "Script error."_s };
365 if (returnedExceptionMessage)
366 *returnedExceptionMessage = genericErrorMessage;
367 }
368 }
369}
370
371void WorkerOrWorkletScriptController::loadAndEvaluateModule(const URL& moduleURL, FetchOptions::Credentials credentials, CompletionHandler<void(Optional<Exception>&&)>&& completionHandler)
372{
373 if (isExecutionForbidden()) {
374 completionHandler(Exception { NotAllowedError });
375 return;
376 }
377
378 initScriptIfNeeded();
379
380 auto& globalObject = *m_globalScopeWrapper.get();
381 VM& vm = globalObject.vm();
382 JSLockHolder lock { vm };
383
384 auto scriptFetcher = WorkerScriptFetcher::create(credentials);
385 {
386 auto& promise = JSExecState::loadModule(globalObject, moduleURL.string(), jsUndefined(), JSC::JSScriptFetcher::create(vm, { scriptFetcher.ptr() }));
387
388 auto task = createSharedTask<void(Optional<Exception>&&)>([completionHandler = WTFMove(completionHandler)](Optional<Exception>&& exception) mutable {
389 completionHandler(WTFMove(exception));
390 });
391
392 auto& fulfillHandler = *JSNativeStdFunction::create(vm, &globalObject, 1, String(), [task, scriptFetcher](JSGlobalObject* globalObject, CallFrame* callFrame) -> JSC::EncodedJSValue {
393 // task->run(WTF::nullopt);
394 VM& vm = globalObject->vm();
395 JSLockHolder lock { vm };
396 auto scope = DECLARE_THROW_SCOPE(vm);
397
398 Identifier moduleKey = jsValueToModuleKey(globalObject, callFrame->argument(0));
399 RETURN_IF_EXCEPTION(scope, { });
400 scriptFetcher->notifyLoadCompleted(*moduleKey.impl());
401
402 auto* context = downcast<WorkerOrWorkletGlobalScope>(jsCast<JSDOMGlobalObject*>(globalObject)->scriptExecutionContext());
403 if (!context || !context->script()) {
404 task->run(WTF::nullopt);
405 return JSValue::encode(jsUndefined());
406 }
407
408 NakedPtr<JSC::Exception> returnedException;
409 JSExecState::linkAndEvaluateModule(*globalObject, moduleKey, jsUndefined(), returnedException);
410 if ((returnedException && isTerminatedExecutionException(vm, returnedException)) || context->script()->isTerminatingExecution()) {
411 if (context->script())
412 context->script()->forbidExecution();
413 task->run(WTF::nullopt);
414 return JSValue::encode(jsUndefined());
415 }
416
417 if (returnedException) {
418 String message;
419 if (context->canIncludeErrorDetails(nullptr, moduleKey.string())) {
420 // FIXME: It's not great that this can run arbitrary code to string-ify the value of the exception.
421 // Do we need to do anything to handle that properly, if it, say, raises another exception?
422 message = returnedException->value().toWTFString(globalObject);
423 } else {
424 // Overwrite the detailed error with a generic error.
425 message = "Script error."_s;
426 }
427 context->reportException(message, { }, { }, { }, { }, { });
428 }
429
430 task->run(WTF::nullopt);
431 return JSValue::encode(jsUndefined());
432 });
433
434 auto& rejectHandler = *JSNativeStdFunction::create(vm, &globalObject, 1, String(), [task](JSGlobalObject* globalObject, CallFrame* callFrame) {
435 VM& vm = globalObject->vm();
436 JSLockHolder lock { vm };
437 JSValue errorValue = callFrame->argument(0);
438 if (errorValue.isObject()) {
439 auto* object = JSC::asObject(errorValue);
440 if (object->getDirect(vm, static_cast<JSVMClientData&>(*vm.clientData).builtinNames().failureKindPrivateName())) {
441 task->run(Exception { AbortError });
442 return JSValue::encode(jsUndefined());
443 }
444 if (object->inherits<ErrorInstance>(vm)) {
445 auto* error = jsCast<ErrorInstance*>(object);
446 switch (error->errorType()) {
447 case ErrorType::SyntaxError: {
448 auto catchScope = DECLARE_CATCH_SCOPE(vm);
449 String message = retrieveErrorMessageWithoutName(*globalObject, vm, errorValue, catchScope);
450 task->run(Exception { SyntaxError, message });
451 return JSValue::encode(jsUndefined());
452 }
453 default:
454 break;
455 }
456 }
457 }
458
459 auto catchScope = DECLARE_CATCH_SCOPE(vm);
460 String message = retrieveErrorMessageWithoutName(*globalObject, vm, errorValue, catchScope);
461 task->run(Exception { AbortError, message });
462 return JSValue::encode(jsUndefined());
463 });
464
465 promise.then(&globalObject, &fulfillHandler, &rejectHandler);
466 }
467 m_globalScope->eventLoop().performMicrotaskCheckpoint();
468}
469