URLPreloader.h (10070B)
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/DataMutex.h" 10 #include "mozilla/FileLocation.h" 11 #include "mozilla/HashFunctions.h" 12 #include "mozilla/LinkedList.h" 13 #include "mozilla/MemoryReporting.h" 14 #include "mozilla/Monitor.h" 15 #include "mozilla/Omnijar.h" 16 #include "mozilla/Result.h" 17 #include "nsClassHashtable.h" 18 #include "nsHashKeys.h" 19 #include "nsIChromeRegistry.h" 20 #include "nsIFile.h" 21 #include "nsIURI.h" 22 #include "nsIMemoryReporter.h" 23 #include "nsIResProtocolHandler.h" 24 #include "nsIThread.h" 25 #include "nsReadableUtils.h" 26 27 class nsZipArchive; 28 29 namespace mozilla { 30 namespace loader { 31 class InputBuffer; 32 } 33 34 using namespace mozilla::loader; 35 36 class ScriptPreloader; 37 38 /** 39 * A singleton class to manage loading local URLs during startup, recording 40 * them, and pre-loading them during early startup in the next session. URLs 41 * that are not already loaded (or already being pre-loaded) when required are 42 * read synchronously from disk, and (if startup is not already complete) 43 * added to the pre-load list for the next session. 44 */ 45 class URLPreloader final : public nsIMemoryReporter { 46 MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) 47 48 URLPreloader() = default; 49 50 public: 51 NS_DECL_THREADSAFE_ISUPPORTS 52 NS_DECL_NSIMEMORYREPORTER 53 54 static URLPreloader& GetSingleton(); 55 56 // The type of read operation to perform. 57 enum ReadType { 58 // Read the file and then immediately forget its data. 59 Forget, 60 // Read the file and retain its data for the next caller. 61 Retain, 62 }; 63 64 // Helpers to read the contents of files or JAR archive entries with various 65 // representations. If the preloader has not yet been initialized, or the 66 // given location is not supported by the cache, the entries will be read 67 // synchronously, and not stored in the cache. 68 static Result<nsCString, nsresult> Read(FileLocation& location, 69 ReadType readType = Forget); 70 71 static Result<nsCString, nsresult> ReadURI(nsIURI* uri, 72 ReadType readType = Forget); 73 74 static Result<nsCString, nsresult> ReadFile(nsIFile* file, 75 ReadType readType = Forget); 76 77 static Result<nsCString, nsresult> ReadZip(nsZipArchive* archive, 78 const nsACString& path, 79 ReadType readType = Forget); 80 81 void SetStartupFinished() { mStartupFinished = true; } 82 83 private: 84 struct CacheKey; 85 86 Result<nsCString, nsresult> ReadInternal(const CacheKey& key, 87 ReadType readType); 88 89 Result<nsCString, nsresult> ReadURIInternal(nsIURI* uri, ReadType readType); 90 91 Result<nsCString, nsresult> ReadFileInternal(nsIFile* file, 92 ReadType readType); 93 94 static Result<nsCString, nsresult> Read(const CacheKey& key, 95 ReadType readType); 96 97 static bool sInitialized; 98 99 static mozilla::StaticRefPtr<URLPreloader> sSingleton; 100 101 protected: 102 friend class AddonManagerStartup; 103 friend class ScriptPreloader; 104 105 virtual ~URLPreloader(); 106 107 Result<Ok, nsresult> WriteCache(); 108 109 static URLPreloader& ReInitialize(); 110 111 // Clear leftover entries after the cache has been written. 112 void Cleanup(); 113 114 // Begins reading files off-thread, and ensures that initialization has 115 // completed before leaving the current scope. The caller *must* ensure that 116 // no code on the main thread access Omnijar, either directly or indirectly, 117 // for the lifetime of this guard object. 118 struct MOZ_RAII AutoBeginReading final { 119 AutoBeginReading() { GetSingleton().BeginBackgroundRead(); } 120 121 ~AutoBeginReading() { 122 auto& reader = GetSingleton(); 123 124 MonitorAutoLock mal(reader.mMonitor); 125 126 while (!reader.mReaderInitialized && URLPreloader::sInitialized) { 127 mal.Wait(); 128 } 129 } 130 }; 131 132 private: 133 // Represents a key for an entry in the URI cache, based on its file or JAR 134 // location. 135 struct CacheKey { 136 // The type of the entry. TypeAppJar and TypeGREJar entries are in the 137 // app-specific or toolkit Omnijar files, and are handled specially. 138 // TypeFile entries are plain files in the filesystem. 139 enum EntryType : uint8_t { 140 TypeAppJar, 141 TypeGREJar, 142 TypeFile, 143 }; 144 145 CacheKey() = default; 146 CacheKey(const CacheKey& other) = default; 147 148 CacheKey(EntryType type, const nsACString& path) 149 : mType(type), mPath(path) {} 150 151 explicit CacheKey(nsIFile* file) : mType(TypeFile) { 152 nsString path; 153 MOZ_ALWAYS_SUCCEEDS(file->GetPath(path)); 154 MOZ_DIAGNOSTIC_ASSERT(path.Length() > 0); 155 CopyUTF16toUTF8(path, mPath); 156 } 157 158 explicit inline CacheKey(InputBuffer& buffer); 159 160 // Encodes or decodes the cache key for storage in a session cache file. 161 template <typename Buffer> 162 void Code(Buffer& buffer) { 163 buffer.codeUint8(*reinterpret_cast<uint8_t*>(&mType)); 164 buffer.codeString(mPath); 165 MOZ_DIAGNOSTIC_ASSERT(mPath.Length() > 0); 166 } 167 168 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. 176 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 188 const char* TypeString() const { 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 201 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> { 217 MOZ_IMPLICIT URLEntry(const CacheKey& key) 218 : CacheKey(key), mData(VoidCString()) {} 219 220 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 { 227 bool Equals(const URLEntry* a, const URLEntry* b) const { 228 return a->mReadTime == b->mReadTime; 229 } 230 231 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<nsCString, nsresult> Read(); 245 static Result<nsCString, nsresult> ReadLocation(FileLocation& location); 246 247 size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { 248 return (mallocSizeOf(this) + 249 mPath.SizeOfExcludingThisEvenIfShared(mallocSizeOf) + 250 mData.SizeOfExcludingThisEvenIfShared(mallocSizeOf)); 251 } 252 253 // Reads the contents of the file referenced by this entry, or wait for 254 // an off-thread read operation to finish if it is currently pending, 255 // and return the file's contents. 256 Result<nsCString, nsresult> ReadOrWait(ReadType readType); 257 258 nsCString mData; 259 260 TimeStamp mReadTime{}; 261 262 nsresult mResultCode = NS_OK; 263 }; 264 265 // Resolves the given URI to a CacheKey, if the URI is cacheable. 266 Result<CacheKey, nsresult> ResolveURI(nsIURI* uri); 267 268 static already_AddRefed<URLPreloader> Create(bool* aInitialized); 269 270 Result<Ok, nsresult> InitInternal(); 271 272 // Returns a file pointer to the (possibly nonexistent) cache file with the 273 // given suffix. 274 Result<nsCOMPtr<nsIFile>, nsresult> GetCacheFile(const nsAString& suffix); 275 // Finds the correct cache file to use for this session. 276 Result<nsCOMPtr<nsIFile>, nsresult> FindCacheFile(); 277 278 Result<Ok, nsresult> ReadCache(LinkedList<URLEntry>& pendingURLs); 279 280 void BackgroundReadFiles(); 281 void BeginBackgroundRead(); 282 283 using HashType = nsClassHashtable<nsGenericHashKey<CacheKey>, URLEntry>; 284 285 size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); 286 287 bool mStartupFinished = false; 288 bool mReaderInitialized = false; 289 290 // Only to be accessed from the cache write thread. 291 bool mCacheWritten = false; 292 293 // The prefix URLs for files in the GRE and App omni jar archives. 294 nsCString mGREPrefix; 295 nsCString mAppPrefix; 296 297 nsCOMPtr<nsIResProtocolHandler> mResProto; 298 nsCOMPtr<nsIChromeRegistry> mChromeReg; 299 nsCOMPtr<nsIFile> mProfD; 300 301 // Note: We use a RefPtr rather than an nsCOMPtr here because the 302 // AssertNoQueryNeeded checks done by getter_AddRefs happen at a time that 303 // violate data access invariants. It's wrapped in a mutex because 304 // the reader thread needs to be able to null this out to terminate itself. 305 DataMutex<RefPtr<nsIThread>> mReaderThread{"ReaderThread"}; 306 307 // A map of URL entries which have were either read this session, or read 308 // from the last session's cache file. 309 HashType mCachedURLs; 310 311 Monitor mMonitor MOZ_UNANNOTATED{"[URLPreloader::mMutex]"}; 312 }; 313 314 } // namespace mozilla 315 316 #endif // URLPreloader_h