WorkletModuleLoader.cpp (12038B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "WorkletModuleLoader.h" 8 9 #include "js/CompileOptions.h" // JS::InstantiateOptions 10 #include "js/experimental/JSStencil.h" // JS::CompileModuleScriptToStencil, JS::InstantiateModuleStencil 11 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 12 #include "js/loader/ModuleLoadRequest.h" 13 #include "mozilla/ScopeExit.h" 14 #include "mozilla/StaticPrefs_javascript.h" 15 #include "mozilla/dom/StructuredCloneHolder.h" 16 #include "mozilla/dom/Worklet.h" 17 #include "mozilla/dom/WorkletFetchHandler.h" 18 #include "nsStringBundle.h" 19 20 using JS::loader::ModuleLoadRequest; 21 using JS::loader::ResolveError; 22 23 namespace mozilla::dom::loader { 24 25 ////////////////////////////////////////////////////////////// 26 // WorkletScriptLoader 27 ////////////////////////////////////////////////////////////// 28 29 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkletScriptLoader) 30 NS_INTERFACE_MAP_END 31 32 NS_IMPL_CYCLE_COLLECTION(WorkletScriptLoader) 33 34 NS_IMPL_CYCLE_COLLECTING_ADDREF(WorkletScriptLoader) 35 NS_IMPL_CYCLE_COLLECTING_RELEASE(WorkletScriptLoader) 36 37 nsresult WorkletScriptLoader::FillCompileOptionsForRequest( 38 JSContext* cx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions, 39 JS::MutableHandle<JSScript*> aIntroductionScript) { 40 aOptions->setIntroductionType("Worklet"); 41 aOptions->setFileAndLine(aRequest->mURL.get(), 1); 42 aOptions->setIsRunOnce(true); 43 aOptions->setNoScriptRval(true); 44 return NS_OK; 45 } 46 47 ////////////////////////////////////////////////////////////// 48 // WorkletModuleLoader 49 ////////////////////////////////////////////////////////////// 50 51 NS_IMPL_ADDREF_INHERITED(WorkletModuleLoader, ModuleLoaderBase) 52 NS_IMPL_RELEASE_INHERITED(WorkletModuleLoader, ModuleLoaderBase) 53 54 NS_IMPL_CYCLE_COLLECTION_INHERITED(WorkletModuleLoader, ModuleLoaderBase, 55 mFetchingRequests) 56 57 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkletModuleLoader) 58 NS_INTERFACE_MAP_END_INHERITING(ModuleLoaderBase) 59 60 WorkletModuleLoader::WorkletModuleLoader(WorkletScriptLoader* aScriptLoader, 61 nsIGlobalObject* aGlobalObject) 62 : ModuleLoaderBase(aScriptLoader, aGlobalObject) { 63 // This should be constructed on a worklet thread. 64 MOZ_ASSERT(!NS_IsMainThread()); 65 } 66 67 already_AddRefed<ModuleLoadRequest> WorkletModuleLoader::CreateRequest( 68 JSContext* aCx, nsIURI* aURI, JS::Handle<JSObject*> aModuleRequest, 69 JS::Handle<JS::Value> aHostDefined, JS::Handle<JS::Value> aPayload, 70 bool aIsDynamicImport, ScriptFetchOptions* aOptions, 71 dom::ReferrerPolicy aReferrerPolicy, nsIURI* aBaseURL, 72 const dom::SRIMetadata& aSriMetadata) { 73 JS::ModuleType moduleType = GetModuleRequestType(aCx, aModuleRequest); 74 ModuleLoadRequest* root = nullptr; 75 MOZ_ASSERT(!aHostDefined.isUndefined()); 76 root = static_cast<ModuleLoadRequest*>(aHostDefined.toPrivate()); 77 MOZ_ASSERT(root); 78 WorkletLoadContext* context = root->mLoadContext->AsWorkletContext(); 79 const nsMainThreadPtrHandle<WorkletFetchHandler>& handlerRef = 80 context->GetHandlerRef(); 81 RefPtr<WorkletLoadContext> loadContext = new WorkletLoadContext(handlerRef); 82 RefPtr<ModuleLoadRequest> request = 83 new ModuleLoadRequest(moduleType, SRIMetadata(), aBaseURL, loadContext, 84 ModuleLoadRequest::Kind::StaticImport, this, root); 85 86 request->mURL = aURI->GetSpecOrDefault(); 87 request->NoCacheEntryFound(aReferrerPolicy, aOptions, aURI); 88 return request.forget(); 89 } 90 91 bool WorkletModuleLoader::CanStartLoad(ModuleLoadRequest* aRequest, 92 nsresult* aRvOut) { 93 return true; 94 } 95 96 nsresult WorkletModuleLoader::StartFetch(ModuleLoadRequest* aRequest) { 97 InsertRequest(aRequest->URI(), aRequest); 98 99 RefPtr<StartFetchRunnable> runnable = 100 new StartFetchRunnable(aRequest->GetWorkletLoadContext()->GetHandlerRef(), 101 aRequest->URI(), aRequest->mReferrer); 102 NS_DispatchToMainThread(runnable.forget()); 103 return NS_OK; 104 } 105 106 nsresult WorkletModuleLoader::CompileFetchedModule( 107 JSContext* aCx, JS::Handle<JSObject*> aGlobal, JS::CompileOptions& aOptions, 108 ModuleLoadRequest* aRequest, JS::MutableHandle<JSObject*> aModuleScript) { 109 switch (aRequest->mModuleType) { 110 case JS::ModuleType::Unknown: 111 case JS::ModuleType::Bytes: 112 MOZ_CRASH("Unexpected module type"); 113 case JS::ModuleType::JavaScriptOrWasm: 114 return CompileJavaScriptOrWasmModule(aCx, aOptions, aRequest, 115 aModuleScript); 116 case JS::ModuleType::JSON: 117 return CompileJsonModule(aCx, aOptions, aRequest, aModuleScript); 118 case JS::ModuleType::CSS: 119 MOZ_CRASH("CSS modules are not supported in worklets"); 120 } 121 122 MOZ_CRASH("Unhandled module type"); 123 } 124 125 nsresult WorkletModuleLoader::CompileJavaScriptOrWasmModule( 126 JSContext* aCx, JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest, 127 JS::MutableHandle<JSObject*> aModuleScript) { 128 MOZ_ASSERT(aRequest->IsTextSource()); 129 130 MaybeSourceText maybeSource; 131 nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource, 132 aRequest->mLoadContext.get()); 133 NS_ENSURE_SUCCESS(rv, rv); 134 135 #ifdef NIGHTLY_BUILD 136 if (aRequest->HasWasmMimeTypeEssence()) { 137 auto compile = [&](auto& source) { 138 return JS::CompileWasmModule(aCx, aOptions, source); 139 }; 140 141 auto* wasmModule = maybeSource.mapNonEmpty(compile); 142 if (!wasmModule) { 143 return NS_ERROR_FAILURE; 144 } 145 146 aModuleScript.set(wasmModule); 147 return NS_OK; 148 } 149 #endif 150 151 RefPtr<JS::Stencil> stencil; 152 153 auto compile = [&](auto& source) { 154 return JS::CompileModuleScriptToStencil(aCx, aOptions, source); 155 }; 156 stencil = maybeSource.mapNonEmpty(compile); 157 158 if (!stencil) { 159 return NS_ERROR_FAILURE; 160 } 161 162 JS::InstantiateOptions instantiateOptions(aOptions); 163 aModuleScript.set( 164 JS::InstantiateModuleStencil(aCx, instantiateOptions, stencil)); 165 return aModuleScript ? NS_OK : NS_ERROR_FAILURE; 166 } 167 168 nsresult WorkletModuleLoader::CompileJsonModule( 169 JSContext* aCx, JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest, 170 JS::MutableHandle<JSObject*> aModuleScript) { 171 MOZ_ASSERT(aRequest->IsTextSource()); 172 173 MaybeSourceText maybeSource; 174 nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource, 175 aRequest->mLoadContext.get()); 176 NS_ENSURE_SUCCESS(rv, rv); 177 178 auto compile = [&](auto& source) { 179 return JS::CompileJsonModule(aCx, aOptions, source); 180 }; 181 182 auto* jsonModule = maybeSource.mapNonEmpty(compile); 183 if (!jsonModule) { 184 return NS_ERROR_FAILURE; 185 } 186 187 aModuleScript.set(jsonModule); 188 return NS_OK; 189 } 190 191 // AddModuleResultRunnable is a Runnable which will notify the result of 192 // Worklet::AddModule on the main thread. 193 class AddModuleResultRunnable final : public Runnable { 194 public: 195 explicit AddModuleResultRunnable( 196 const nsMainThreadPtrHandle<WorkletFetchHandler>& aHandlerRef, 197 bool aSucceeded) 198 : Runnable("Worklet::AddModuleResultRunnable"), 199 mHandlerRef(aHandlerRef), 200 mSucceeded(aSucceeded) { 201 MOZ_ASSERT(!NS_IsMainThread()); 202 } 203 204 ~AddModuleResultRunnable() = default; 205 206 NS_IMETHOD 207 Run() override; 208 209 private: 210 nsMainThreadPtrHandle<WorkletFetchHandler> mHandlerRef; 211 bool mSucceeded; 212 }; 213 214 NS_IMETHODIMP 215 AddModuleResultRunnable::Run() { 216 MOZ_ASSERT(NS_IsMainThread()); 217 if (mSucceeded) { 218 mHandlerRef->ExecutionSucceeded(); 219 } else { 220 mHandlerRef->ExecutionFailed(); 221 } 222 223 return NS_OK; 224 } 225 226 class AddModuleThrowErrorRunnable final : public Runnable, 227 public StructuredCloneHolder { 228 public: 229 explicit AddModuleThrowErrorRunnable( 230 const nsMainThreadPtrHandle<WorkletFetchHandler>& aHandlerRef) 231 : Runnable("Worklet::AddModuleThrowErrorRunnable"), 232 StructuredCloneHolder(CloningSupported, TransferringNotSupported, 233 StructuredCloneScope::SameProcess), 234 mHandlerRef(aHandlerRef) { 235 MOZ_ASSERT(!NS_IsMainThread()); 236 } 237 238 ~AddModuleThrowErrorRunnable() = default; 239 240 NS_IMETHOD 241 Run() override; 242 243 private: 244 nsMainThreadPtrHandle<WorkletFetchHandler> mHandlerRef; 245 }; 246 247 NS_IMETHODIMP 248 AddModuleThrowErrorRunnable::Run() { 249 MOZ_ASSERT(NS_IsMainThread()); 250 251 nsCOMPtr<nsIGlobalObject> global = 252 do_QueryInterface(mHandlerRef->mWorklet->GetParentObject()); 253 MOZ_ASSERT(global); 254 255 AutoJSAPI jsapi; 256 if (NS_WARN_IF(!jsapi.Init(global))) { 257 mHandlerRef->ExecutionFailed(); 258 return NS_ERROR_FAILURE; 259 } 260 261 JSContext* cx = jsapi.cx(); 262 JS::Rooted<JS::Value> error(cx); 263 ErrorResult result; 264 Read(global, cx, &error, result); 265 (void)NS_WARN_IF(result.Failed()); 266 mHandlerRef->ExecutionFailed(error); 267 268 return NS_OK; 269 } 270 271 void WorkletModuleLoader::OnModuleLoadComplete(ModuleLoadRequest* aRequest) { 272 if (aRequest->IsStaticImport()) { 273 return; 274 } 275 276 const nsMainThreadPtrHandle<WorkletFetchHandler>& handlerRef = 277 aRequest->GetWorkletLoadContext()->GetHandlerRef(); 278 279 auto addModuleFailed = MakeScopeExit([&] { 280 RefPtr<AddModuleResultRunnable> runnable = 281 new AddModuleResultRunnable(handlerRef, false); 282 NS_DispatchToMainThread(runnable.forget()); 283 }); 284 285 if (!aRequest->mModuleScript) { 286 return; 287 } 288 289 bool hasParseError = aRequest->mModuleScript->HasParseError(); 290 bool hasError = aRequest->mModuleScript->HasErrorToRethrow(); 291 292 if (!hasParseError && !hasError) { 293 if (!aRequest->InstantiateModuleGraph()) { 294 return; 295 } 296 297 nsresult rv = aRequest->EvaluateModule(); 298 if (NS_FAILED(rv)) { 299 return; 300 } 301 302 hasError = aRequest->mModuleScript->HasErrorToRethrow(); 303 } 304 305 if (hasParseError || hasError) { 306 AutoJSAPI jsapi; 307 if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) { 308 return; 309 } 310 311 JSContext* cx = jsapi.cx(); 312 JS::Rooted<JS::Value> error(cx); 313 if (hasParseError) { 314 error = aRequest->mModuleScript->ParseError(); 315 } else { 316 error = aRequest->mModuleScript->ErrorToRethrow(); 317 } 318 JS_SetPendingException(cx, error); 319 RefPtr<AddModuleThrowErrorRunnable> runnable = 320 new AddModuleThrowErrorRunnable(handlerRef); 321 ErrorResult result; 322 runnable->Write(cx, error, result); 323 if (NS_WARN_IF(result.Failed())) { 324 return; 325 } 326 327 addModuleFailed.release(); 328 NS_DispatchToMainThread(runnable.forget()); 329 return; 330 } 331 332 addModuleFailed.release(); 333 RefPtr<AddModuleResultRunnable> runnable = 334 new AddModuleResultRunnable(handlerRef, true); 335 NS_DispatchToMainThread(runnable.forget()); 336 } 337 338 // TODO: Bug 1808301: Call FormatLocalizedString from a worklet thread. 339 nsresult WorkletModuleLoader::GetResolveFailureMessage( 340 ResolveError aError, const nsAString& aSpecifier, nsAString& aResult) { 341 uint8_t index = static_cast<uint8_t>(aError); 342 MOZ_ASSERT(index < static_cast<uint8_t>(ResolveError::Length)); 343 MOZ_ASSERT(mLocalizedStrs); 344 MOZ_ASSERT(!mLocalizedStrs->IsEmpty()); 345 if (!mLocalizedStrs || NS_WARN_IF(mLocalizedStrs->IsEmpty())) { 346 return NS_ERROR_FAILURE; 347 } 348 349 const nsString& localizedStr = mLocalizedStrs->ElementAt(index); 350 351 AutoTArray<nsString, 1> params; 352 params.AppendElement(aSpecifier); 353 354 nsStringBundleBase::FormatString(localizedStr.get(), params, aResult); 355 return NS_OK; 356 } 357 358 void WorkletModuleLoader::InsertRequest(nsIURI* aURI, 359 ModuleLoadRequest* aRequest) { 360 mFetchingRequests.InsertOrUpdate(aURI, aRequest); 361 } 362 363 void WorkletModuleLoader::RemoveRequest(nsIURI* aURI) { 364 MOZ_ASSERT(mFetchingRequests.Remove(aURI)); 365 } 366 367 ModuleLoadRequest* WorkletModuleLoader::GetRequest(nsIURI* aURI) const { 368 RefPtr<ModuleLoadRequest> req; 369 MOZ_ALWAYS_TRUE(mFetchingRequests.Get(aURI, getter_AddRefs(req))); 370 return req; 371 } 372 373 } // namespace mozilla::dom::loader