ModuleLoader.cpp (19675B)
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 "ModuleLoader.h" 8 9 #include "GeckoProfiler.h" 10 #include "ScriptLoader.h" 11 #include "js/CompileOptions.h" // JS::CompileOptions, JS::InstantiateOptions 12 #include "js/ContextOptions.h" // JS::ContextOptionsRef 13 #include "js/MemoryFunctions.h" 14 #include "js/Modules.h" // JS::FinishDynamicModuleImport, JS::{G,S}etModuleResolveHook, JS::Get{ModulePrivate,ModuleScript,RequestedModule{s,Specifier,SourcePos}}, JS::SetModule{DynamicImport,Metadata}Hook 15 #include "js/PropertyAndElement.h" // JS_DefineProperty 16 #include "js/Realm.h" 17 #include "js/SourceText.h" 18 #include "js/experimental/JSStencil.h" // JS::Stencil, JS::CompileModuleScriptToStencil, JS::InstantiateModuleStencil 19 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 20 #include "js/loader/LoadedScript.h" 21 #include "js/loader/ModuleLoadRequest.h" 22 #include "js/loader/ModuleLoaderBase.h" 23 #include "js/loader/ScriptLoadRequest.h" 24 #include "jsapi.h" 25 #include "mozilla/Assertions.h" 26 #include "mozilla/CycleCollectedJSContext.h" 27 #include "mozilla/LoadInfo.h" 28 #include "mozilla/Maybe.h" 29 #include "mozilla/StyleSheet.h" 30 #include "mozilla/StyleSheetInlines.h" 31 #include "mozilla/dom/AutoEntryScript.h" 32 #include "mozilla/dom/Document.h" 33 #include "mozilla/dom/Element.h" 34 #include "mozilla/dom/RequestBinding.h" 35 #include "nsContentSecurityManager.h" 36 #include "nsError.h" 37 #include "nsIContent.h" 38 #include "nsIPrincipal.h" 39 #include "nsJSUtils.h" 40 #include "xpcpublic.h" 41 42 using JS::SourceText; 43 using namespace JS::loader; 44 45 namespace mozilla::dom { 46 47 #undef LOG 48 #define LOG(args) \ 49 MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args) 50 51 #define LOG_ENABLED() \ 52 MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug) 53 54 ////////////////////////////////////////////////////////////// 55 // DOM module loader 56 ////////////////////////////////////////////////////////////// 57 58 ModuleLoader::ModuleLoader(ScriptLoader* aLoader, 59 nsIGlobalObject* aGlobalObject, Kind aKind) 60 : ModuleLoaderBase(aLoader, aGlobalObject), mKind(aKind) {} 61 62 ScriptLoader* ModuleLoader::GetScriptLoader() { 63 return static_cast<ScriptLoader*>(mLoader.get()); 64 } 65 66 bool ModuleLoader::CanStartLoad(ModuleLoadRequest* aRequest, nsresult* aRvOut) { 67 if (!GetScriptLoader()->GetDocument()) { 68 *aRvOut = NS_ERROR_NULL_POINTER; 69 return false; 70 } 71 72 nsCOMPtr<nsIPrincipal> principal = aRequest->TriggeringPrincipal(); 73 if (BasePrincipal::Cast(principal)->ContentScriptAddonPolicy()) { 74 // To prevent dynamic code execution, content scripts can only 75 // load moz-extension URLs. 76 if (!aRequest->URI()->SchemeIs("moz-extension")) { 77 *aRvOut = NS_ERROR_DOM_WEBEXT_CONTENT_SCRIPT_URI; 78 return false; 79 } 80 } else { 81 // If this document is sandboxed without 'allow-scripts', abort. 82 if (GetScriptLoader()->GetDocument()->HasScriptsBlockedBySandbox()) { 83 *aRvOut = NS_ERROR_CONTENT_BLOCKED; 84 return false; 85 } 86 } 87 88 if (LOG_ENABLED()) { 89 nsAutoCString url; 90 aRequest->URI()->GetAsciiSpec(url); 91 LOG(("ScriptLoadRequest (%p): Start Module Load (url = %s)", aRequest, 92 url.get())); 93 } 94 95 return true; 96 } 97 98 nsresult ModuleLoader::StartFetch(ModuleLoadRequest* aRequest) { 99 if (aRequest->IsCachedStencil()) { 100 GetScriptLoader()->EmulateNetworkEvents(aRequest); 101 SetModuleFetchStarted(aRequest); 102 return aRequest->OnFetchComplete(NS_OK); 103 } 104 105 // According to the spec, module scripts have different behaviour to classic 106 // scripts and always use CORS. Only exception: Non linkable about: pages 107 // which load local module scripts. 108 bool isAboutPageLoadingChromeURI = ScriptLoader::IsAboutPageLoadingChromeURI( 109 aRequest, GetScriptLoader()->GetDocument()); 110 111 nsContentSecurityManager::CORSSecurityMapping corsMapping = 112 isAboutPageLoadingChromeURI 113 ? nsContentSecurityManager::CORSSecurityMapping::DISABLE_CORS_CHECKS 114 : nsContentSecurityManager::CORSSecurityMapping::REQUIRE_CORS_CHECKS; 115 116 nsSecurityFlags securityFlags = 117 nsContentSecurityManager::ComputeSecurityFlags(aRequest->CORSMode(), 118 corsMapping); 119 120 securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME; 121 122 // Delegate Shared Behavior to base ScriptLoader 123 // 124 // aCharsetForPreload is passed as Nothing() because this is not a preload 125 // and `StartLoadInternal` is able to find the charset by using `aRequest` 126 // for this case. 127 nsresult rv = GetScriptLoader()->StartLoadInternal( 128 aRequest, securityFlags, Nothing() /* aCharsetForPreload */); 129 NS_ENSURE_SUCCESS(rv, rv); 130 131 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-import()-module-script-graph 132 // Step 1. Disallow further import maps given settings object. 133 if (!aRequest->GetScriptLoadContext()->IsPreload()) { 134 LOG(("ScriptLoadRequest (%p): Disallow further import maps.", aRequest)); 135 DisallowImportMaps(); 136 } 137 138 LOG(("ScriptLoadRequest (%p): Start fetching module", aRequest)); 139 140 return NS_OK; 141 } 142 143 void ModuleLoader::AsyncExecuteInlineModule(ModuleLoadRequest* aRequest) { 144 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread( 145 mozilla::NewRunnableMethod<RefPtr<ModuleLoadRequest>>( 146 "ModuleLoader::ExecuteInlineModule", this, 147 &ModuleLoader::ExecuteInlineModule, aRequest))); 148 } 149 150 void ModuleLoader::ExecuteInlineModule(ModuleLoadRequest* aRequest) { 151 MOZ_ASSERT(aRequest->IsFinished()); 152 MOZ_ASSERT(aRequest->IsTopLevel()); 153 MOZ_ASSERT(aRequest->GetScriptLoadContext()->mIsInline); 154 155 if (aRequest->GetScriptLoadContext()->GetParserCreated() == NOT_FROM_PARSER) { 156 GetScriptLoader()->RunScriptWhenSafe(aRequest); 157 } else { 158 GetScriptLoader()->MaybeMoveToLoadedList(aRequest); 159 GetScriptLoader()->ProcessPendingRequests(); 160 } 161 162 aRequest->GetScriptLoadContext()->MaybeUnblockOnload(); 163 } 164 165 void ModuleLoader::OnModuleLoadComplete(ModuleLoadRequest* aRequest) { 166 MOZ_ASSERT(aRequest->IsFinished()); 167 168 if (aRequest->IsTopLevel() || aRequest->IsDynamicImport()) { 169 if (aRequest->GetScriptLoadContext()->mIsInline && 170 aRequest->GetScriptLoadContext()->GetParserCreated() == 171 NOT_FROM_PARSER) { 172 // https://html.spec.whatwg.org/#prepare-the-script-element 173 // Step 32.2. 174 // type: "module": 175 // 3.1. Queue an element task on the networking task source given 176 // el to perform the following steps: 177 // 1. Mark as ready el given result. 178 // 179 // Step 33. If ... el's type is "module": 180 // ... 181 // 3. Otherwise, if el is not parser-inserted: 182 // 3. Set el's steps to run when the result is ready to the 183 // following: 184 // ... 185 // 2.1. Execute the script element scripts[0]. 186 AsyncExecuteInlineModule(aRequest); 187 return; 188 } else if (aRequest->GetScriptLoadContext()->mIsInline && 189 aRequest->GetScriptLoadContext()->GetParserCreated() != 190 NOT_FROM_PARSER && 191 !nsContentUtils::IsSafeToRunScript()) { 192 // Avoid giving inline async module scripts that don't have 193 // external dependencies a guaranteed execution time relative 194 // to the HTML parse. That is, deliberately avoid guaranteeing 195 // that the script would always observe a DOM shape where the 196 // parser has not added further elements to the DOM. 197 // (If `nsContentUtils::IsSafeToRunScript()` returns `true`, 198 // we come here synchronously from the parser. If it returns 199 // `false` we come here from an external dependency completing 200 // its fetch, in which case we already are at an unspecific 201 // point relative to the parse.) 202 AsyncExecuteInlineModule(aRequest); 203 return; 204 } else { 205 GetScriptLoader()->MaybeMoveToLoadedList(aRequest); 206 GetScriptLoader()->ProcessPendingRequestsAsync(); 207 } 208 } 209 210 aRequest->GetScriptLoadContext()->MaybeUnblockOnload(); 211 } 212 213 nsresult ModuleLoader::CompileFetchedModule( 214 JSContext* aCx, JS::Handle<JSObject*> aGlobal, JS::CompileOptions& aOptions, 215 ModuleLoadRequest* aRequest, JS::MutableHandle<JSObject*> aModuleOut) { 216 if (!nsJSUtils::IsScriptable(aGlobal)) { 217 return NS_ERROR_FAILURE; 218 } 219 220 switch (aRequest->mModuleType) { 221 case JS::ModuleType::Unknown: 222 MOZ_CRASH("Unexpected module type"); 223 case JS::ModuleType::JavaScriptOrWasm: 224 return CompileJavaScriptOrWasmModule(aCx, aOptions, aRequest, aModuleOut); 225 case JS::ModuleType::JSON: 226 return CompileJsonModule(aCx, aOptions, aRequest, aModuleOut); 227 case JS::ModuleType::CSS: 228 return CompileCssModule(aCx, aOptions, aRequest, aModuleOut); 229 case JS::ModuleType::Bytes: 230 MOZ_CRASH("Unexpected module type"); 231 } 232 233 MOZ_CRASH("Unhandled module type"); 234 } 235 236 nsresult ModuleLoader::CompileJavaScriptOrWasmModule( 237 JSContext* aCx, JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest, 238 JS::MutableHandle<JSObject*> aModuleOut) { 239 GetScriptLoader()->CalculateCacheFlag(aRequest); 240 241 #ifdef NIGHTLY_BUILD 242 if (aRequest->HasWasmMimeTypeEssence()) { 243 MOZ_ASSERT(aRequest->IsTextSource()); 244 245 ModuleLoader::MaybeSourceText maybeSource; 246 nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource, 247 aRequest->mLoadContext.get()); 248 NS_ENSURE_SUCCESS(rv, rv); 249 250 auto compile = [&](auto& source) { 251 return JS::CompileWasmModule(aCx, aOptions, source); 252 }; 253 254 auto* wasmModule = maybeSource.mapNonEmpty(compile); 255 if (!wasmModule) { 256 return NS_ERROR_FAILURE; 257 } 258 259 aModuleOut.set(wasmModule); 260 return NS_OK; 261 } 262 #endif 263 264 if (aRequest->IsCachedStencil()) { 265 JS::InstantiateOptions instantiateOptions(aOptions); 266 RefPtr<JS::Stencil> stencil = aRequest->GetStencil(); 267 aModuleOut.set( 268 JS::InstantiateModuleStencil(aCx, instantiateOptions, stencil)); 269 if (!aModuleOut) { 270 return NS_ERROR_FAILURE; 271 } 272 273 bool alreadyStarted; 274 if (!JS::StartCollectingDelazifications(aCx, aModuleOut, stencil, 275 alreadyStarted)) { 276 return NS_ERROR_FAILURE; 277 } 278 (void)alreadyStarted; 279 280 return NS_OK; 281 } 282 283 if (aRequest->GetScriptLoadContext()->mWasCompiledOMT) { 284 JS::InstantiationStorage storage; 285 RefPtr<JS::Stencil> stencil = 286 aRequest->GetScriptLoadContext()->StealOffThreadResult(aCx, &storage); 287 if (!stencil) { 288 return NS_ERROR_FAILURE; 289 } 290 291 aRequest->SetStencil(stencil); 292 293 JS::InstantiateOptions instantiateOptions(aOptions); 294 aModuleOut.set(JS::InstantiateModuleStencil(aCx, instantiateOptions, 295 stencil, &storage)); 296 if (!aModuleOut) { 297 return NS_ERROR_FAILURE; 298 } 299 300 if (aRequest->PassedConditionForEitherCache()) { 301 bool alreadyStarted; 302 if (!JS::StartCollectingDelazifications(aCx, aModuleOut, stencil, 303 alreadyStarted)) { 304 return NS_ERROR_FAILURE; 305 } 306 MOZ_ASSERT(!alreadyStarted); 307 } 308 309 GetScriptLoader()->TryCacheRequest(aRequest); 310 311 return NS_OK; 312 } 313 314 RefPtr<JS::Stencil> stencil; 315 if (aRequest->IsTextSource()) { 316 MaybeSourceText maybeSource; 317 nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource, 318 aRequest->mLoadContext.get()); 319 NS_ENSURE_SUCCESS(rv, rv); 320 321 auto compile = [&](auto& source) { 322 return JS::CompileModuleScriptToStencil(aCx, aOptions, source); 323 }; 324 stencil = maybeSource.mapNonEmpty(compile); 325 } else { 326 MOZ_ASSERT(aRequest->IsSerializedStencil()); 327 JS::DecodeOptions decodeOptions(aOptions); 328 decodeOptions.borrowBuffer = true; 329 330 JS::TranscodeRange range = aRequest->SerializedStencil(); 331 JS::TranscodeResult tr = 332 JS::DecodeStencil(aCx, decodeOptions, range, getter_AddRefs(stencil)); 333 if (tr != JS::TranscodeResult::Ok) { 334 return NS_ERROR_DOM_JS_DECODING_ERROR; 335 } 336 } 337 338 if (!stencil) { 339 return NS_ERROR_FAILURE; 340 } 341 342 aRequest->SetStencil(stencil); 343 344 JS::InstantiateOptions instantiateOptions(aOptions); 345 aModuleOut.set( 346 JS::InstantiateModuleStencil(aCx, instantiateOptions, stencil)); 347 if (!aModuleOut) { 348 return NS_ERROR_FAILURE; 349 } 350 351 if (aRequest->PassedConditionForEitherCache()) { 352 bool alreadyStarted; 353 if (!JS::StartCollectingDelazifications(aCx, aModuleOut, stencil, 354 alreadyStarted)) { 355 return NS_ERROR_FAILURE; 356 } 357 MOZ_ASSERT(!alreadyStarted); 358 } 359 360 GetScriptLoader()->TryCacheRequest(aRequest); 361 362 return NS_OK; 363 } 364 365 nsresult ModuleLoader::CompileJsonModule( 366 JSContext* aCx, JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest, 367 JS::MutableHandle<JSObject*> aModuleOut) { 368 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mWasCompiledOMT); 369 370 MOZ_ASSERT(aRequest->IsTextSource()); 371 ModuleLoader::MaybeSourceText maybeSource; 372 nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource, 373 aRequest->mLoadContext.get()); 374 NS_ENSURE_SUCCESS(rv, rv); 375 376 auto compile = [&](auto& source) { 377 return JS::CompileJsonModule(aCx, aOptions, source); 378 }; 379 380 auto* jsonModule = maybeSource.mapNonEmpty(compile); 381 if (!jsonModule) { 382 return NS_ERROR_FAILURE; 383 } 384 385 aModuleOut.set(jsonModule); 386 return NS_OK; 387 } 388 389 nsresult ModuleLoader::CompileCssModule( 390 JSContext* aCx, JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest, 391 JS::MutableHandle<JSObject*> aModuleOut) { 392 MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mWasCompiledOMT); 393 MOZ_ASSERT(mozilla::StaticPrefs::layout_css_module_scripts_enabled()); 394 395 MOZ_ASSERT(aRequest->IsTextSource()); 396 ModuleLoader::MaybeSourceText maybeSource; 397 nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource, 398 aRequest->mLoadContext.get()); 399 NS_ENSURE_SUCCESS(rv, rv); 400 401 // https://html.spec.whatwg.org/#creating-a-css-module-script 402 JS::Rooted<JSObject*> cssModule(aCx, nullptr); 403 ErrorResult error; 404 auto compile = [&](auto& source) { 405 using T = decltype(source); 406 static_assert(std::is_same_v<T, JS::SourceText<char16_t>&> || 407 std::is_same_v<T, JS::SourceText<Utf8Unit>&>); 408 409 nsCOMPtr<nsPIDOMWindowInner> window = 410 do_QueryInterface(aRequest->GetGlobalObject()); 411 if (!window) { 412 error.ThrowNotSupportedError("Not supported when there is no document"); 413 return; 414 } 415 416 Document* constructorDocument = window->GetExtantDoc(); 417 if (!constructorDocument) { 418 error.ThrowNotSupportedError("Not supported when there is no document"); 419 return; 420 } 421 422 // 5. Let sheet be the result of running the steps to create a constructed 423 // CSSStyleSheet 424 // with an empty dictionary as the argument. 425 // Note that according to the specification, the baseURL should be the 426 // baseURL of the document, but that doesn't seem correct (see 427 // https://github.com/whatwg/html/issues/11629). 428 dom::CSSStyleSheetInit options; 429 RefPtr<StyleSheet> sheet = StyleSheet::CreateConstructedSheet( 430 *constructorDocument, aRequest->BaseURL(), options, error); 431 if (error.Failed()) { 432 return; 433 } 434 435 // 6. Run the steps to synchronously replace the rules of a CSSStyleSheet on 436 // sheet given source. Ideally we wouldn't run this on the main thread for 437 // large scripts, see https://bugzilla.mozilla.org/show_bug.cgi?id=1987143. 438 if constexpr (std::is_same_v<T, JS::SourceText<mozilla::Utf8Unit>&>) { 439 nsDependentCSubstring text(source.get(), source.length()); 440 sheet->ReplaceSync(text, error); 441 } else if constexpr (std::is_same_v<T, JS::SourceText<char16_t>&>) { 442 nsDependentSubstring text(source.get(), source.length()); 443 sheet->ReplaceSync(NS_ConvertUTF16toUTF8(text), error); 444 } 445 if (error.Failed()) { 446 return; 447 } 448 449 JS::Rooted<JS::Value> val(aCx, JS::NullValue()); 450 if (!GetOrCreateDOMReflector(aCx, sheet, &val) || !val.isObject()) { 451 if (!JS_IsExceptionPending(aCx)) { 452 error.ThrowUnknownError("Internal error"); 453 } 454 return; 455 } 456 457 // Steps. 1 - 4 (re-ordered), 7, 8 458 cssModule.set(JS::CreateDefaultExportSyntheticModule(aCx, val)); 459 }; 460 461 maybeSource.mapNonEmpty(compile); 462 if (!cssModule) { 463 if (error.Failed()) { 464 MOZ_ALWAYS_TRUE(error.MaybeSetPendingException(aCx)); 465 } 466 return NS_ERROR_FAILURE; 467 } 468 469 aModuleOut.set(cssModule); 470 return NS_OK; 471 } 472 473 already_AddRefed<ModuleLoadRequest> ModuleLoader::CreateTopLevel( 474 nsIURI* aURI, nsIScriptElement* aElement, ReferrerPolicy aReferrerPolicy, 475 ScriptFetchOptions* aFetchOptions, const SRIMetadata& aIntegrity, 476 nsIURI* aReferrer, ScriptLoadContext* aContext, 477 ScriptLoadRequestType aRequestType) { 478 RefPtr<ModuleLoadRequest> request = new ModuleLoadRequest( 479 JS::ModuleType::JavaScript, aIntegrity, aReferrer, aContext, 480 ModuleLoadRequest::Kind::TopLevel, this, nullptr); 481 482 GetScriptLoader()->TryUseCache(aReferrerPolicy, aFetchOptions, aURI, request, 483 aElement, aFetchOptions->mNonce, aRequestType); 484 485 return request.forget(); 486 } 487 488 already_AddRefed<ModuleLoadRequest> ModuleLoader::CreateRequest( 489 JSContext* aCx, nsIURI* aURI, JS::Handle<JSObject*> aModuleRequest, 490 JS::Handle<JS::Value> aHostDefined, JS::Handle<JS::Value> aPayload, 491 bool aIsDynamicImport, ScriptFetchOptions* aOptions, 492 ReferrerPolicy aReferrerPolicy, nsIURI* aBaseURL, 493 const SRIMetadata& aSriMetadata) { 494 RefPtr<ScriptLoadContext> context = new ScriptLoadContext(); 495 context->mIsInline = false; 496 ModuleLoadRequest::Kind kind; 497 ModuleLoadRequest* root = nullptr; 498 if (aIsDynamicImport) { 499 context->mScriptMode = ScriptLoadContext::ScriptMode::eAsync; 500 kind = ModuleLoadRequest::Kind::DynamicImport; 501 } else { 502 MOZ_ASSERT(!aHostDefined.isUndefined()); 503 root = static_cast<ModuleLoadRequest*>(aHostDefined.toPrivate()); 504 MOZ_ASSERT(root); 505 LoadContextBase* loadContext = root->mLoadContext; 506 context->mScriptMode = loadContext->AsWindowContext()->mScriptMode; 507 kind = ModuleLoadRequest::Kind::StaticImport; 508 } 509 510 JS::ModuleType moduleType = GetModuleRequestType(aCx, aModuleRequest); 511 RefPtr<ModuleLoadRequest> request = new ModuleLoadRequest( 512 moduleType, aSriMetadata, aBaseURL, context, kind, this, root); 513 514 GetScriptLoader()->TryUseCache(aReferrerPolicy, aOptions, aURI, request); 515 516 return request.forget(); 517 } 518 519 already_AddRefed<ScriptFetchOptions> 520 ModuleLoader::CreateDefaultScriptFetchOptions() { 521 RefPtr<ScriptFetchOptions> options = ScriptFetchOptions::CreateDefault(); 522 nsCOMPtr<nsIPrincipal> principal = GetGlobalObject()->PrincipalOrNull(); 523 options->SetTriggeringPrincipal(principal); 524 return options.forget(); 525 } 526 527 nsIURI* ModuleLoader::GetClientReferrerURI() { 528 Document* document = GetScriptLoader()->GetDocument(); 529 #ifdef DEBUG 530 nsCOMPtr<nsIPrincipal> principal = GetGlobalObject()->PrincipalOrNull(); 531 #endif // DEBUG 532 MOZ_ASSERT_IF(GetKind() == WebExtension, 533 BasePrincipal::Cast(principal)->ContentScriptAddonPolicy()); 534 MOZ_ASSERT_IF(GetKind() == Normal, principal == document->NodePrincipal()); 535 536 return document->GetDocBaseURI(); 537 } 538 539 ModuleLoader::~ModuleLoader() { 540 LOG(("ModuleLoader::~ModuleLoader %p", this)); 541 mLoader = nullptr; 542 } 543 544 #undef LOG 545 #undef LOG_ENABLED 546 547 } // namespace mozilla::dom