tor-browser

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

nsXULPrototypeCache.cpp (15441B)


      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 "nsXULPrototypeCache.h"
      8 
      9 #include "js/TracingAPI.h"
     10 #include "js/experimental/JSStencil.h"
     11 #include "mozilla/Preferences.h"
     12 #include "mozilla/RefPtr.h"
     13 #include "mozilla/StaticPrefs_nglayout.h"
     14 #include "mozilla/StyleSheetInlines.h"
     15 #include "mozilla/UniquePtrExtensions.h"
     16 #include "mozilla/intl/LocaleService.h"
     17 #include "mozilla/scache/StartupCache.h"
     18 #include "mozilla/scache/StartupCacheUtils.h"
     19 #include "nsAppDirectoryServiceDefs.h"
     20 #include "nsIFile.h"
     21 #include "nsIMemoryReporter.h"
     22 #include "nsIObjectInputStream.h"
     23 #include "nsIObjectOutputStream.h"
     24 #include "nsIObserverService.h"
     25 #include "nsIStorageStream.h"
     26 #include "nsIURI.h"
     27 #include "nsNetUtil.h"
     28 #include "nsXULPrototypeDocument.h"
     29 
     30 using namespace mozilla;
     31 using namespace mozilla::scache;
     32 using mozilla::intl::LocaleService;
     33 
     34 static const char kXULCacheInfoKey[] = "nsXULPrototypeCache.startupCache";
     35 #define CACHE_PREFIX(aCompilationTarget) "xulcache/" aCompilationTarget
     36 
     37 static void DisableXULCacheChangedCallback(const char* aPref, void* aClosure) {
     38  if (nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance()) {
     39    if (!cache->IsEnabled()) {
     40      // AbortCaching() calls Flush() for us.
     41      cache->AbortCaching();
     42    }
     43  }
     44 }
     45 
     46 //----------------------------------------------------------------------
     47 
     48 nsXULPrototypeCache* nsXULPrototypeCache::sInstance = nullptr;
     49 
     50 nsXULPrototypeCache::nsXULPrototypeCache() = default;
     51 
     52 NS_IMPL_ISUPPORTS(nsXULPrototypeCache, nsIObserver)
     53 
     54 /* static */
     55 nsXULPrototypeCache* nsXULPrototypeCache::GetInstance() {
     56  if (!sInstance) {
     57    NS_ADDREF(sInstance = new nsXULPrototypeCache());
     58 
     59    Preferences::RegisterCallback(
     60        DisableXULCacheChangedCallback,
     61        nsDependentCString(
     62            StaticPrefs::GetPrefName_nglayout_debug_disable_xul_cache()));
     63 
     64    nsCOMPtr<nsIObserverService> obsSvc =
     65        mozilla::services::GetObserverService();
     66    if (obsSvc) {
     67      nsXULPrototypeCache* p = sInstance;
     68      obsSvc->AddObserver(p, "chrome-flush-caches", false);
     69      obsSvc->AddObserver(p, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
     70      obsSvc->AddObserver(p, "startupcache-invalidate", false);
     71    }
     72  }
     73  return sInstance;
     74 }
     75 
     76 //----------------------------------------------------------------------
     77 
     78 NS_IMETHODIMP
     79 nsXULPrototypeCache::Observe(nsISupports* aSubject, const char* aTopic,
     80                             const char16_t* aData) {
     81  if (!strcmp(aTopic, "chrome-flush-caches") ||
     82      !strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     83    Flush();
     84  } else if (!strcmp(aTopic, "startupcache-invalidate")) {
     85    AbortCaching();
     86  } else {
     87    NS_WARNING("Unexpected observer topic.");
     88  }
     89  return NS_OK;
     90 }
     91 
     92 nsXULPrototypeDocument* nsXULPrototypeCache::GetPrototype(nsIURI* aURI) {
     93  if (!aURI) return nullptr;
     94 
     95  nsCOMPtr<nsIURI> uriWithoutRef;
     96  NS_GetURIWithoutRef(aURI, getter_AddRefs(uriWithoutRef));
     97 
     98  nsXULPrototypeDocument* protoDoc = mPrototypeTable.GetWeak(uriWithoutRef);
     99  if (protoDoc) {
    100    return protoDoc;
    101  }
    102 
    103  nsresult rv = BeginCaching(aURI);
    104  if (NS_FAILED(rv)) return nullptr;
    105 
    106  // No prototype in XUL memory cache. Spin up the cache Service.
    107  nsCOMPtr<nsIObjectInputStream> ois;
    108  rv = GetPrototypeInputStream(aURI, getter_AddRefs(ois));
    109  if (NS_FAILED(rv)) {
    110    return nullptr;
    111  }
    112 
    113  RefPtr<nsXULPrototypeDocument> newProto;
    114  rv = NS_NewXULPrototypeDocument(getter_AddRefs(newProto));
    115  if (NS_FAILED(rv)) return nullptr;
    116 
    117  rv = newProto->Read(ois);
    118  if (NS_SUCCEEDED(rv)) {
    119    rv = PutPrototype(newProto);
    120  } else {
    121    newProto = nullptr;
    122  }
    123 
    124  mInputStreamTable.Remove(aURI);
    125  return newProto;
    126 }
    127 
    128 nsresult nsXULPrototypeCache::PutPrototype(nsXULPrototypeDocument* aDocument) {
    129  if (!aDocument->GetURI()) {
    130    return NS_ERROR_FAILURE;
    131  }
    132 
    133  nsCOMPtr<nsIURI> uri;
    134  NS_GetURIWithoutRef(aDocument->GetURI(), getter_AddRefs(uri));
    135 
    136  // Put() releases any old value
    137  mPrototypeTable.InsertOrUpdate(uri, RefPtr{aDocument});
    138 
    139  return NS_OK;
    140 }
    141 
    142 JS::Stencil* nsXULPrototypeCache::GetStencil(nsIURI* aURI) {
    143  if (auto* entry = mStencilTable.GetEntry(aURI)) {
    144    return entry->mStencil;
    145  }
    146  return nullptr;
    147 }
    148 
    149 nsresult nsXULPrototypeCache::PutStencil(nsIURI* aURI, JS::Stencil* aStencil) {
    150  MOZ_ASSERT(aStencil, "Need a non-NULL stencil");
    151 
    152 #ifdef DEBUG_BUG_392650
    153  if (mStencilTable.Get(aURI)) {
    154    nsAutoCString scriptName;
    155    aURI->GetSpec(scriptName);
    156    nsAutoCString message("Loaded script ");
    157    message += scriptName;
    158    message += " twice (bug 392650)";
    159    NS_WARNING(message.get());
    160  }
    161 #endif
    162 
    163  mStencilTable.PutEntry(aURI)->mStencil = aStencil;
    164 
    165  return NS_OK;
    166 }
    167 
    168 void nsXULPrototypeCache::Flush() {
    169  mPrototypeTable.Clear();
    170  mStencilTable.Clear();
    171 }
    172 
    173 bool nsXULPrototypeCache::IsEnabled() {
    174  return !StaticPrefs::nglayout_debug_disable_xul_cache();
    175 }
    176 
    177 void nsXULPrototypeCache::AbortCaching() {
    178  // Flush the XUL cache for good measure, in case we cached a bogus/downrev
    179  // script, somehow.
    180  Flush();
    181 
    182  // Clear the cache set
    183  mStartupCacheURITable.Clear();
    184 }
    185 
    186 nsresult nsXULPrototypeCache::WritePrototype(
    187    nsXULPrototypeDocument* aPrototypeDocument) {
    188  nsresult rv = NS_OK, rv2 = NS_OK;
    189 
    190  if (!StartupCache::GetSingleton()) return NS_OK;
    191 
    192  nsCOMPtr<nsIURI> protoURI = aPrototypeDocument->GetURI();
    193 
    194  nsCOMPtr<nsIObjectOutputStream> oos;
    195  rv = GetPrototypeOutputStream(protoURI, getter_AddRefs(oos));
    196  NS_ENSURE_SUCCESS(rv, rv);
    197 
    198  rv = aPrototypeDocument->Write(oos);
    199  NS_ENSURE_SUCCESS(rv, rv);
    200  FinishPrototypeOutputStream(protoURI);
    201  return NS_FAILED(rv) ? rv : rv2;
    202 }
    203 
    204 static nsresult PathifyURIForType(nsXULPrototypeCache::CacheType cacheType,
    205                                  nsIURI* in, nsACString& out) {
    206  switch (cacheType) {
    207    case nsXULPrototypeCache::CacheType::Prototype:
    208      return PathifyURI(CACHE_PREFIX("proto"), in, out);
    209    case nsXULPrototypeCache::CacheType::Script:
    210      return PathifyURI(CACHE_PREFIX("script"), in, out);
    211  }
    212  MOZ_ASSERT_UNREACHABLE("unknown cache type?");
    213  return NS_ERROR_UNEXPECTED;
    214 }
    215 
    216 nsresult nsXULPrototypeCache::GetInputStream(CacheType cacheType, nsIURI* uri,
    217                                             nsIObjectInputStream** stream) {
    218  nsAutoCString spec;
    219  nsresult rv = PathifyURIForType(cacheType, uri, spec);
    220  if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE;
    221 
    222  const char* buf;
    223  uint32_t len;
    224  nsCOMPtr<nsIObjectInputStream> ois;
    225  StartupCache* sc = StartupCache::GetSingleton();
    226  if (!sc) return NS_ERROR_NOT_AVAILABLE;
    227 
    228  rv = sc->GetBuffer(spec.get(), &buf, &len);
    229  if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE;
    230 
    231  rv = NewObjectInputStreamFromBuffer(buf, len, getter_AddRefs(ois));
    232  NS_ENSURE_SUCCESS(rv, rv);
    233 
    234  mInputStreamTable.InsertOrUpdate(uri, ois);
    235 
    236  ois.forget(stream);
    237  return NS_OK;
    238 }
    239 
    240 nsresult nsXULPrototypeCache::FinishInputStream(nsIURI* uri) {
    241  mInputStreamTable.Remove(uri);
    242  return NS_OK;
    243 }
    244 
    245 nsresult nsXULPrototypeCache::GetOutputStream(nsIURI* uri,
    246                                              nsIObjectOutputStream** stream) {
    247  nsresult rv;
    248  nsCOMPtr<nsIObjectOutputStream> objectOutput;
    249  nsCOMPtr<nsIStorageStream> storageStream;
    250  bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream));
    251  if (found) {
    252    // Setting an output stream here causes crashes on Windows. The previous
    253    // version of this code always returned NS_ERROR_OUT_OF_MEMORY here,
    254    // because it used a mistyped contract ID to create its object stream.
    255    return NS_ERROR_NOT_IMPLEMENTED;
    256 #if 0
    257        nsCOMPtr<nsIOutputStream> outputStream
    258            = do_QueryInterface(storageStream);
    259        objectOutput = NS_NewObjectOutputStream(outputStream);
    260 #endif
    261  } else {
    262    rv = NewObjectOutputWrappedStorageStream(
    263        getter_AddRefs(objectOutput), getter_AddRefs(storageStream), false);
    264    NS_ENSURE_SUCCESS(rv, rv);
    265    mOutputStreamTable.InsertOrUpdate(uri, storageStream);
    266  }
    267  objectOutput.forget(stream);
    268  return NS_OK;
    269 }
    270 
    271 nsresult nsXULPrototypeCache::FinishOutputStream(CacheType cacheType,
    272                                                 nsIURI* uri) {
    273  nsresult rv;
    274  StartupCache* sc = StartupCache::GetSingleton();
    275  if (!sc) return NS_ERROR_NOT_AVAILABLE;
    276 
    277  nsCOMPtr<nsIStorageStream> storageStream;
    278  bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream));
    279  if (!found) return NS_ERROR_UNEXPECTED;
    280  nsCOMPtr<nsIOutputStream> outputStream = do_QueryInterface(storageStream);
    281  outputStream->Close();
    282 
    283  UniqueFreePtr<char[]> buf;
    284  uint32_t len;
    285  rv = NewBufferFromStorageStream(storageStream, &buf, &len);
    286  NS_ENSURE_SUCCESS(rv, rv);
    287 
    288  if (!mStartupCacheURITable.GetEntry(uri)) {
    289    nsAutoCString spec;
    290    rv = PathifyURIForType(cacheType, uri, spec);
    291    if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE;
    292    rv = sc->PutBuffer(spec.get(), std::move(buf), len);
    293    if (NS_SUCCEEDED(rv)) {
    294      mOutputStreamTable.Remove(uri);
    295      mStartupCacheURITable.PutEntry(uri);
    296    }
    297  }
    298 
    299  return rv;
    300 }
    301 
    302 // We have data if we're in the middle of writing it or we already
    303 // have it in the cache.
    304 nsresult nsXULPrototypeCache::HasData(CacheType cacheType, nsIURI* uri,
    305                                      bool* exists) {
    306  if (mOutputStreamTable.Get(uri, nullptr)) {
    307    *exists = true;
    308    return NS_OK;
    309  }
    310  nsAutoCString spec;
    311  nsresult rv = PathifyURIForType(cacheType, uri, spec);
    312  if (NS_FAILED(rv)) {
    313    *exists = false;
    314    return NS_OK;
    315  }
    316  UniquePtr<char[]> buf;
    317  StartupCache* sc = StartupCache::GetSingleton();
    318  if (sc) {
    319    *exists = sc->HasEntry(spec.get());
    320  } else {
    321    *exists = false;
    322  }
    323  return NS_OK;
    324 }
    325 
    326 nsresult nsXULPrototypeCache::BeginCaching(nsIURI* aURI) {
    327  nsresult rv, tmp;
    328 
    329  nsAutoCString path;
    330  aURI->GetPathQueryRef(path);
    331  if (!(StringEndsWith(path, ".xul"_ns) || StringEndsWith(path, ".xhtml"_ns))) {
    332    return NS_ERROR_NOT_AVAILABLE;
    333  }
    334 
    335  StartupCache* startupCache = StartupCache::GetSingleton();
    336  if (!startupCache) return NS_ERROR_FAILURE;
    337 
    338  if (StaticPrefs::nglayout_debug_disable_xul_cache()) {
    339    return NS_ERROR_NOT_AVAILABLE;
    340  }
    341 
    342  // Get the chrome directory to validate against the one stored in the
    343  // cache file, or to store there if we're generating a new file.
    344  nsCOMPtr<nsIFile> chromeDir;
    345  rv = NS_GetSpecialDirectory(NS_APP_CHROME_DIR, getter_AddRefs(chromeDir));
    346  if (NS_FAILED(rv)) return rv;
    347  nsAutoCString chromePath;
    348  rv = chromeDir->GetPersistentDescriptor(chromePath);
    349  if (NS_FAILED(rv)) return rv;
    350 
    351  // XXXbe we assume the first package's locale is the same as the locale of
    352  // all subsequent packages of cached chrome URIs....
    353  nsAutoCString package;
    354  rv = aURI->GetHost(package);
    355  if (NS_FAILED(rv)) return rv;
    356  nsAutoCString locale;
    357  LocaleService::GetInstance()->GetAppLocaleAsBCP47(locale);
    358 
    359  nsAutoCString fileChromePath, fileLocale;
    360 
    361  const char* buf = nullptr;
    362  uint32_t len, amtRead;
    363  nsCOMPtr<nsIObjectInputStream> objectInput;
    364 
    365  rv = startupCache->GetBuffer(kXULCacheInfoKey, &buf, &len);
    366  if (NS_SUCCEEDED(rv))
    367    rv = NewObjectInputStreamFromBuffer(buf, len, getter_AddRefs(objectInput));
    368 
    369  if (NS_SUCCEEDED(rv)) {
    370    rv = objectInput->ReadCString(fileLocale);
    371    tmp = objectInput->ReadCString(fileChromePath);
    372    if (NS_FAILED(tmp)) {
    373      rv = tmp;
    374    }
    375    if (NS_FAILED(rv) ||
    376        (!fileChromePath.Equals(chromePath) || !fileLocale.Equals(locale))) {
    377      // Our cache won't be valid in this case, we'll need to rewrite.
    378      // XXX This blows away work that other consumers (like
    379      // mozJSModuleLoader) have done, need more fine-grained control.
    380      startupCache->InvalidateCache();
    381      mStartupCacheURITable.Clear();
    382      rv = NS_ERROR_UNEXPECTED;
    383    }
    384  } else if (rv != NS_ERROR_NOT_AVAILABLE)
    385    // NS_ERROR_NOT_AVAILABLE is normal, usually if there's no cachefile.
    386    return rv;
    387 
    388  if (NS_FAILED(rv)) {
    389    // Either the cache entry was invalid or it didn't exist, so write it now.
    390    nsCOMPtr<nsIObjectOutputStream> objectOutput;
    391    nsCOMPtr<nsIInputStream> inputStream;
    392    nsCOMPtr<nsIStorageStream> storageStream;
    393    rv = NewObjectOutputWrappedStorageStream(
    394        getter_AddRefs(objectOutput), getter_AddRefs(storageStream), false);
    395    if (NS_SUCCEEDED(rv)) {
    396      rv = objectOutput->WriteStringZ(locale.get());
    397      tmp = objectOutput->WriteStringZ(chromePath.get());
    398      if (NS_FAILED(tmp)) {
    399        rv = tmp;
    400      }
    401      tmp = objectOutput->Close();
    402      if (NS_FAILED(tmp)) {
    403        rv = tmp;
    404      }
    405      tmp = storageStream->NewInputStream(0, getter_AddRefs(inputStream));
    406      if (NS_FAILED(tmp)) {
    407        rv = tmp;
    408      }
    409    }
    410 
    411    if (NS_SUCCEEDED(rv)) {
    412      uint64_t len64;
    413      rv = inputStream->Available(&len64);
    414      if (NS_SUCCEEDED(rv)) {
    415        if (len64 <= UINT32_MAX)
    416          len = (uint32_t)len64;
    417        else
    418          rv = NS_ERROR_FILE_TOO_BIG;
    419      }
    420    }
    421 
    422    if (NS_SUCCEEDED(rv)) {
    423      auto putBuf = UniqueFreePtr<char[]>(
    424          reinterpret_cast<char*>(malloc(sizeof(char) * len)));
    425      rv = inputStream->Read(putBuf.get(), len, &amtRead);
    426      if (NS_SUCCEEDED(rv) && len == amtRead)
    427        rv = startupCache->PutBuffer(kXULCacheInfoKey, std::move(putBuf), len);
    428      else {
    429        rv = NS_ERROR_UNEXPECTED;
    430      }
    431    }
    432 
    433    // Failed again, just bail.
    434    if (NS_FAILED(rv)) {
    435      startupCache->InvalidateCache();
    436      mStartupCacheURITable.Clear();
    437      return NS_ERROR_FAILURE;
    438    }
    439  }
    440 
    441  return NS_OK;
    442 }
    443 
    444 void nsXULPrototypeCache::MarkInCCGeneration(uint32_t aGeneration) {
    445  for (const auto& prototype : mPrototypeTable.Values()) {
    446    prototype->MarkInCCGeneration(aGeneration);
    447  }
    448 }
    449 
    450 MOZ_DEFINE_MALLOC_SIZE_OF(CacheMallocSizeOf)
    451 
    452 static void ReportSize(const nsCString& aPath, size_t aAmount,
    453                       const nsCString& aDescription,
    454                       nsIHandleReportCallback* aHandleReport,
    455                       nsISupports* aData) {
    456  nsAutoCString path("explicit/xul-prototype-cache/");
    457  path += aPath;
    458  aHandleReport->Callback(""_ns, path, nsIMemoryReporter::KIND_HEAP,
    459                          nsIMemoryReporter::UNITS_BYTES, aAmount, aDescription,
    460                          aData);
    461 }
    462 
    463 /* static */
    464 void nsXULPrototypeCache::CollectMemoryReports(
    465    nsIHandleReportCallback* aHandleReport, nsISupports* aData) {
    466  if (!sInstance) {
    467    return;
    468  }
    469 
    470  MallocSizeOf mallocSizeOf = CacheMallocSizeOf;
    471  size_t other = mallocSizeOf(sInstance);
    472 
    473 #define REPORT_SIZE(_path, _amount, _desc) \
    474  ReportSize(_path, _amount, nsLiteralCString(_desc), aHandleReport, aData)
    475 
    476  other += sInstance->mPrototypeTable.ShallowSizeOfExcludingThis(mallocSizeOf);
    477  // TODO Report content in mPrototypeTable?
    478 
    479  other += sInstance->mStencilTable.ShallowSizeOfExcludingThis(mallocSizeOf);
    480  // TODO Report content inside mStencilTable?
    481 
    482  other +=
    483      sInstance->mStartupCacheURITable.ShallowSizeOfExcludingThis(mallocSizeOf);
    484 
    485  other +=
    486      sInstance->mOutputStreamTable.ShallowSizeOfExcludingThis(mallocSizeOf);
    487  other +=
    488      sInstance->mInputStreamTable.ShallowSizeOfExcludingThis(mallocSizeOf);
    489 
    490  REPORT_SIZE("other"_ns, other,
    491              "Memory used by "
    492              "the instance and tables of the XUL prototype cache.");
    493 
    494 #undef REPORT_SIZE
    495 }