1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #ifndef URLPreloader_h
7 #define URLPreloader_h
8 
9 #include "mozilla/FileLocation.h"
10 #include "mozilla/HashFunctions.h"
11 #include "mozilla/LinkedList.h"
12 #include "mozilla/MemoryReporting.h"
13 #include "mozilla/Monitor.h"
14 #include "mozilla/Omnijar.h"
15 #include "mozilla/Range.h"
16 #include "mozilla/Vector.h"
17 #include "mozilla/Result.h"
18 #include "nsClassHashtable.h"
19 #include "nsHashKeys.h"
20 #include "nsIChromeRegistry.h"
21 #include "nsIFile.h"
22 #include "nsIURI.h"
23 #include "nsIMemoryReporter.h"
24 #include "nsIObserver.h"
25 #include "nsIResProtocolHandler.h"
26 #include "nsIThread.h"
27 #include "nsReadableUtils.h"
28 
29 class nsZipArchive;
30 
31 namespace mozilla {
32 namespace loader {
33 class InputBuffer;
34 }
35 
36 using namespace mozilla::loader;
37 
38 class ScriptPreloader;
39 
40 /**
41  * A singleton class to manage loading local URLs during startup, recording
42  * them, and pre-loading them during early startup in the next session. URLs
43  * that are not already loaded (or already being pre-loaded) when required are
44  * read synchronously from disk, and (if startup is not already complete)
45  * added to the pre-load list for the next session.
46  */
47 class URLPreloader final : public nsIObserver, public nsIMemoryReporter {
48   MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
49 
50   URLPreloader();
51 
52  public:
53   NS_DECL_THREADSAFE_ISUPPORTS
54   NS_DECL_NSIOBSERVER
55   NS_DECL_NSIMEMORYREPORTER
56 
57   static URLPreloader& GetSingleton();
58 
59   // The type of read operation to perform.
60   enum ReadType {
61     // Read the file and then immediately forget its data.
62     Forget,
63     // Read the file and retain its data for the next caller.
64     Retain,
65   };
66 
67   // Helpers to read the contents of files or JAR archive entries with various
68   // representations. If the preloader has not yet been initialized, or the
69   // given location is not supported by the cache, the entries will be read
70   // synchronously, and not stored in the cache.
71   static Result<const nsCString, nsresult> Read(FileLocation& location,
72                                                 ReadType readType = Forget);
73 
74   static Result<const nsCString, nsresult> ReadURI(nsIURI* uri,
75                                                    ReadType readType = Forget);
76 
77   static Result<const nsCString, nsresult> ReadFile(nsIFile* file,
78                                                     ReadType readType = Forget);
79 
80   static Result<const nsCString, nsresult> ReadZip(nsZipArchive* archive,
81                                                    const nsACString& path,
82                                                    ReadType readType = Forget);
83 
84  private:
85   struct CacheKey;
86 
87   Result<const nsCString, nsresult> ReadInternal(const CacheKey& key,
88                                                  ReadType readType);
89 
90   Result<const nsCString, nsresult> ReadURIInternal(nsIURI* uri,
91                                                     ReadType readType);
92 
93   Result<const nsCString, nsresult> ReadFileInternal(nsIFile* file,
94                                                      ReadType readType);
95 
96   static Result<const nsCString, nsresult> Read(const CacheKey& key,
97                                                 ReadType readType);
98 
99   static bool sInitialized;
100 
101   static mozilla::StaticRefPtr<URLPreloader> sSingleton;
102 
103  protected:
104   friend class AddonManagerStartup;
105   friend class ScriptPreloader;
106 
107   virtual ~URLPreloader();
108 
109   Result<Ok, nsresult> WriteCache();
110 
111   static URLPreloader& ReInitialize();
112 
113   // Clear leftover entries after the cache has been written.
114   void Cleanup();
115 
116   // Begins reading files off-thread, and ensures that initialization has
117   // completed before leaving the current scope. The caller *must* ensure that
118   // no code on the main thread access Omnijar, either directly or indirectly,
119   // for the lifetime of this guard object.
120   struct MOZ_RAII AutoBeginReading final {
AutoBeginReadingfinal121     AutoBeginReading() { GetSingleton().BeginBackgroundRead(); }
122 
~AutoBeginReadingfinal123     ~AutoBeginReading() {
124       auto& reader = GetSingleton();
125 
126       MonitorAutoLock mal(reader.mMonitor);
127 
128       while (!reader.mReaderInitialized && URLPreloader::sInitialized) {
129         mal.Wait();
130       }
131     }
132   };
133 
134  private:
135   // Represents a key for an entry in the URI cache, based on its file or JAR
136   // location.
137   struct CacheKey {
138     // The type of the entry. TypeAppJar and TypeGREJar entries are in the
139     // app-specific or toolkit Omnijar files, and are handled specially.
140     // TypeFile entries are plain files in the filesystem.
141     enum EntryType : uint8_t {
142       TypeAppJar,
143       TypeGREJar,
144       TypeFile,
145     };
146 
147     CacheKey() = default;
148     CacheKey(const CacheKey& other) = default;
149 
CacheKeyCacheKey150     CacheKey(EntryType type, const nsACString& path)
151         : mType(type), mPath(path) {}
152 
CacheKeyCacheKey153     explicit CacheKey(nsIFile* file) : mType(TypeFile) {
154       nsString path;
155       MOZ_ALWAYS_SUCCEEDS(file->GetPath(path));
156       CopyUTF16toUTF8(path, mPath);
157     }
158 
159     explicit inline CacheKey(InputBuffer& buffer);
160 
161     // Encodes or decodes the cache key for storage in a session cache file.
162     template <typename Buffer>
CodeCacheKey163     void Code(Buffer& buffer) {
164       buffer.codeUint8(*reinterpret_cast<uint8_t*>(&mType));
165       buffer.codeString(mPath);
166     }
167 
HashCacheKey168     uint32_t Hash() const { return HashGeneric(mType, HashString(mPath)); }
169 
170     bool operator==(const CacheKey& other) const {
171       return mType == other.mType && mPath == other.mPath;
172     }
173 
174     // Returns the Omnijar type for this entry. This may *only* be called
175     // for Omnijar entries.
OmnijarTypeCacheKey176     Omnijar::Type OmnijarType() {
177       switch (mType) {
178         case TypeAppJar:
179           return Omnijar::APP;
180         case TypeGREJar:
181           return Omnijar::GRE;
182         default:
183           MOZ_CRASH("Unexpected entry type");
184           return Omnijar::GRE;
185       }
186     }
187 
TypeStringCacheKey188     const char* TypeString() {
189       switch (mType) {
190         case TypeAppJar:
191           return "AppJar";
192         case TypeGREJar:
193           return "GREJar";
194         case TypeFile:
195           return "File";
196       }
197       MOZ_ASSERT_UNREACHABLE("no such type");
198       return "";
199     }
200 
ArchiveCacheKey201     already_AddRefed<nsZipArchive> Archive() {
202       return Omnijar::GetReader(OmnijarType());
203     }
204 
205     Result<FileLocation, nsresult> ToFileLocation();
206 
207     EntryType mType = TypeFile;
208 
209     // The path of the entry. For Type*Jar entries, this is the path within
210     // the Omnijar archive. For TypeFile entries, this is the full path to
211     // the file.
212     nsCString mPath{};
213   };
214 
215   // Represents an entry in the URI cache.
216   struct URLEntry final : public CacheKey, public LinkedListElement<URLEntry> {
URLEntryfinal217     MOZ_IMPLICIT URLEntry(const CacheKey& key)
218         : CacheKey(key), mData(VoidCString()) {}
219 
URLEntryfinal220     explicit URLEntry(nsIFile* file) : CacheKey(file) {}
221 
222     // For use with nsTArray::Sort.
223     //
224     // Sorts entries by the time they were initially read during this
225     // session.
226     struct Comparator final {
Equalsfinal::final227       bool Equals(const URLEntry* a, const URLEntry* b) const {
228         return a->mReadTime == b->mReadTime;
229       }
230 
LessThanfinal::final231       bool LessThan(const URLEntry* a, const URLEntry* b) const {
232         return a->mReadTime < b->mReadTime;
233       }
234     };
235 
236     // Sets the first-used time of this file to the earlier of its current
237     // first-use time or the given timestamp.
238     void UpdateUsedTime(const TimeStamp& time = TimeStamp::Now()) {
239       if (!mReadTime || time < mReadTime) {
240         mReadTime = time;
241       }
242     }
243 
244     Result<const nsCString, nsresult> Read();
245     static Result<const nsCString, nsresult> ReadLocation(
246         FileLocation& location);
247 
SizeOfIncludingThisfinal248     size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
249       return (mallocSizeOf(this) +
250               mPath.SizeOfExcludingThisEvenIfShared(mallocSizeOf) +
251               mData.SizeOfExcludingThisEvenIfShared(mallocSizeOf));
252     }
253 
254     // Reads the contents of the file referenced by this entry, or wait for
255     // an off-thread read operation to finish if it is currently pending,
256     // and return the file's contents.
257     Result<const nsCString, nsresult> ReadOrWait(ReadType readType);
258 
259     nsCString mData;
260 
261     TimeStamp mReadTime{};
262 
263     nsresult mResultCode = NS_OK;
264   };
265 
266   // Resolves the given URI to a CacheKey, if the URI is cacheable.
267   Result<CacheKey, nsresult> ResolveURI(nsIURI* uri);
268 
269   Result<Ok, nsresult> InitInternal();
270 
271   // Returns a file pointer to the (possibly nonexistent) cache file with the
272   // given suffix.
273   Result<nsCOMPtr<nsIFile>, nsresult> GetCacheFile(const nsAString& suffix);
274   // Finds the correct cache file to use for this session.
275   Result<nsCOMPtr<nsIFile>, nsresult> FindCacheFile();
276 
277   Result<Ok, nsresult> ReadCache(LinkedList<URLEntry>& pendingURLs);
278 
279   void BackgroundReadFiles();
280   void BeginBackgroundRead();
281 
282   using HashType = nsClassHashtable<nsGenericHashKey<CacheKey>, URLEntry>;
283 
284   size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
285 
286   bool mStartupFinished = false;
287   bool mReaderInitialized = false;
288 
289   // Only to be accessed from the cache write thread.
290   bool mCacheWritten = false;
291 
292   // The prefix URLs for files in the GRE and App omni jar archives.
293   nsCString mGREPrefix;
294   nsCString mAppPrefix;
295 
296   nsCOMPtr<nsIResProtocolHandler> mResProto;
297   nsCOMPtr<nsIChromeRegistry> mChromeReg;
298   nsCOMPtr<nsIFile> mProfD;
299 
300   // Note: We use a RefPtr rather than an nsCOMPtr here because the
301   // AssertNoQueryNeeded checks done by getter_AddRefs happen at a time that
302   // violate data access invariants.
303   RefPtr<nsIThread> mReaderThread;
304 
305   // A map of URL entries which have were either read this session, or read
306   // from the last session's cache file.
307   HashType mCachedURLs;
308 
309   Monitor mMonitor{"[URLPreloader::mMutex]"};
310 };
311 
312 }  // namespace mozilla
313 
314 #endif  // URLPreloader_h
315