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