tor-browser

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

SharedScriptCache.cpp (11844B)


      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 "SharedScriptCache.h"
      8 
      9 #include "ScriptLoadHandler.h"  // ScriptLoadHandler
     10 #include "ScriptLoader.h"       // ScriptLoader
     11 #include "ScriptTrace.h"        // TRACE_FOR_TEST
     12 #include "js/experimental/CompileScript.h"  // JS::FrontendContext, JS::NewFrontendContext, JS::DestroyFrontendContext
     13 #include "mozilla/Maybe.h"              // Maybe, Some, Nothing
     14 #include "mozilla/TaskController.h"     // TaskController, Task
     15 #include "mozilla/dom/ContentParent.h"  // dom::ContentParent
     16 #include "nsIMemoryReporter.h"  // nsIMemoryReporter, MOZ_DEFINE_MALLOC_SIZE_OF, RegisterWeakMemoryReporter, UnregisterWeakMemoryReporter, MOZ_COLLECT_REPORT, KIND_HEAP, UNITS_BYTES
     17 #include "nsIPrefBranch.h"   // nsIPrefBranch, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
     18 #include "nsIPrefService.h"  // NS_PREFSERVICE_CONTRACTID
     19 #include "nsIPrincipal.h"    // nsIPrincipal
     20 #include "nsISupportsImpl.h"  // NS_IMPL_ISUPPORTS
     21 #include "nsStringFwd.h"      // nsACString
     22 
     23 namespace mozilla::dom {
     24 
     25 ScriptHashKey::ScriptHashKey(
     26    ScriptLoader* aLoader, const JS::loader::ScriptLoadRequest* aRequest,
     27    mozilla::dom::ReferrerPolicy aReferrerPolicy,
     28    const JS::loader::ScriptFetchOptions* aFetchOptions,
     29    const nsCOMPtr<nsIURI> aURI)
     30    : PLDHashEntryHdr(),
     31      mURI(aURI),
     32      mPartitionPrincipal(aLoader->PartitionedPrincipal()),
     33      mLoaderPrincipal(aLoader->LoaderPrincipal()),
     34      mKind(aRequest->mKind),
     35      mCORSMode(aFetchOptions->mCORSMode),
     36      mReferrerPolicy(aReferrerPolicy),
     37      mSRIMetadata(aRequest->mIntegrity),
     38      mNonce(aFetchOptions->mNonce) {
     39  if (mKind == JS::loader::ScriptKind::eClassic) {
     40    if (aRequest->GetScriptLoadContext()->HasScriptElement()) {
     41      aRequest->GetScriptLoadContext()->GetHintCharset(mHintCharset);
     42    }
     43  }
     44 
     45  MOZ_COUNT_CTOR(ScriptHashKey);
     46 }
     47 
     48 ScriptHashKey::ScriptHashKey(ScriptLoader* aLoader,
     49                             const JS::loader::ScriptLoadRequest* aRequest,
     50                             const JS::loader::LoadedScript* aLoadedScript)
     51    : ScriptHashKey(aLoader, aRequest, aLoadedScript->ReferrerPolicy(),
     52                    aLoadedScript->GetFetchOptions(), aLoadedScript->GetURI()) {
     53 }
     54 
     55 ScriptHashKey::ScriptHashKey(const ScriptLoadData& aLoadData)
     56    : ScriptHashKey(aLoadData.CacheKey()) {}
     57 
     58 bool ScriptHashKey::KeyEquals(const ScriptHashKey& aKey) const {
     59  {
     60    bool eq;
     61    if (NS_FAILED(mURI->Equals(aKey.mURI, &eq)) || !eq) {
     62      return false;
     63    }
     64  }
     65 
     66  if (!mPartitionPrincipal->Equals(aKey.mPartitionPrincipal)) {
     67    return false;
     68  }
     69 
     70  // NOTE: mLoaderPrincipal is only for the SharedSubResourceCache logic,
     71  //       not for comparison here.
     72 
     73  if (mKind != aKey.mKind) {
     74    return false;
     75  }
     76 
     77  if (mCORSMode != aKey.mCORSMode) {
     78    return false;
     79  }
     80 
     81  if (mReferrerPolicy != aKey.mReferrerPolicy) {
     82    return false;
     83  }
     84 
     85  if (!mSRIMetadata.CanTrustBeDelegatedTo(aKey.mSRIMetadata) ||
     86      !aKey.mSRIMetadata.CanTrustBeDelegatedTo(mSRIMetadata)) {
     87    return false;
     88  }
     89 
     90  if (mNonce != aKey.mNonce) {
     91    return false;
     92  }
     93 
     94  // NOTE: module always use UTF-8.
     95  if (mKind == JS::loader::ScriptKind::eClassic) {
     96    if (mHintCharset != aKey.mHintCharset) {
     97      return false;
     98    }
     99  }
    100 
    101  return true;
    102 }
    103 
    104 NS_IMPL_ISUPPORTS(ScriptLoadData, nsISupports)
    105 
    106 ScriptLoadData::ScriptLoadData(ScriptLoader* aLoader,
    107                               JS::loader::ScriptLoadRequest* aRequest,
    108                               JS::loader::LoadedScript* aLoadedScript)
    109    : mExpirationTime(aRequest->ExpirationTime()),
    110      mLoader(aLoader),
    111      mKey(aLoader, aRequest, aLoadedScript),
    112      mLoadedScript(aLoadedScript),
    113      mNetworkMetadata(aRequest->mNetworkMetadata) {}
    114 
    115 NS_IMPL_ISUPPORTS(SharedScriptCache, nsIMemoryReporter)
    116 
    117 MOZ_DEFINE_MALLOC_SIZE_OF(SharedScriptCacheMallocSizeOf)
    118 
    119 SharedScriptCache::SharedScriptCache() = default;
    120 
    121 void SharedScriptCache::Init() {
    122  RegisterWeakMemoryReporter(this);
    123 
    124  // URL classification (tracking protection etc) are handled inside
    125  // nsHttpChannel.
    126  // The cache reflects the policy for whether to block or not, and once
    127  // the policy is modified, we should discard the cache, to avoid running
    128  // a cached script which is supposed to be blocked.
    129  auto ClearCache = [](const char*, void*) { Clear(); };
    130  Preferences::RegisterPrefixCallback(ClearCache, "urlclassifier.");
    131  Preferences::RegisterCallback(ClearCache,
    132                                "privacy.trackingprotection.enabled");
    133 }
    134 
    135 SharedScriptCache::~SharedScriptCache() { UnregisterWeakMemoryReporter(this); }
    136 
    137 void SharedScriptCache::LoadCompleted(SharedScriptCache* aCache,
    138                                      ScriptLoadData& aData) {}
    139 
    140 NS_IMETHODIMP
    141 SharedScriptCache::CollectReports(nsIHandleReportCallback* aHandleReport,
    142                                  nsISupports* aData, bool aAnonymize) {
    143  MOZ_COLLECT_REPORT("explicit/js-non-window/cache", KIND_HEAP, UNITS_BYTES,
    144                     SharedScriptCacheMallocSizeOf(this) +
    145                         SizeOfExcludingThis(SharedScriptCacheMallocSizeOf),
    146                     "Memory used for SharedScriptCache to share script "
    147                     "across documents");
    148  return NS_OK;
    149 }
    150 
    151 /* static */
    152 void SharedScriptCache::Clear(const Maybe<bool>& aChrome,
    153                              const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal,
    154                              const Maybe<nsCString>& aSchemelessSite,
    155                              const Maybe<OriginAttributesPattern>& aPattern,
    156                              const Maybe<nsCString>& aURL) {
    157  using ContentParent = dom::ContentParent;
    158 
    159  if (XRE_IsParentProcess()) {
    160    for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
    161      (void)cp->SendClearScriptCache(aChrome, aPrincipal, aSchemelessSite,
    162                                     aPattern, aURL);
    163    }
    164  }
    165 
    166  if (sSingleton) {
    167    sSingleton->ClearInProcess(aChrome, aPrincipal, aSchemelessSite, aPattern,
    168                               aURL);
    169  }
    170 }
    171 
    172 /* static */
    173 void SharedScriptCache::Invalidate() {
    174  using ContentParent = dom::ContentParent;
    175 
    176  if (XRE_IsParentProcess()) {
    177    for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
    178      (void)cp->SendInvalidateScriptCache();
    179    }
    180  }
    181 
    182  if (sSingleton) {
    183    sSingleton->InvalidateInProcess();
    184  }
    185  TRACE_FOR_TEST_0("memorycache:invalidate");
    186 }
    187 
    188 void SharedScriptCache::InvalidateInProcess() {
    189  for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) {
    190    if (!iter.Data().mResource->HasCacheEntryId()) {
    191      iter.Remove();
    192    } else {
    193      iter.Data().mResource->SetDirty();
    194    }
    195  }
    196 }
    197 
    198 /* static */
    199 void SharedScriptCache::PrepareForLastCC() {
    200  if (sSingleton) {
    201    sSingleton->mComplete.Clear();
    202    sSingleton->mPending.Clear();
    203    sSingleton->mLoading.Clear();
    204  }
    205 }
    206 
    207 static bool ShouldSave(JS::loader::LoadedScript* aLoadedScript,
    208                       ScriptLoader::DiskCacheStrategy aStrategy) {
    209  if (!aLoadedScript->HasDiskCacheReference()) {
    210    return false;
    211  }
    212 
    213  if (!aLoadedScript->HasSRI()) {
    214    return false;
    215  }
    216 
    217  if (aStrategy.mHasSourceLengthMin) {
    218    size_t len = JS::GetScriptSourceLength(aLoadedScript->GetStencil());
    219    if (len < aStrategy.mSourceLengthMin) {
    220      return false;
    221    }
    222  }
    223 
    224  if (aStrategy.mHasFetchCountMin) {
    225    if (aLoadedScript->mFetchCount < aStrategy.mFetchCountMin) {
    226      return false;
    227    }
    228  }
    229 
    230  return true;
    231 }
    232 
    233 bool SharedScriptCache::MaybeScheduleUpdateDiskCache() {
    234  auto strategy = ScriptLoader::GetDiskCacheStrategy();
    235  if (strategy.mIsDisabled) {
    236    return false;
    237  }
    238 
    239  bool hasSaveable = false;
    240  for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) {
    241    JS::loader::LoadedScript* loadedScript = iter.Data().mResource;
    242    if (ShouldSave(loadedScript, strategy)) {
    243      hasSaveable = true;
    244      break;
    245    }
    246  }
    247 
    248  if (!hasSaveable) {
    249    return false;
    250  }
    251 
    252  // TODO: Apply more flexible scheduling (bug 1902951)
    253 
    254  nsCOMPtr<nsIRunnable> updater =
    255      NewRunnableMethod("SharedScriptCache::UpdateDiskCache", this,
    256                        &SharedScriptCache::UpdateDiskCache);
    257  (void)NS_DispatchToCurrentThreadQueue(updater.forget(),
    258                                        EventQueuePriority::Idle);
    259  return true;
    260 }
    261 
    262 class ScriptEncodeAndCompressionTask : public mozilla::Task {
    263 public:
    264  ScriptEncodeAndCompressionTask()
    265      : Task(Kind::OffMainThreadOnly, EventQueuePriority::Idle) {}
    266  virtual ~ScriptEncodeAndCompressionTask() = default;
    267 
    268 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
    269  bool GetName(nsACString& aName) override {
    270    aName.AssignLiteral("ScriptEncodeAndCompressionTask");
    271    return true;
    272  }
    273 #endif
    274 
    275  TaskResult Run() override {
    276    SharedScriptCache::Get()->EncodeAndCompress();
    277    return TaskResult::Complete;
    278  }
    279 };
    280 
    281 class ScriptSaveTask : public mozilla::Task {
    282 public:
    283  ScriptSaveTask() : Task(Kind::MainThreadOnly, EventQueuePriority::Idle) {}
    284  virtual ~ScriptSaveTask() = default;
    285 
    286 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
    287  bool GetName(nsACString& aName) override {
    288    aName.AssignLiteral("ScriptSaveTask");
    289    return true;
    290  }
    291 #endif
    292 
    293  TaskResult Run() override {
    294    SharedScriptCache::Get()->SaveToDiskCache();
    295    return TaskResult::Complete;
    296  }
    297 };
    298 
    299 void SharedScriptCache::UpdateDiskCache() {
    300  auto strategy = ScriptLoader::GetDiskCacheStrategy();
    301  if (strategy.mIsDisabled) {
    302    return;
    303  }
    304 
    305  mozilla::MutexAutoLock lock(mEncodeMutex);
    306 
    307  if (!mEncodeItems.empty()) {
    308    return;
    309  }
    310 
    311  for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) {
    312    JS::loader::LoadedScript* loadedScript = iter.Data().mResource;
    313    if (!ShouldSave(loadedScript, strategy)) {
    314      continue;
    315    }
    316 
    317    if (!mEncodeItems.emplaceBack(loadedScript->GetStencil(),
    318                                  std::move(loadedScript->SRI()),
    319                                  loadedScript)) {
    320      continue;
    321    }
    322  }
    323 
    324  if (mEncodeItems.empty()) {
    325    return;
    326  }
    327 
    328  RefPtr<ScriptEncodeAndCompressionTask> encodeTask =
    329      new ScriptEncodeAndCompressionTask();
    330  RefPtr<ScriptSaveTask> saveTask = new ScriptSaveTask();
    331  saveTask->AddDependency(encodeTask);
    332 
    333  TaskController::Get()->AddTask(encodeTask.forget());
    334  TaskController::Get()->AddTask(saveTask.forget());
    335 }
    336 
    337 void SharedScriptCache::EncodeAndCompress() {
    338  JS::FrontendContext* fc = JS::NewFrontendContext();
    339  if (!fc) {
    340    return;
    341  }
    342 
    343  mozilla::MutexAutoLock lock(mEncodeMutex);
    344 
    345  for (auto& item : mEncodeItems) {
    346    if (!ScriptLoader::EncodeAndCompress(fc, item.mLoadedScript, item.mStencil,
    347                                         item.mSRI, item.mCompressed)) {
    348      item.mCompressed.clear();
    349    }
    350  }
    351 
    352  JS::DestroyFrontendContext(fc);
    353 }
    354 
    355 void SharedScriptCache::SaveToDiskCache() {
    356  MOZ_ASSERT(NS_IsMainThread());
    357 
    358  mozilla::MutexAutoLock lock(mEncodeMutex);
    359 
    360  for (const auto& item : mEncodeItems) {
    361    if (item.mCompressed.empty()) {
    362      item.mLoadedScript->DropDiskCacheReference();
    363      item.mLoadedScript->DropSRIOrSRIAndSerializedStencil();
    364      TRACE_FOR_TEST(item.mLoadedScript, "diskcache:failed");
    365      continue;
    366    }
    367 
    368    if (!ScriptLoader::SaveToDiskCache(item.mLoadedScript, item.mCompressed)) {
    369      item.mLoadedScript->DropDiskCacheReference();
    370      item.mLoadedScript->DropSRIOrSRIAndSerializedStencil();
    371      TRACE_FOR_TEST(item.mLoadedScript, "diskcache:failed");
    372    }
    373 
    374    item.mLoadedScript->DropDiskCacheReference();
    375    item.mLoadedScript->DropSRIOrSRIAndSerializedStencil();
    376    TRACE_FOR_TEST(item.mLoadedScript, "diskcache:saved");
    377  }
    378 
    379  mEncodeItems.clear();
    380 }
    381 
    382 }  // namespace mozilla::dom