tor-browser

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

URLPreloader.cpp (20353B)


      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 "ScriptPreloader-inl.h"
      8 #include "mozilla/URLPreloader.h"
      9 #include "mozilla/loader/AutoMemMap.h"
     10 
     11 #include "mozilla/ClearOnShutdown.h"
     12 #include "mozilla/FileUtils.h"
     13 #include "mozilla/IOBuffers.h"
     14 #include "mozilla/Logging.h"
     15 #include "mozilla/ScopeExit.h"
     16 #include "mozilla/Services.h"
     17 #include "mozilla/Try.h"
     18 #include "mozilla/Vector.h"
     19 #include "mozilla/scache/StartupCache.h"
     20 
     21 #include "crc32c.h"
     22 #include "MainThreadUtils.h"
     23 #include "nsPrintfCString.h"
     24 #include "nsDebug.h"
     25 #include "nsIFile.h"
     26 #include "nsIFileURL.h"
     27 #include "nsNetUtil.h"
     28 #include "nsPromiseFlatString.h"
     29 #include "nsProxyRelease.h"
     30 #include "nsThreadUtils.h"
     31 #include "nsXULAppAPI.h"
     32 #include "nsZipArchive.h"
     33 #include "xpcpublic.h"
     34 
     35 namespace mozilla {
     36 namespace {
     37 static LazyLogModule gURLLog("URLPreloader");
     38 
     39 #define LOG(level, ...) MOZ_LOG(gURLLog, LogLevel::level, (__VA_ARGS__))
     40 
     41 template <typename T>
     42 bool StartsWith(const T& haystack, const T& needle) {
     43  return StringHead(haystack, needle.Length()) == needle;
     44 }
     45 }  // anonymous namespace
     46 
     47 using namespace mozilla::loader;
     48 using mozilla::scache::StartupCache;
     49 
     50 nsresult URLPreloader::CollectReports(nsIHandleReportCallback* aHandleReport,
     51                                      nsISupports* aData, bool aAnonymize) {
     52  MOZ_COLLECT_REPORT("explicit/url-preloader/other", KIND_HEAP, UNITS_BYTES,
     53                     ShallowSizeOfIncludingThis(MallocSizeOf),
     54                     "Memory used by the URL preloader service itself.");
     55 
     56  for (const auto& elem : mCachedURLs.Values()) {
     57    nsAutoCString pathName;
     58    pathName.Append(elem->mPath);
     59    // The backslashes will automatically be replaced with slashes in
     60    // about:memory, without splitting each path component into a separate
     61    // branch in the memory report tree.
     62    pathName.ReplaceChar('/', '\\');
     63 
     64    nsPrintfCString path("explicit/url-preloader/cached-urls/%s/[%s]",
     65                         elem->TypeString(), pathName.get());
     66 
     67    aHandleReport->Callback(
     68        ""_ns, path, KIND_HEAP, UNITS_BYTES,
     69        elem->SizeOfIncludingThis(MallocSizeOf),
     70        nsLiteralCString("Memory used to hold cache data for files which "
     71                         "have been read or pre-loaded during this session."),
     72        aData);
     73  }
     74 
     75  return NS_OK;
     76 }
     77 
     78 // static
     79 already_AddRefed<URLPreloader> URLPreloader::Create(bool* aInitialized) {
     80  // The static APIs like URLPreloader::Read work in the child process because
     81  // they fall back to a synchronous read. The actual preloader must be
     82  // explicitly initialized, and this should only be done in the parent.
     83  MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
     84 
     85  RefPtr<URLPreloader> preloader = new URLPreloader();
     86  if (preloader->InitInternal().isOk()) {
     87    *aInitialized = true;
     88    RegisterWeakMemoryReporter(preloader);
     89  } else {
     90    *aInitialized = false;
     91  }
     92 
     93  return preloader.forget();
     94 }
     95 
     96 URLPreloader& URLPreloader::GetSingleton() {
     97  if (!sSingleton) {
     98    sSingleton = Create(&sInitialized);
     99    ClearOnShutdown(&sSingleton);
    100  }
    101 
    102  return *sSingleton;
    103 }
    104 
    105 bool URLPreloader::sInitialized = false;
    106 
    107 StaticRefPtr<URLPreloader> URLPreloader::sSingleton;
    108 
    109 URLPreloader::~URLPreloader() {
    110  if (sInitialized) {
    111    UnregisterWeakMemoryReporter(this);
    112    sInitialized = false;
    113  }
    114 }
    115 
    116 Result<Ok, nsresult> URLPreloader::InitInternal() {
    117  MOZ_RELEASE_ASSERT(NS_IsMainThread());
    118 
    119  if (Omnijar::HasOmnijar(Omnijar::GRE)) {
    120    MOZ_TRY(Omnijar::GetURIString(Omnijar::GRE, mGREPrefix));
    121  }
    122  if (Omnijar::HasOmnijar(Omnijar::APP)) {
    123    MOZ_TRY(Omnijar::GetURIString(Omnijar::APP, mAppPrefix));
    124  }
    125 
    126  nsresult rv;
    127  nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
    128  MOZ_TRY(rv);
    129 
    130  nsCOMPtr<nsIProtocolHandler> ph;
    131  MOZ_TRY(ios->GetProtocolHandler("resource", getter_AddRefs(ph)));
    132 
    133  mResProto = do_QueryInterface(ph, &rv);
    134  MOZ_TRY(rv);
    135 
    136  mChromeReg = services::GetChromeRegistry();
    137  if (!mChromeReg) {
    138    return Err(NS_ERROR_UNEXPECTED);
    139  }
    140 
    141  MOZ_TRY(NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(mProfD)));
    142 
    143  return Ok();
    144 }
    145 
    146 URLPreloader& URLPreloader::ReInitialize() {
    147  MOZ_ASSERT(sSingleton);
    148  sSingleton = nullptr;
    149  sSingleton = Create(&sInitialized);
    150  return *sSingleton;
    151 }
    152 
    153 Result<nsCOMPtr<nsIFile>, nsresult> URLPreloader::GetCacheFile(
    154    const nsAString& suffix) {
    155  if (!mProfD) {
    156    return Err(NS_ERROR_NOT_INITIALIZED);
    157  }
    158 
    159  nsCOMPtr<nsIFile> cacheFile;
    160  MOZ_TRY(mProfD->Clone(getter_AddRefs(cacheFile)));
    161 
    162  MOZ_TRY(cacheFile->AppendNative("startupCache"_ns));
    163  (void)cacheFile->Create(nsIFile::DIRECTORY_TYPE, 0777);
    164 
    165  MOZ_TRY(cacheFile->Append(u"urlCache"_ns + suffix));
    166 
    167  return std::move(cacheFile);
    168 }
    169 
    170 static const uint8_t URL_MAGIC[] = "mozURLcachev003";
    171 
    172 Result<nsCOMPtr<nsIFile>, nsresult> URLPreloader::FindCacheFile() {
    173  if (StartupCache::GetIgnoreDiskCache()) {
    174    return Err(NS_ERROR_ABORT);
    175  }
    176 
    177  nsCOMPtr<nsIFile> cacheFile = MOZ_TRY(GetCacheFile(u".bin"_ns));
    178 
    179  bool exists;
    180  MOZ_TRY(cacheFile->Exists(&exists));
    181  if (exists) {
    182    MOZ_TRY(cacheFile->MoveTo(nullptr, u"urlCache-current.bin"_ns));
    183  } else {
    184    MOZ_TRY(cacheFile->SetLeafName(u"urlCache-current.bin"_ns));
    185    MOZ_TRY(cacheFile->Exists(&exists));
    186    if (!exists) {
    187      return Err(NS_ERROR_FILE_NOT_FOUND);
    188    }
    189  }
    190 
    191  return std::move(cacheFile);
    192 }
    193 
    194 Result<Ok, nsresult> URLPreloader::WriteCache() {
    195  MOZ_ASSERT(!NS_IsMainThread());
    196  MOZ_DIAGNOSTIC_ASSERT(mStartupFinished);
    197 
    198  // The script preloader might call us a second time, if it has to re-write
    199  // its cache after a cache flush. We don't care about cache flushes, since
    200  // our cache doesn't store any file data, only paths. And we currently clear
    201  // our cached file list after the first write, which means that a second
    202  // write would (aside from breaking the invariant that we never touch
    203  // mCachedURLs off-main-thread after the first write, and trigger a data
    204  // race) mean we get no pre-loading on the next startup.
    205  if (mCacheWritten) {
    206    return Ok();
    207  }
    208  mCacheWritten = true;
    209 
    210  LOG(Debug, "Writing cache...");
    211 
    212  nsCOMPtr<nsIFile> cacheFile = MOZ_TRY(GetCacheFile(u"-new.bin"_ns));
    213 
    214  bool exists;
    215  MOZ_TRY(cacheFile->Exists(&exists));
    216  if (exists) {
    217    MOZ_TRY(cacheFile->Remove(false));
    218  }
    219 
    220  {
    221    AutoFDClose raiiFd;
    222    MOZ_TRY(cacheFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, 0644,
    223                                        getter_Transfers(raiiFd)));
    224    const auto fd = raiiFd.get();
    225 
    226    nsTArray<URLEntry*> entries;
    227    for (const auto& entry : mCachedURLs.Values()) {
    228      if (entry->mReadTime) {
    229        entries.AppendElement(entry.get());
    230      }
    231    }
    232 
    233    entries.Sort(URLEntry::Comparator());
    234 
    235    OutputBuffer buf;
    236    for (auto entry : entries) {
    237      entry->Code(buf);
    238    }
    239 
    240    uint8_t headerSize[4];
    241    LittleEndian::writeUint32(headerSize, buf.cursor());
    242 
    243    uint8_t crc[4];
    244    LittleEndian::writeUint32(crc, ComputeCrc32c(~0, buf.Get(), buf.cursor()));
    245 
    246    MOZ_TRY(Write(fd, URL_MAGIC, sizeof(URL_MAGIC)));
    247    MOZ_TRY(Write(fd, headerSize, sizeof(headerSize)));
    248    MOZ_TRY(Write(fd, crc, sizeof(crc)));
    249    MOZ_TRY(Write(fd, buf.Get(), buf.cursor()));
    250  }
    251 
    252  MOZ_TRY(cacheFile->MoveTo(nullptr, u"urlCache.bin"_ns));
    253 
    254  NS_DispatchToMainThread(
    255      NewRunnableMethod("URLPreloader::Cleanup", this, &URLPreloader::Cleanup));
    256 
    257  return Ok();
    258 }
    259 
    260 void URLPreloader::Cleanup() { mCachedURLs.Clear(); }
    261 
    262 Result<Ok, nsresult> URLPreloader::ReadCache(
    263    LinkedList<URLEntry>& pendingURLs) {
    264  LOG(Debug, "Reading cache...");
    265 
    266  nsCOMPtr<nsIFile> cacheFile = MOZ_TRY(FindCacheFile());
    267 
    268  AutoMemMap cache;
    269  MOZ_TRY(cache.init(cacheFile));
    270 
    271  auto size = cache.size();
    272 
    273  uint32_t headerSize;
    274  uint32_t crc;
    275  if (size < sizeof(URL_MAGIC) + sizeof(headerSize) + sizeof(crc)) {
    276    return Err(NS_ERROR_UNEXPECTED);
    277  }
    278 
    279  auto data = cache.get<uint8_t>();
    280  auto end = data + size;
    281 
    282  if (memcmp(URL_MAGIC, data.get(), sizeof(URL_MAGIC))) {
    283    return Err(NS_ERROR_UNEXPECTED);
    284  }
    285  data += sizeof(URL_MAGIC);
    286 
    287  headerSize = LittleEndian::readUint32(data.get());
    288  data += sizeof(headerSize);
    289 
    290  crc = LittleEndian::readUint32(data.get());
    291  data += sizeof(crc);
    292 
    293  if (data + headerSize > end) {
    294    return Err(NS_ERROR_UNEXPECTED);
    295  }
    296 
    297  if (crc != ComputeCrc32c(~0, data.get(), headerSize)) {
    298    return Err(NS_ERROR_UNEXPECTED);
    299  }
    300 
    301  {
    302    mMonitor.AssertCurrentThreadOwns();
    303 
    304    auto cleanup = MakeScopeExit([&]() {
    305      while (auto* elem = pendingURLs.getFirst()) {
    306        elem->remove();
    307      }
    308      mCachedURLs.Clear();
    309    });
    310 
    311    Range<const uint8_t> header(data, data + headerSize);
    312    data += headerSize;
    313 
    314    InputBuffer buf(header);
    315    while (!buf.finished()) {
    316      CacheKey key(buf);
    317 
    318      LOG(Debug, "Cached file: %s %s", key.TypeString(), key.mPath.get());
    319 
    320      // Don't bother doing anything else if the key didn't load correctly.
    321      // We're going to throw it out right away, and it is possible that this
    322      // leads to pendingURLs getting into a weird state.
    323      if (buf.error()) {
    324        return Err(NS_ERROR_UNEXPECTED);
    325      }
    326 
    327      auto entry = mCachedURLs.GetOrInsertNew(key, key);
    328      entry->mResultCode = NS_ERROR_NOT_INITIALIZED;
    329 
    330      if (entry->isInList()) {
    331 #ifdef NIGHTLY_BUILD
    332        MOZ_DIAGNOSTIC_ASSERT(pendingURLs.contains(entry),
    333                              "Entry should be in pendingURLs");
    334        MOZ_DIAGNOSTIC_ASSERT(key.mPath.Length() > 0,
    335                              "Path should be non-empty");
    336        MOZ_DIAGNOSTIC_CRASH("Entry should be new and not in any list");
    337 #endif
    338        return Err(NS_ERROR_UNEXPECTED);
    339      }
    340 
    341      pendingURLs.insertBack(entry);
    342    }
    343 
    344    MOZ_RELEASE_ASSERT(!buf.error(),
    345                       "We should have already bailed on an error");
    346 
    347    cleanup.release();
    348  }
    349 
    350  return Ok();
    351 }
    352 
    353 void URLPreloader::BackgroundReadFiles() {
    354  auto cleanup = MakeScopeExit([&]() {
    355    auto lock = mReaderThread.Lock();
    356    auto& readerThread = lock.ref();
    357    NS_DispatchToMainThread(NewRunnableMethod(
    358        "nsIThread::AsyncShutdown", readerThread, &nsIThread::AsyncShutdown));
    359 
    360    readerThread = nullptr;
    361  });
    362 
    363  Vector<nsZipCursor> cursors;
    364  LinkedList<URLEntry> pendingURLs;
    365  {
    366    MonitorAutoLock mal(mMonitor);
    367 
    368    if (ReadCache(pendingURLs).isErr()) {
    369      mReaderInitialized = true;
    370      mal.NotifyAll();
    371      return;
    372    }
    373 
    374    int numZipEntries = 0;
    375    for (auto entry : pendingURLs) {
    376      if (entry->mType != entry->TypeFile) {
    377        numZipEntries++;
    378      }
    379    }
    380    MOZ_RELEASE_ASSERT(cursors.reserve(numZipEntries));
    381 
    382    // Initialize the zip cursors for all files in Omnijar while the monitor
    383    // is locked. Omnijar is not threadsafe, so the caller of
    384    // AutoBeginReading guard must ensure that no code accesses Omnijar
    385    // until this segment is done. Once the cursors have been initialized,
    386    // the actual reading and decompression can safely be done off-thread,
    387    // as is the case for thread-retargeted jar: channels.
    388    for (auto entry : pendingURLs) {
    389      if (entry->mType == entry->TypeFile) {
    390        continue;
    391      }
    392 
    393      RefPtr<nsZipArchive> zip = entry->Archive();
    394      if (!zip) {
    395        MOZ_CRASH_UNSAFE_PRINTF(
    396            "Failed to get Omnijar %s archive for entry (path: \"%s\")",
    397            entry->TypeString(), entry->mPath.get());
    398      }
    399 
    400      auto item = zip->GetItem(entry->mPath);
    401      if (!item) {
    402        entry->mResultCode = NS_ERROR_FILE_NOT_FOUND;
    403        continue;
    404      }
    405 
    406      size_t size = item->RealSize();
    407 
    408      entry->mData.SetLength(size);
    409      auto data = entry->mData.BeginWriting();
    410 
    411      cursors.infallibleEmplaceBack(item, zip, reinterpret_cast<uint8_t*>(data),
    412                                    size, true);
    413    }
    414 
    415    mReaderInitialized = true;
    416    mal.NotifyAll();
    417  }
    418 
    419  // Loop over the entries, read the file's contents, store them in the
    420  // entry's mData pointer, and notify any waiting threads to check for
    421  // completion.
    422  uint32_t i = 0;
    423  for (auto entry : pendingURLs) {
    424    // If there is any other error code, the entry has already failed at
    425    // this point, so don't bother trying to read it again.
    426    if (entry->mResultCode != NS_ERROR_NOT_INITIALIZED) {
    427      continue;
    428    }
    429 
    430    nsresult rv = NS_OK;
    431 
    432    LOG(Debug, "Background reading %s file %s", entry->TypeString(),
    433        entry->mPath.get());
    434 
    435    if (entry->mType == entry->TypeFile) {
    436      auto result = entry->Read();
    437      if (result.isErr()) {
    438        rv = result.unwrapErr();
    439      }
    440    } else {
    441      auto& cursor = cursors[i++];
    442 
    443      uint32_t len;
    444      cursor.Copy(&len);
    445      if (len != entry->mData.Length()) {
    446        entry->mData.Truncate();
    447        rv = NS_ERROR_FAILURE;
    448      }
    449    }
    450 
    451    entry->mResultCode = rv;
    452    mMonitor.NotifyAll();
    453  }
    454 
    455  // We're done reading pending entries, so clear the list.
    456  pendingURLs.clear();
    457 }
    458 
    459 void URLPreloader::BeginBackgroundRead() {
    460  auto lock = mReaderThread.Lock();
    461  auto& readerThread = lock.ref();
    462  if (!readerThread && !mReaderInitialized && sInitialized) {
    463    nsresult rv;
    464    rv = NS_NewNamedThread("BGReadURLs", getter_AddRefs(readerThread));
    465    if (NS_WARN_IF(NS_FAILED(rv))) {
    466      return;
    467    }
    468 
    469    nsCOMPtr<nsIRunnable> runnable =
    470        NewRunnableMethod("URLPreloader::BackgroundReadFiles", this,
    471                          &URLPreloader::BackgroundReadFiles);
    472    rv = readerThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
    473    if (NS_WARN_IF(NS_FAILED(rv))) {
    474      // If we can't launch the task, just destroy the thread
    475      readerThread = nullptr;
    476      return;
    477    }
    478  }
    479 }
    480 
    481 Result<nsCString, nsresult> URLPreloader::ReadInternal(const CacheKey& key,
    482                                                       ReadType readType) {
    483  if (mStartupFinished || !mReaderInitialized) {
    484    URLEntry entry(key);
    485 
    486    return entry.Read();
    487  }
    488 
    489  auto entry = mCachedURLs.GetOrInsertNew(key, key);
    490 
    491  entry->UpdateUsedTime();
    492 
    493  return entry->ReadOrWait(readType);
    494 }
    495 
    496 Result<nsCString, nsresult> URLPreloader::ReadURIInternal(nsIURI* uri,
    497                                                          ReadType readType) {
    498  CacheKey key = MOZ_TRY(ResolveURI(uri));
    499 
    500  return ReadInternal(key, readType);
    501 }
    502 
    503 /* static */ Result<nsCString, nsresult> URLPreloader::Read(const CacheKey& key,
    504                                                            ReadType readType) {
    505  // If we're being called before the preloader has been initialized (i.e.,
    506  // before the profile has been initialized), just fall back to a synchronous
    507  // read. This happens when we're reading .ini and preference files that are
    508  // needed to locate and initialize the profile.
    509  if (!sInitialized) {
    510    return URLEntry(key).Read();
    511  }
    512 
    513  return GetSingleton().ReadInternal(key, readType);
    514 }
    515 
    516 /* static */ Result<nsCString, nsresult> URLPreloader::ReadURI(
    517    nsIURI* uri, ReadType readType) {
    518  if (!sInitialized) {
    519    return Err(NS_ERROR_NOT_INITIALIZED);
    520  }
    521 
    522  return GetSingleton().ReadURIInternal(uri, readType);
    523 }
    524 
    525 /* static */ Result<nsCString, nsresult> URLPreloader::ReadFile(
    526    nsIFile* file, ReadType readType) {
    527  return Read(CacheKey(file), readType);
    528 }
    529 
    530 /* static */ Result<nsCString, nsresult> URLPreloader::Read(
    531    FileLocation& location, ReadType readType) {
    532  if (location.IsZip()) {
    533    if (location.GetBaseZip()) {
    534      nsCString path;
    535      location.GetPath(path);
    536      return ReadZip(location.GetBaseZip(), path);
    537    }
    538    return URLEntry::ReadLocation(location);
    539  }
    540 
    541  nsCOMPtr<nsIFile> file = location.GetBaseFile();
    542  return ReadFile(file, readType);
    543 }
    544 
    545 /* static */ Result<nsCString, nsresult> URLPreloader::ReadZip(
    546    nsZipArchive* zip, const nsACString& path, ReadType readType) {
    547  // If the zip archive belongs to an Omnijar location, map it to a cache
    548  // entry, and cache it as normal. Otherwise, simply read the entry
    549  // synchronously, since other JAR archives are currently unsupported by the
    550  // cache.
    551  RefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::GRE);
    552  if (zip == reader) {
    553    CacheKey key(CacheKey::TypeGREJar, path);
    554    return Read(key, readType);
    555  }
    556 
    557  reader = Omnijar::GetReader(Omnijar::APP);
    558  if (zip == reader) {
    559    CacheKey key(CacheKey::TypeAppJar, path);
    560    return Read(key, readType);
    561  }
    562 
    563  // Not an Omnijar archive, so just read it directly.
    564  FileLocation location(zip, path);
    565  return URLEntry::ReadLocation(location);
    566 }
    567 
    568 Result<URLPreloader::CacheKey, nsresult> URLPreloader::ResolveURI(nsIURI* uri) {
    569  nsCString spec;
    570  nsCString scheme;
    571  MOZ_TRY(uri->GetSpec(spec));
    572  MOZ_TRY(uri->GetScheme(scheme));
    573 
    574  nsCOMPtr<nsIURI> resolved;
    575 
    576  // If the URI is a resource: or chrome: URI, first resolve it to the
    577  // underlying URI that it wraps.
    578  if (scheme.EqualsLiteral("resource")) {
    579    MOZ_TRY(mResProto->ResolveURI(uri, spec));
    580    MOZ_TRY(NS_NewURI(getter_AddRefs(resolved), spec));
    581  } else if (scheme.EqualsLiteral("chrome")) {
    582    MOZ_TRY(mChromeReg->ConvertChromeURL(uri, getter_AddRefs(resolved)));
    583    MOZ_TRY(resolved->GetSpec(spec));
    584  } else {
    585    resolved = uri;
    586  }
    587  MOZ_TRY(resolved->GetScheme(scheme));
    588 
    589  // Try the GRE and App Omnijar prefixes.
    590  if (mGREPrefix.Length() && StartsWith(spec, mGREPrefix)) {
    591    return CacheKey(CacheKey::TypeGREJar, Substring(spec, mGREPrefix.Length()));
    592  }
    593 
    594  if (mAppPrefix.Length() && StartsWith(spec, mAppPrefix)) {
    595    return CacheKey(CacheKey::TypeAppJar, Substring(spec, mAppPrefix.Length()));
    596  }
    597 
    598  // Try for a file URI.
    599  if (scheme.EqualsLiteral("file")) {
    600    nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(resolved);
    601    MOZ_ASSERT(fileURL);
    602 
    603    nsCOMPtr<nsIFile> file;
    604    MOZ_TRY(fileURL->GetFile(getter_AddRefs(file)));
    605 
    606    nsString path;
    607    MOZ_TRY(file->GetPath(path));
    608 
    609    return CacheKey(CacheKey::TypeFile, NS_ConvertUTF16toUTF8(path));
    610  }
    611 
    612  // Not a file or Omnijar URI, so currently unsupported.
    613  return Err(NS_ERROR_INVALID_ARG);
    614 }
    615 
    616 size_t URLPreloader::ShallowSizeOfIncludingThis(
    617    mozilla::MallocSizeOf mallocSizeOf) {
    618  return (mallocSizeOf(this) +
    619          mAppPrefix.SizeOfExcludingThisEvenIfShared(mallocSizeOf) +
    620          mGREPrefix.SizeOfExcludingThisEvenIfShared(mallocSizeOf) +
    621          mCachedURLs.ShallowSizeOfExcludingThis(mallocSizeOf));
    622 }
    623 
    624 Result<FileLocation, nsresult> URLPreloader::CacheKey::ToFileLocation() {
    625  if (mType == TypeFile) {
    626    nsCOMPtr<nsIFile> file;
    627    MOZ_TRY(
    628        NS_NewLocalFile(NS_ConvertUTF8toUTF16(mPath), getter_AddRefs(file)));
    629    return FileLocation(file);
    630  }
    631 
    632  RefPtr<nsZipArchive> zip = Archive();
    633  return FileLocation(zip, mPath);
    634 }
    635 
    636 Result<nsCString, nsresult> URLPreloader::URLEntry::Read() {
    637  FileLocation location = MOZ_TRY(ToFileLocation());
    638 
    639  mData = MOZ_TRY(ReadLocation(location));
    640  return mData;
    641 }
    642 
    643 /* static */ Result<nsCString, nsresult> URLPreloader::URLEntry::ReadLocation(
    644    FileLocation& location) {
    645  FileLocation::Data data;
    646  MOZ_TRY(location.GetData(data));
    647 
    648  uint32_t size;
    649  MOZ_TRY(data.GetSize(&size));
    650 
    651  nsCString result;
    652  result.SetLength(size);
    653  MOZ_TRY(data.Copy(result.BeginWriting(), size));
    654 
    655  return std::move(result);
    656 }
    657 
    658 Result<nsCString, nsresult> URLPreloader::URLEntry::ReadOrWait(
    659    ReadType readType) {
    660  auto now = TimeStamp::Now();
    661  LOG(Info, "Reading %s\n", mPath.get());
    662  auto cleanup = MakeScopeExit([&]() {
    663    LOG(Info, "Read in %fms\n", (TimeStamp::Now() - now).ToMilliseconds());
    664  });
    665 
    666  if (mResultCode == NS_ERROR_NOT_INITIALIZED) {
    667    MonitorAutoLock mal(GetSingleton().mMonitor);
    668 
    669    while (mResultCode == NS_ERROR_NOT_INITIALIZED) {
    670      mal.Wait();
    671    }
    672  }
    673 
    674  if (mResultCode == NS_OK && mData.IsVoid()) {
    675    LOG(Info, "Reading synchronously...\n");
    676    return Read();
    677  }
    678 
    679  if (NS_FAILED(mResultCode)) {
    680    return Err(mResultCode);
    681  }
    682 
    683  nsCString res = mData;
    684 
    685  if (readType == Forget) {
    686    mData.SetIsVoid(true);
    687  }
    688  return res;
    689 }
    690 
    691 inline URLPreloader::CacheKey::CacheKey(InputBuffer& buffer) {
    692  Code(buffer);
    693  MOZ_DIAGNOSTIC_ASSERT(
    694      mType == TypeAppJar || mType == TypeGREJar || mType == TypeFile,
    695      "mType should be valid");
    696 }
    697 
    698 NS_IMPL_ISUPPORTS(URLPreloader, nsIMemoryReporter)
    699 
    700 #undef LOG
    701 
    702 }  // namespace mozilla