tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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