LoadedScript.cpp (15117B)
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 "LoadedScript.h" 8 9 #include "mozilla/AlreadyAddRefed.h" // already_AddRefed 10 #include "mozilla/HoldDropJSObjects.h" 11 #include "mozilla/RefPtr.h" // RefPtr, mozilla::MakeRefPtr 12 #include "mozilla/UniquePtr.h" // mozilla::UniquePtr 13 14 #include "mozilla/dom/ScriptLoadContext.h" // ScriptLoadContext 15 #include "jsfriendapi.h" 16 #include "js/Modules.h" // JS::{Get,Set}ModulePrivate 17 #include "js/experimental/JSStencil.h" // JS::SizeOfStencil 18 #include "LoadContextBase.h" // LoadContextBase 19 #include "nsIChannel.h" // nsIChannel 20 21 namespace JS::loader { 22 23 ////////////////////////////////////////////////////////////// 24 // LoadedScript 25 ////////////////////////////////////////////////////////////// 26 27 MOZ_DEFINE_MALLOC_SIZE_OF(LoadedScriptMallocSizeOf) 28 29 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LoadedScript) 30 NS_INTERFACE_MAP_ENTRY(nsISupports) 31 NS_INTERFACE_MAP_END 32 33 // LoadedScript can be accessed from multiple threads. 34 // 35 // For instance, worker script loader passes the ScriptLoadRequest and 36 // the associated LoadedScript to the main thread to perform the actual load. 37 // Even while it's handled by the main thread, the LoadedScript is 38 // the target of the worker thread's cycle collector. 39 // 40 // Fields that can be modified by other threads shouldn't be touched by 41 // the cycle collection. 42 // 43 // NOTE: nsIURI doesn't have to be touched here because it cannot be a part 44 // of cycle. 45 NS_IMPL_CYCLE_COLLECTION(LoadedScript, mFetchOptions, mCacheEntry) 46 47 NS_IMPL_CYCLE_COLLECTING_ADDREF(LoadedScript) 48 NS_IMPL_CYCLE_COLLECTING_RELEASE(LoadedScript) 49 50 LoadedScript::LoadedScript(ScriptKind aKind, 51 mozilla::dom::ReferrerPolicy aReferrerPolicy, 52 ScriptFetchOptions* aFetchOptions, nsIURI* aURI) 53 : mDataType(DataType::eUnknown), 54 mKind(aKind), 55 mReferrerPolicy(aReferrerPolicy), 56 mSerializedStencilOffset(0), 57 mCacheEntryId(InvalidCacheEntryId), 58 mIsDirty(false), 59 mTookLongInPreviousRuns(false), 60 mFetchOptions(aFetchOptions), 61 mURI(aURI), 62 mReceivedScriptTextLength(0) { 63 MOZ_ASSERT(mFetchOptions); 64 MOZ_ASSERT(mURI); 65 } 66 67 LoadedScript::LoadedScript(const LoadedScript& aOther) 68 : mDataType(DataType::eCachedStencil), 69 mKind(aOther.mKind), 70 mReferrerPolicy(aOther.mReferrerPolicy), 71 mSerializedStencilOffset(0), 72 mCacheEntryId(aOther.mCacheEntryId), 73 mIsDirty(aOther.mIsDirty), 74 mTookLongInPreviousRuns(aOther.mTookLongInPreviousRuns), 75 mFetchOptions(aOther.mFetchOptions), 76 mURI(aOther.mURI), 77 mBaseURL(aOther.mBaseURL), 78 mReceivedScriptTextLength(0), 79 mStencil(aOther.mStencil) { 80 MOZ_ASSERT(mFetchOptions); 81 MOZ_ASSERT(mURI); 82 // NOTE: This is only for the cached stencil case. 83 // The script text and the serialized stencil are not reflected. 84 MOZ_DIAGNOSTIC_ASSERT(aOther.mDataType == DataType::eCachedStencil); 85 MOZ_DIAGNOSTIC_ASSERT(mStencil); 86 MOZ_ASSERT(!mScriptData); 87 MOZ_ASSERT(mSRIAndSerializedStencil.empty()); 88 } 89 90 LoadedScript::~LoadedScript() { 91 mozilla::UnregisterWeakMemoryReporter(this); 92 mozilla::DropJSObjects(this); 93 } 94 95 void LoadedScript::RegisterMemoryReport() { 96 mozilla::RegisterWeakMemoryReporter(this); 97 } 98 99 NS_IMETHODIMP 100 LoadedScript::CollectReports(nsIHandleReportCallback* aHandleReport, 101 nsISupports* aData, bool aAnonymize) { 102 #define COLLECT_REPORT(path, kind) \ 103 MOZ_COLLECT_REPORT(path, KIND_HEAP, UNITS_BYTES, \ 104 SizeOfIncludingThis(LoadedScriptMallocSizeOf), \ 105 "Memory used for LoadedScript to hold on " kind \ 106 " across documents") 107 108 switch (mKind) { 109 case ScriptKind::eClassic: 110 COLLECT_REPORT("explicit/js/script/loaded-script/classic", "scripts"); 111 break; 112 case ScriptKind::eImportMap: 113 COLLECT_REPORT("explicit/js/script/loaded-script/import-map", 114 "import-maps"); 115 break; 116 case ScriptKind::eModule: 117 COLLECT_REPORT("explicit/js/script/loaded-script/module", "modules"); 118 break; 119 case ScriptKind::eEvent: 120 COLLECT_REPORT("explicit/js/script/loaded-script/event", "event scripts"); 121 break; 122 } 123 124 #undef COLLECT_REPORT 125 return NS_OK; 126 } 127 128 size_t LoadedScript::SizeOfIncludingThis( 129 mozilla::MallocSizeOf aMallocSizeOf) const { 130 size_t bytes = aMallocSizeOf(this); 131 132 if (IsTextSource()) { 133 if (IsUTF16Text()) { 134 bytes += ScriptText<char16_t>().sizeOfExcludingThis(aMallocSizeOf); 135 } else { 136 bytes += ScriptText<Utf8Unit>().sizeOfExcludingThis(aMallocSizeOf); 137 } 138 } 139 140 bytes += mSRIAndSerializedStencil.sizeOfExcludingThis(aMallocSizeOf); 141 142 if (mStencil) { 143 bytes += JS::SizeOfStencil(mStencil, aMallocSizeOf); 144 } 145 146 return bytes; 147 } 148 149 void LoadedScript::AssociateWithScript(JSScript* aScript) { 150 // Verify that the rewritten URL is available when manipulating LoadedScript. 151 MOZ_ASSERT(mBaseURL); 152 153 // Set a JSScript's private value to point to this object. The JS engine will 154 // increment our reference count by calling HostAddRefTopLevelScript(). This 155 // is decremented by HostReleaseTopLevelScript() below when the JSScript dies. 156 157 MOZ_ASSERT(GetScriptPrivate(aScript).isUndefined()); 158 SetScriptPrivate(aScript, PrivateValue(this)); 159 } 160 161 nsresult LoadedScript::GetScriptSource(JSContext* aCx, 162 MaybeSourceText* aMaybeSource, 163 LoadContextBase* aMaybeLoadContext) { 164 // If there's no script text, we try to get it from the element 165 bool isWindowContext = 166 aMaybeLoadContext && aMaybeLoadContext->IsWindowContext(); 167 if (isWindowContext && aMaybeLoadContext->AsWindowContext()->mIsInline) { 168 nsAutoString inlineData; 169 auto* scriptLoadContext = aMaybeLoadContext->AsWindowContext(); 170 scriptLoadContext->GetInlineScriptText(inlineData); 171 172 size_t nbytes = inlineData.Length() * sizeof(char16_t); 173 UniqueTwoByteChars chars(static_cast<char16_t*>(JS_malloc(aCx, nbytes))); 174 if (!chars) { 175 return NS_ERROR_OUT_OF_MEMORY; 176 } 177 178 memcpy(chars.get(), inlineData.get(), nbytes); 179 180 SourceText<char16_t> srcBuf; 181 if (!srcBuf.init(aCx, std::move(chars), inlineData.Length())) { 182 return NS_ERROR_OUT_OF_MEMORY; 183 } 184 185 aMaybeSource->construct<SourceText<char16_t>>(std::move(srcBuf)); 186 return NS_OK; 187 } 188 189 size_t length = ScriptTextLength(); 190 if (IsUTF16Text()) { 191 UniqueTwoByteChars chars; 192 chars.reset(ScriptText<char16_t>().extractOrCopyRawBuffer()); 193 if (!chars) { 194 JS_ReportOutOfMemory(aCx); 195 return NS_ERROR_OUT_OF_MEMORY; 196 } 197 198 SourceText<char16_t> srcBuf; 199 if (!srcBuf.init(aCx, std::move(chars), length)) { 200 return NS_ERROR_OUT_OF_MEMORY; 201 } 202 203 aMaybeSource->construct<SourceText<char16_t>>(std::move(srcBuf)); 204 return NS_OK; 205 } 206 207 MOZ_ASSERT(IsUTF8Text()); 208 mozilla::UniquePtr<Utf8Unit[], FreePolicy> chars; 209 chars.reset(ScriptText<Utf8Unit>().extractOrCopyRawBuffer()); 210 if (!chars) { 211 JS_ReportOutOfMemory(aCx); 212 return NS_ERROR_OUT_OF_MEMORY; 213 } 214 215 SourceText<Utf8Unit> srcBuf; 216 if (!srcBuf.init(aCx, std::move(chars), length)) { 217 return NS_ERROR_OUT_OF_MEMORY; 218 } 219 220 aMaybeSource->construct<SourceText<Utf8Unit>>(std::move(srcBuf)); 221 return NS_OK; 222 } 223 224 static bool IsInternalURIScheme(nsIURI* uri) { 225 return uri->SchemeIs("moz-extension") || uri->SchemeIs("resource") || 226 uri->SchemeIs("moz-src") || uri->SchemeIs("chrome"); 227 } 228 229 void LoadedScript::SetBaseURLFromChannelAndOriginalURI(nsIChannel* aChannel, 230 nsIURI* aOriginalURI) { 231 // Fixup moz-extension: and resource: URIs, because the channel URI will 232 // point to file:, which won't be allowed to load. 233 if (aOriginalURI && IsInternalURIScheme(aOriginalURI)) { 234 mBaseURL = aOriginalURI; 235 } else { 236 aChannel->GetURI(getter_AddRefs(mBaseURL)); 237 } 238 } 239 240 inline void CheckModuleScriptPrivate(LoadedScript* script, 241 const Value& aPrivate) { 242 #ifdef DEBUG 243 if (script->IsModuleScript()) { 244 JSObject* module = script->AsModuleScript()->mModuleRecord.unbarrieredGet(); 245 MOZ_ASSERT_IF(module, GetModulePrivate(module) == aPrivate); 246 } 247 #endif 248 } 249 250 void HostAddRefTopLevelScript(const Value& aPrivate) { 251 // Increment the reference count of a LoadedScript object that is now pointed 252 // to by a JSScript. The reference count is decremented by 253 // HostReleaseTopLevelScript() below. 254 255 auto script = static_cast<LoadedScript*>(aPrivate.toPrivate()); 256 CheckModuleScriptPrivate(script, aPrivate); 257 script->AddRef(); 258 } 259 260 void HostReleaseTopLevelScript(const Value& aPrivate) { 261 // Decrement the reference count of a LoadedScript object that was pointed to 262 // by a JSScript. The reference count was originally incremented by 263 // HostAddRefTopLevelScript() above. 264 265 auto script = static_cast<LoadedScript*>(aPrivate.toPrivate()); 266 CheckModuleScriptPrivate(script, aPrivate); 267 script->Release(); 268 } 269 270 ////////////////////////////////////////////////////////////// 271 // EventScript 272 ////////////////////////////////////////////////////////////// 273 274 EventScript::EventScript(mozilla::dom::ReferrerPolicy aReferrerPolicy, 275 ScriptFetchOptions* aFetchOptions, nsIURI* aURI) 276 : LoadedScript(ScriptKind::eEvent, aReferrerPolicy, aFetchOptions, aURI) { 277 // EventScripts are not using ScriptLoadRequest, and mBaseURL and mURI are 278 // the same thing. 279 SetBaseURL(aURI); 280 } 281 282 ////////////////////////////////////////////////////////////// 283 // ClassicScript 284 ////////////////////////////////////////////////////////////// 285 286 ClassicScript::ClassicScript(mozilla::dom::ReferrerPolicy aReferrerPolicy, 287 ScriptFetchOptions* aFetchOptions, nsIURI* aURI) 288 : LoadedScript(ScriptKind::eClassic, aReferrerPolicy, aFetchOptions, aURI) { 289 } 290 291 ////////////////////////////////////////////////////////////// 292 // ImportMapScript 293 ////////////////////////////////////////////////////////////// 294 295 ImportMapScript::ImportMapScript(mozilla::dom::ReferrerPolicy aReferrerPolicy, 296 ScriptFetchOptions* aFetchOptions, 297 nsIURI* aURI) 298 : LoadedScript(ScriptKind::eImportMap, aReferrerPolicy, aFetchOptions, 299 aURI) {} 300 301 ////////////////////////////////////////////////////////////// 302 // ModuleScript 303 ////////////////////////////////////////////////////////////// 304 305 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(ModuleScript, LoadedScript) 306 307 NS_IMPL_CYCLE_COLLECTION_CLASS(ModuleScript) 308 309 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ModuleScript, LoadedScript) 310 tmp->UnlinkModuleRecord(); 311 tmp->mParseError.setUndefined(); 312 tmp->mErrorToRethrow.setUndefined(); 313 tmp->DropDiskCacheReference(); 314 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 315 316 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ModuleScript, LoadedScript) 317 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 318 319 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ModuleScript, LoadedScript) 320 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mModuleRecord) 321 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mParseError) 322 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mErrorToRethrow) 323 NS_IMPL_CYCLE_COLLECTION_TRACE_END 324 325 ModuleScript::ModuleScript(mozilla::dom::ReferrerPolicy aReferrerPolicy, 326 ScriptFetchOptions* aFetchOptions, nsIURI* aURI) 327 : LoadedScript(ScriptKind::eModule, aReferrerPolicy, aFetchOptions, aURI) { 328 MOZ_ASSERT(!ModuleRecord()); 329 MOZ_ASSERT(!HasParseError()); 330 MOZ_ASSERT(!HasErrorToRethrow()); 331 } 332 333 ModuleScript::ModuleScript(const LoadedScript& aOther) : LoadedScript(aOther) { 334 MOZ_ASSERT(!ModuleRecord()); 335 MOZ_ASSERT(!HasParseError()); 336 MOZ_ASSERT(!HasErrorToRethrow()); 337 } 338 339 /* static */ 340 already_AddRefed<ModuleScript> ModuleScript::FromCache( 341 const LoadedScript& aScript) { 342 MOZ_DIAGNOSTIC_ASSERT(aScript.IsModuleScript()); 343 MOZ_DIAGNOSTIC_ASSERT(aScript.IsCachedStencil()); 344 345 return mozilla::MakeRefPtr<ModuleScript>(aScript).forget(); 346 } 347 348 already_AddRefed<LoadedScript> ModuleScript::ToCache() { 349 MOZ_DIAGNOSTIC_ASSERT(IsCachedStencil()); 350 MOZ_DIAGNOSTIC_ASSERT(!HasParseError()); 351 MOZ_DIAGNOSTIC_ASSERT(!HasErrorToRethrow()); 352 353 return mozilla::MakeRefPtr<LoadedScript>(*this).forget(); 354 } 355 356 void ModuleScript::Shutdown() { 357 if (mModuleRecord) { 358 ClearModuleEnvironment(mModuleRecord); 359 } 360 361 UnlinkModuleRecord(); 362 } 363 364 void ModuleScript::UnlinkModuleRecord() { 365 // Remove the module record's pointer to this object if present and decrement 366 // our reference count. The reference is added by SetModuleRecord() below. 367 // 368 if (mModuleRecord) { 369 // Take care not to trigger gray unmarking because this takes a lot of time 370 // when we're tearing down the entire page. This is safe because we are only 371 // writing undefined into the module private, so it won't create any 372 // black-gray edges. 373 JSObject* module = mModuleRecord.unbarrieredGet(); 374 if (IsCyclicModule(module)) { 375 MOZ_ASSERT(GetModulePrivate(module).toPrivate() == this); 376 ClearModulePrivate(module); 377 } 378 mModuleRecord = nullptr; 379 } 380 } 381 382 ModuleScript::~ModuleScript() { 383 // The object may be destroyed without being unlinked first. 384 UnlinkModuleRecord(); 385 } 386 387 void ModuleScript::SetModuleRecord(Handle<JSObject*> aModuleRecord) { 388 MOZ_ASSERT(!mModuleRecord); 389 MOZ_ASSERT_IF(IsModuleScript(), !AsModuleScript()->HasParseError()); 390 MOZ_ASSERT_IF(IsModuleScript(), !AsModuleScript()->HasErrorToRethrow()); 391 392 mModuleRecord = aModuleRecord; 393 394 if (IsCyclicModule(mModuleRecord)) { 395 // Make module's host defined field point to this object. The JS engine will 396 // increment our reference count by calling HostAddRefTopLevelScript(). This 397 // is decremented when the field is cleared in UnlinkModuleRecord() above or 398 // when the module record dies. 399 MOZ_ASSERT(GetModulePrivate(mModuleRecord).isUndefined()); 400 SetModulePrivate(mModuleRecord, PrivateValue(this)); 401 } 402 403 mozilla::HoldJSObjects(this); 404 } 405 406 void ModuleScript::SetParseError(const Value& aError) { 407 MOZ_ASSERT(!aError.isUndefined()); 408 MOZ_ASSERT(!HasParseError()); 409 MOZ_ASSERT(!HasErrorToRethrow()); 410 411 UnlinkModuleRecord(); 412 mParseError = aError; 413 mozilla::HoldJSObjects(this); 414 } 415 416 void ModuleScript::SetErrorToRethrow(const Value& aError) { 417 MOZ_ASSERT(!aError.isUndefined()); 418 419 // This is only called after SetModuleRecord() or SetParseError() so we don't 420 // need to call HoldJSObjects() here. 421 MOZ_ASSERT(ModuleRecord() || HasParseError()); 422 423 mErrorToRethrow = aError; 424 } 425 426 void ModuleScript::SetForPreload(bool aValue) { mForPreload = aValue; } 427 void ModuleScript::SetHadImportMap(bool aValue) { mHadImportMap = aValue; } 428 429 } // namespace JS::loader