summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/loader/URLPreloader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/xpconnect/loader/URLPreloader.cpp')
-rw-r--r--js/xpconnect/loader/URLPreloader.cpp707
1 files changed, 707 insertions, 0 deletions
diff --git a/js/xpconnect/loader/URLPreloader.cpp b/js/xpconnect/loader/URLPreloader.cpp
new file mode 100644
index 0000000000..c5bcbad5bb
--- /dev/null
+++ b/js/xpconnect/loader/URLPreloader.cpp
@@ -0,0 +1,707 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScriptPreloader-inl.h"
+#include "mozilla/URLPreloader.h"
+#include "mozilla/loader/AutoMemMap.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/IOBuffers.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Vector.h"
+#include "mozilla/scache/StartupCache.h"
+
+#include "crc32c.h"
+#include "MainThreadUtils.h"
+#include "nsPrintfCString.h"
+#include "nsDebug.h"
+#include "nsIFile.h"
+#include "nsIFileURL.h"
+#include "nsNetUtil.h"
+#include "nsPromiseFlatString.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "nsZipArchive.h"
+#include "xpcpublic.h"
+
+namespace mozilla {
+namespace {
+static LazyLogModule gURLLog("URLPreloader");
+
+#define LOG(level, ...) MOZ_LOG(gURLLog, LogLevel::level, (__VA_ARGS__))
+
+template <typename T>
+bool StartsWith(const T& haystack, const T& needle) {
+ return StringHead(haystack, needle.Length()) == needle;
+}
+} // anonymous namespace
+
+using namespace mozilla::loader;
+using mozilla::scache::StartupCache;
+
+nsresult URLPreloader::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ MOZ_COLLECT_REPORT("explicit/url-preloader/other", KIND_HEAP, UNITS_BYTES,
+ ShallowSizeOfIncludingThis(MallocSizeOf),
+ "Memory used by the URL preloader service itself.");
+
+ for (const auto& elem : mCachedURLs.Values()) {
+ nsAutoCString pathName;
+ pathName.Append(elem->mPath);
+ // The backslashes will automatically be replaced with slashes in
+ // about:memory, without splitting each path component into a separate
+ // branch in the memory report tree.
+ pathName.ReplaceChar('/', '\\');
+
+ nsPrintfCString path("explicit/url-preloader/cached-urls/%s/[%s]",
+ elem->TypeString(), pathName.get());
+
+ aHandleReport->Callback(
+ ""_ns, path, KIND_HEAP, UNITS_BYTES,
+ elem->SizeOfIncludingThis(MallocSizeOf),
+ nsLiteralCString("Memory used to hold cache data for files which "
+ "have been read or pre-loaded during this session."),
+ aData);
+ }
+
+ return NS_OK;
+}
+
+// static
+already_AddRefed<URLPreloader> URLPreloader::Create(bool* aInitialized) {
+ // The static APIs like URLPreloader::Read work in the child process because
+ // they fall back to a synchronous read. The actual preloader must be
+ // explicitly initialized, and this should only be done in the parent.
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+
+ RefPtr<URLPreloader> preloader = new URLPreloader();
+ if (preloader->InitInternal().isOk()) {
+ *aInitialized = true;
+ RegisterWeakMemoryReporter(preloader);
+ } else {
+ *aInitialized = false;
+ }
+
+ return preloader.forget();
+}
+
+URLPreloader& URLPreloader::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = Create(&sInitialized);
+ ClearOnShutdown(&sSingleton);
+ }
+
+ return *sSingleton;
+}
+
+bool URLPreloader::sInitialized = false;
+
+StaticRefPtr<URLPreloader> URLPreloader::sSingleton;
+
+URLPreloader::~URLPreloader() {
+ if (sInitialized) {
+ UnregisterWeakMemoryReporter(this);
+ sInitialized = false;
+ }
+}
+
+Result<Ok, nsresult> URLPreloader::InitInternal() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ if (Omnijar::HasOmnijar(Omnijar::GRE)) {
+ MOZ_TRY(Omnijar::GetURIString(Omnijar::GRE, mGREPrefix));
+ }
+ if (Omnijar::HasOmnijar(Omnijar::APP)) {
+ MOZ_TRY(Omnijar::GetURIString(Omnijar::APP, mAppPrefix));
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsIProtocolHandler> ph;
+ MOZ_TRY(ios->GetProtocolHandler("resource", getter_AddRefs(ph)));
+
+ mResProto = do_QueryInterface(ph, &rv);
+ MOZ_TRY(rv);
+
+ mChromeReg = services::GetChromeRegistry();
+ if (!mChromeReg) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ MOZ_TRY(NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(mProfD)));
+
+ return Ok();
+}
+
+URLPreloader& URLPreloader::ReInitialize() {
+ MOZ_ASSERT(sSingleton);
+ sSingleton = nullptr;
+ sSingleton = Create(&sInitialized);
+ return *sSingleton;
+}
+
+Result<nsCOMPtr<nsIFile>, nsresult> URLPreloader::GetCacheFile(
+ const nsAString& suffix) {
+ if (!mProfD) {
+ return Err(NS_ERROR_NOT_INITIALIZED);
+ }
+
+ nsCOMPtr<nsIFile> cacheFile;
+ MOZ_TRY(mProfD->Clone(getter_AddRefs(cacheFile)));
+
+ MOZ_TRY(cacheFile->AppendNative("startupCache"_ns));
+ Unused << cacheFile->Create(nsIFile::DIRECTORY_TYPE, 0777);
+
+ MOZ_TRY(cacheFile->Append(u"urlCache"_ns + suffix));
+
+ return std::move(cacheFile);
+}
+
+static const uint8_t URL_MAGIC[] = "mozURLcachev003";
+
+Result<nsCOMPtr<nsIFile>, nsresult> URLPreloader::FindCacheFile() {
+ if (StartupCache::GetIgnoreDiskCache()) {
+ return Err(NS_ERROR_ABORT);
+ }
+
+ nsCOMPtr<nsIFile> cacheFile;
+ MOZ_TRY_VAR(cacheFile, GetCacheFile(u".bin"_ns));
+
+ bool exists;
+ MOZ_TRY(cacheFile->Exists(&exists));
+ if (exists) {
+ MOZ_TRY(cacheFile->MoveTo(nullptr, u"urlCache-current.bin"_ns));
+ } else {
+ MOZ_TRY(cacheFile->SetLeafName(u"urlCache-current.bin"_ns));
+ MOZ_TRY(cacheFile->Exists(&exists));
+ if (!exists) {
+ return Err(NS_ERROR_FILE_NOT_FOUND);
+ }
+ }
+
+ return std::move(cacheFile);
+}
+
+Result<Ok, nsresult> URLPreloader::WriteCache() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(mStartupFinished);
+
+ // The script preloader might call us a second time, if it has to re-write
+ // its cache after a cache flush. We don't care about cache flushes, since
+ // our cache doesn't store any file data, only paths. And we currently clear
+ // our cached file list after the first write, which means that a second
+ // write would (aside from breaking the invariant that we never touch
+ // mCachedURLs off-main-thread after the first write, and trigger a data
+ // race) mean we get no pre-loading on the next startup.
+ if (mCacheWritten) {
+ return Ok();
+ }
+ mCacheWritten = true;
+
+ LOG(Debug, "Writing cache...");
+
+ nsCOMPtr<nsIFile> cacheFile;
+ MOZ_TRY_VAR(cacheFile, GetCacheFile(u"-new.bin"_ns));
+
+ bool exists;
+ MOZ_TRY(cacheFile->Exists(&exists));
+ if (exists) {
+ MOZ_TRY(cacheFile->Remove(false));
+ }
+
+ {
+ AutoFDClose fd;
+ MOZ_TRY(cacheFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, 0644,
+ &fd.rwget()));
+
+ nsTArray<URLEntry*> entries;
+ for (const auto& entry : mCachedURLs.Values()) {
+ if (entry->mReadTime) {
+ entries.AppendElement(entry.get());
+ }
+ }
+
+ entries.Sort(URLEntry::Comparator());
+
+ OutputBuffer buf;
+ for (auto entry : entries) {
+ entry->Code(buf);
+ }
+
+ uint8_t headerSize[4];
+ LittleEndian::writeUint32(headerSize, buf.cursor());
+
+ uint8_t crc[4];
+ LittleEndian::writeUint32(crc, ComputeCrc32c(~0, buf.Get(), buf.cursor()));
+
+ MOZ_TRY(Write(fd, URL_MAGIC, sizeof(URL_MAGIC)));
+ MOZ_TRY(Write(fd, headerSize, sizeof(headerSize)));
+ MOZ_TRY(Write(fd, crc, sizeof(crc)));
+ MOZ_TRY(Write(fd, buf.Get(), buf.cursor()));
+ }
+
+ MOZ_TRY(cacheFile->MoveTo(nullptr, u"urlCache.bin"_ns));
+
+ NS_DispatchToMainThread(
+ NewRunnableMethod("URLPreloader::Cleanup", this, &URLPreloader::Cleanup));
+
+ return Ok();
+}
+
+void URLPreloader::Cleanup() { mCachedURLs.Clear(); }
+
+Result<Ok, nsresult> URLPreloader::ReadCache(
+ LinkedList<URLEntry>& pendingURLs) {
+ LOG(Debug, "Reading cache...");
+
+ nsCOMPtr<nsIFile> cacheFile;
+ MOZ_TRY_VAR(cacheFile, FindCacheFile());
+
+ AutoMemMap cache;
+ MOZ_TRY(cache.init(cacheFile));
+
+ auto size = cache.size();
+
+ uint32_t headerSize;
+ uint32_t crc;
+ if (size < sizeof(URL_MAGIC) + sizeof(headerSize) + sizeof(crc)) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ auto data = cache.get<uint8_t>();
+ auto end = data + size;
+
+ if (memcmp(URL_MAGIC, data.get(), sizeof(URL_MAGIC))) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+ data += sizeof(URL_MAGIC);
+
+ headerSize = LittleEndian::readUint32(data.get());
+ data += sizeof(headerSize);
+
+ crc = LittleEndian::readUint32(data.get());
+ data += sizeof(crc);
+
+ if (data + headerSize > end) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ if (crc != ComputeCrc32c(~0, data.get(), headerSize)) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ {
+ mMonitor.AssertCurrentThreadOwns();
+
+ auto cleanup = MakeScopeExit([&]() {
+ while (auto* elem = pendingURLs.getFirst()) {
+ elem->remove();
+ }
+ mCachedURLs.Clear();
+ });
+
+ Range<uint8_t> header(data, data + headerSize);
+ data += headerSize;
+
+ InputBuffer buf(header);
+ while (!buf.finished()) {
+ CacheKey key(buf);
+
+ LOG(Debug, "Cached file: %s %s", key.TypeString(), key.mPath.get());
+
+ // Don't bother doing anything else if the key didn't load correctly.
+ // We're going to throw it out right away, and it is possible that this
+ // leads to pendingURLs getting into a weird state.
+ if (buf.error()) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ auto entry = mCachedURLs.GetOrInsertNew(key, key);
+ entry->mResultCode = NS_ERROR_NOT_INITIALIZED;
+
+ if (entry->isInList()) {
+#ifdef NIGHTLY_BUILD
+ MOZ_DIAGNOSTIC_ASSERT(pendingURLs.contains(entry),
+ "Entry should be in pendingURLs");
+ MOZ_DIAGNOSTIC_ASSERT(key.mPath.Length() > 0,
+ "Path should be non-empty");
+ MOZ_DIAGNOSTIC_ASSERT(false, "Entry should be new and not in any list");
+#endif
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ pendingURLs.insertBack(entry);
+ }
+
+ MOZ_RELEASE_ASSERT(!buf.error(),
+ "We should have already bailed on an error");
+
+ cleanup.release();
+ }
+
+ return Ok();
+}
+
+void URLPreloader::BackgroundReadFiles() {
+ auto cleanup = MakeScopeExit([&]() {
+ auto lock = mReaderThread.Lock();
+ auto& readerThread = lock.ref();
+ NS_DispatchToMainThread(NewRunnableMethod(
+ "nsIThread::AsyncShutdown", readerThread, &nsIThread::AsyncShutdown));
+
+ readerThread = nullptr;
+ });
+
+ Vector<nsZipCursor> cursors;
+ LinkedList<URLEntry> pendingURLs;
+ {
+ MonitorAutoLock mal(mMonitor);
+
+ if (ReadCache(pendingURLs).isErr()) {
+ mReaderInitialized = true;
+ mal.NotifyAll();
+ return;
+ }
+
+ int numZipEntries = 0;
+ for (auto entry : pendingURLs) {
+ if (entry->mType != entry->TypeFile) {
+ numZipEntries++;
+ }
+ }
+ MOZ_RELEASE_ASSERT(cursors.reserve(numZipEntries));
+
+ // Initialize the zip cursors for all files in Omnijar while the monitor
+ // is locked. Omnijar is not threadsafe, so the caller of
+ // AutoBeginReading guard must ensure that no code accesses Omnijar
+ // until this segment is done. Once the cursors have been initialized,
+ // the actual reading and decompression can safely be done off-thread,
+ // as is the case for thread-retargeted jar: channels.
+ for (auto entry : pendingURLs) {
+ if (entry->mType == entry->TypeFile) {
+ continue;
+ }
+
+ RefPtr<nsZipArchive> zip = entry->Archive();
+ if (!zip) {
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "Failed to get Omnijar %s archive for entry (path: \"%s\")",
+ entry->TypeString(), entry->mPath.get());
+ }
+
+ auto item = zip->GetItem(entry->mPath.get());
+ if (!item) {
+ entry->mResultCode = NS_ERROR_FILE_NOT_FOUND;
+ continue;
+ }
+
+ size_t size = item->RealSize();
+
+ entry->mData.SetLength(size);
+ auto data = entry->mData.BeginWriting();
+
+ cursors.infallibleEmplaceBack(item, zip, reinterpret_cast<uint8_t*>(data),
+ size, true);
+ }
+
+ mReaderInitialized = true;
+ mal.NotifyAll();
+ }
+
+ // Loop over the entries, read the file's contents, store them in the
+ // entry's mData pointer, and notify any waiting threads to check for
+ // completion.
+ uint32_t i = 0;
+ for (auto entry : pendingURLs) {
+ // If there is any other error code, the entry has already failed at
+ // this point, so don't bother trying to read it again.
+ if (entry->mResultCode != NS_ERROR_NOT_INITIALIZED) {
+ continue;
+ }
+
+ nsresult rv = NS_OK;
+
+ LOG(Debug, "Background reading %s file %s", entry->TypeString(),
+ entry->mPath.get());
+
+ if (entry->mType == entry->TypeFile) {
+ auto result = entry->Read();
+ if (result.isErr()) {
+ rv = result.unwrapErr();
+ }
+ } else {
+ auto& cursor = cursors[i++];
+
+ uint32_t len;
+ cursor.Copy(&len);
+ if (len != entry->mData.Length()) {
+ entry->mData.Truncate();
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+
+ entry->mResultCode = rv;
+ mMonitor.NotifyAll();
+ }
+
+ // We're done reading pending entries, so clear the list.
+ pendingURLs.clear();
+}
+
+void URLPreloader::BeginBackgroundRead() {
+ auto lock = mReaderThread.Lock();
+ auto& readerThread = lock.ref();
+ if (!readerThread && !mReaderInitialized && sInitialized) {
+ nsresult rv;
+ rv = NS_NewNamedThread("BGReadURLs", getter_AddRefs(readerThread));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod("URLPreloader::BackgroundReadFiles", this,
+ &URLPreloader::BackgroundReadFiles);
+ rv = readerThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // If we can't launch the task, just destroy the thread
+ readerThread = nullptr;
+ return;
+ }
+ }
+}
+
+Result<nsCString, nsresult> URLPreloader::ReadInternal(const CacheKey& key,
+ ReadType readType) {
+ if (mStartupFinished || !mReaderInitialized) {
+ URLEntry entry(key);
+
+ return entry.Read();
+ }
+
+ auto entry = mCachedURLs.GetOrInsertNew(key, key);
+
+ entry->UpdateUsedTime();
+
+ return entry->ReadOrWait(readType);
+}
+
+Result<nsCString, nsresult> URLPreloader::ReadURIInternal(nsIURI* uri,
+ ReadType readType) {
+ CacheKey key;
+ MOZ_TRY_VAR(key, ResolveURI(uri));
+
+ return ReadInternal(key, readType);
+}
+
+/* static */ Result<nsCString, nsresult> URLPreloader::Read(const CacheKey& key,
+ ReadType readType) {
+ // If we're being called before the preloader has been initialized (i.e.,
+ // before the profile has been initialized), just fall back to a synchronous
+ // read. This happens when we're reading .ini and preference files that are
+ // needed to locate and initialize the profile.
+ if (!sInitialized) {
+ return URLEntry(key).Read();
+ }
+
+ return GetSingleton().ReadInternal(key, readType);
+}
+
+/* static */ Result<nsCString, nsresult> URLPreloader::ReadURI(
+ nsIURI* uri, ReadType readType) {
+ if (!sInitialized) {
+ return Err(NS_ERROR_NOT_INITIALIZED);
+ }
+
+ return GetSingleton().ReadURIInternal(uri, readType);
+}
+
+/* static */ Result<nsCString, nsresult> URLPreloader::ReadFile(
+ nsIFile* file, ReadType readType) {
+ return Read(CacheKey(file), readType);
+}
+
+/* static */ Result<nsCString, nsresult> URLPreloader::Read(
+ FileLocation& location, ReadType readType) {
+ if (location.IsZip()) {
+ if (location.GetBaseZip()) {
+ nsCString path;
+ location.GetPath(path);
+ return ReadZip(location.GetBaseZip(), path);
+ }
+ return URLEntry::ReadLocation(location);
+ }
+
+ nsCOMPtr<nsIFile> file = location.GetBaseFile();
+ return ReadFile(file, readType);
+}
+
+/* static */ Result<nsCString, nsresult> URLPreloader::ReadZip(
+ nsZipArchive* zip, const nsACString& path, ReadType readType) {
+ // If the zip archive belongs to an Omnijar location, map it to a cache
+ // entry, and cache it as normal. Otherwise, simply read the entry
+ // synchronously, since other JAR archives are currently unsupported by the
+ // cache.
+ RefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::GRE);
+ if (zip == reader) {
+ CacheKey key(CacheKey::TypeGREJar, path);
+ return Read(key, readType);
+ }
+
+ reader = Omnijar::GetReader(Omnijar::APP);
+ if (zip == reader) {
+ CacheKey key(CacheKey::TypeAppJar, path);
+ return Read(key, readType);
+ }
+
+ // Not an Omnijar archive, so just read it directly.
+ FileLocation location(zip, PromiseFlatCString(path).BeginReading());
+ return URLEntry::ReadLocation(location);
+}
+
+Result<URLPreloader::CacheKey, nsresult> URLPreloader::ResolveURI(nsIURI* uri) {
+ nsCString spec;
+ nsCString scheme;
+ MOZ_TRY(uri->GetSpec(spec));
+ MOZ_TRY(uri->GetScheme(scheme));
+
+ nsCOMPtr<nsIURI> resolved;
+
+ // If the URI is a resource: or chrome: URI, first resolve it to the
+ // underlying URI that it wraps.
+ if (scheme.EqualsLiteral("resource")) {
+ MOZ_TRY(mResProto->ResolveURI(uri, spec));
+ MOZ_TRY(NS_NewURI(getter_AddRefs(resolved), spec));
+ } else if (scheme.EqualsLiteral("chrome")) {
+ MOZ_TRY(mChromeReg->ConvertChromeURL(uri, getter_AddRefs(resolved)));
+ MOZ_TRY(resolved->GetSpec(spec));
+ } else {
+ resolved = uri;
+ }
+ MOZ_TRY(resolved->GetScheme(scheme));
+
+ // Try the GRE and App Omnijar prefixes.
+ if (mGREPrefix.Length() && StartsWith(spec, mGREPrefix)) {
+ return CacheKey(CacheKey::TypeGREJar, Substring(spec, mGREPrefix.Length()));
+ }
+
+ if (mAppPrefix.Length() && StartsWith(spec, mAppPrefix)) {
+ return CacheKey(CacheKey::TypeAppJar, Substring(spec, mAppPrefix.Length()));
+ }
+
+ // Try for a file URI.
+ if (scheme.EqualsLiteral("file")) {
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(resolved);
+ MOZ_ASSERT(fileURL);
+
+ nsCOMPtr<nsIFile> file;
+ MOZ_TRY(fileURL->GetFile(getter_AddRefs(file)));
+
+ nsString path;
+ MOZ_TRY(file->GetPath(path));
+
+ return CacheKey(CacheKey::TypeFile, NS_ConvertUTF16toUTF8(path));
+ }
+
+ // Not a file or Omnijar URI, so currently unsupported.
+ return Err(NS_ERROR_INVALID_ARG);
+}
+
+size_t URLPreloader::ShallowSizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) {
+ return (mallocSizeOf(this) +
+ mAppPrefix.SizeOfExcludingThisEvenIfShared(mallocSizeOf) +
+ mGREPrefix.SizeOfExcludingThisEvenIfShared(mallocSizeOf) +
+ mCachedURLs.ShallowSizeOfExcludingThis(mallocSizeOf));
+}
+
+Result<FileLocation, nsresult> URLPreloader::CacheKey::ToFileLocation() {
+ if (mType == TypeFile) {
+ nsCOMPtr<nsIFile> file;
+ MOZ_TRY(NS_NewLocalFile(NS_ConvertUTF8toUTF16(mPath), false,
+ getter_AddRefs(file)));
+ return FileLocation(file);
+ }
+
+ RefPtr<nsZipArchive> zip = Archive();
+ return FileLocation(zip, mPath.get());
+}
+
+Result<nsCString, nsresult> URLPreloader::URLEntry::Read() {
+ FileLocation location;
+ MOZ_TRY_VAR(location, ToFileLocation());
+
+ MOZ_TRY_VAR(mData, ReadLocation(location));
+ return mData;
+}
+
+/* static */ Result<nsCString, nsresult> URLPreloader::URLEntry::ReadLocation(
+ FileLocation& location) {
+ FileLocation::Data data;
+ MOZ_TRY(location.GetData(data));
+
+ uint32_t size;
+ MOZ_TRY(data.GetSize(&size));
+
+ nsCString result;
+ result.SetLength(size);
+ MOZ_TRY(data.Copy(result.BeginWriting(), size));
+
+ return std::move(result);
+}
+
+Result<nsCString, nsresult> URLPreloader::URLEntry::ReadOrWait(
+ ReadType readType) {
+ auto now = TimeStamp::Now();
+ LOG(Info, "Reading %s\n", mPath.get());
+ auto cleanup = MakeScopeExit([&]() {
+ LOG(Info, "Read in %fms\n", (TimeStamp::Now() - now).ToMilliseconds());
+ });
+
+ if (mResultCode == NS_ERROR_NOT_INITIALIZED) {
+ MonitorAutoLock mal(GetSingleton().mMonitor);
+
+ while (mResultCode == NS_ERROR_NOT_INITIALIZED) {
+ mal.Wait();
+ }
+ }
+
+ if (mResultCode == NS_OK && mData.IsVoid()) {
+ LOG(Info, "Reading synchronously...\n");
+ return Read();
+ }
+
+ if (NS_FAILED(mResultCode)) {
+ return Err(mResultCode);
+ }
+
+ nsCString res = mData;
+
+ if (readType == Forget) {
+ mData.SetIsVoid(true);
+ }
+ return res;
+}
+
+inline URLPreloader::CacheKey::CacheKey(InputBuffer& buffer) {
+ Code(buffer);
+ MOZ_DIAGNOSTIC_ASSERT(
+ mType == TypeAppJar || mType == TypeGREJar || mType == TypeFile,
+ "mType should be valid");
+}
+
+NS_IMPL_ISUPPORTS(URLPreloader, nsIMemoryReporter)
+
+#undef LOG
+
+} // namespace mozilla