--- a/js/xpconnect/loader/ScriptPreloader-inl.h
+++ b/js/xpconnect/loader/ScriptPreloader-inl.h
@@ -35,16 +35,25 @@ public:
operator nsresult() { return mErrorValue; }
};
namespace loader {
using mozilla::dom::AutoJSAPI;
+static inline Result<Ok, nsresult>
+Write(PRFileDesc* fd, const void* data, int32_t len)
+{
+ if (PR_Write(fd, data, len) != len) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ return Ok();
+}
+
struct MOZ_RAII AutoSafeJSAPI : public AutoJSAPI
{
AutoSafeJSAPI() { Init(); }
};
static inline Result<Ok, nsresult>
WrapNSResult(PRStatus aRv)
{
@@ -289,18 +298,25 @@ public:
ElemType get()
{
if (done_) {
return nullptr;
}
return iter().Data();
}
+ const ElemType get() const
+ {
+ return const_cast<Elem*>(this)->get();
+ }
+
ElemType operator->() { return get(); }
+ const ElemType operator->() const { return get(); }
+
operator ElemType() { return get(); }
void Remove() { iter().Remove(); }
Elem& operator++()
{
MOZ_ASSERT(!done_);
--- a/js/xpconnect/loader/ScriptPreloader.cpp
+++ b/js/xpconnect/loader/ScriptPreloader.cpp
@@ -3,16 +3,18 @@
/* 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 "mozilla/ScriptPreloader.h"
#include "ScriptPreloader-inl.h"
#include "mozilla/loader/ScriptCacheActors.h"
+#include "mozilla/URLPreloader.h"
+
#include "mozilla/ArrayUtils.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/FileUtils.h"
#include "mozilla/Logging.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Services.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/ContentChild.h"
@@ -408,16 +410,20 @@ ScriptPreloader::InitCache(const nsAStri
mBaseName = basePath;
RegisterWeakMemoryReporter(this);
if (!XRE_IsParentProcess()) {
return Ok();
}
+ // Note: Code on the main thread *must not access Omnijar in any way* until
+ // this AutoBeginReading guard is destroyed.
+ URLPreloader::AutoBeginReading abr;
+
MOZ_TRY(OpenCache());
return InitCacheInternal();
}
Result<Ok, nsresult>
ScriptPreloader::InitCache(const Maybe<ipc::FileDescriptor>& cacheFile, ScriptCacheChild* cacheChild)
{
@@ -512,25 +518,16 @@ ScriptPreloader::InitCacheInternal()
mPendingScripts = Move(scripts);
cleanup.release();
}
DecodeNextBatch(OFF_THREAD_FIRST_CHUNK_SIZE);
return Ok();
}
-static inline Result<Ok, nsresult>
-Write(PRFileDesc* fd, const void* data, int32_t len)
-{
- if (PR_Write(fd, data, len) != len) {
- return Err(NS_ERROR_FAILURE);
- }
- return Ok();
-}
-
void
ScriptPreloader::PrepareCacheWriteInternal()
{
MOZ_ASSERT(NS_IsMainThread());
mMonitor.AssertCurrentThreadOwns();
auto cleanup = MakeScopeExit([&] () {
@@ -598,16 +595,18 @@ ScriptPreloader::PrepareCacheWrite()
//
// - A block of XDR data for the encoded scripts, with each script's data at
// an offset from the start of the block, as specified above.
Result<Ok, nsresult>
ScriptPreloader::WriteCache()
{
MOZ_ASSERT(!NS_IsMainThread());
+ URLPreloader::GetSingleton().WriteCache();
+
if (!mDataPrepared && !mSaveComplete) {
MOZ_ASSERT(!mBlockedOnSyncDispatch);
mBlockedOnSyncDispatch = true;
MonitorAutoUnlock mau(mSaveMonitor);
NS_DispatchToMainThread(
NewRunnableMethod("ScriptPreloader::PrepareCacheWrite",
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/loader/URLPreloader.cpp
@@ -0,0 +1,690 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=99: */
+/* 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 "mozilla/URLPreloader.h"
+#include "mozilla/loader/AutoMemMap.h"
+#include "ScriptPreloader-inl.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Vector.h"
+
+#include "MainThreadUtils.h"
+#include "nsPrintfCString.h"
+#include "nsDebug.h"
+#include "nsIFile.h"
+#include "nsIFileURL.h"
+#include "nsIObserverService.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "nsZipArchive.h"
+#include "xpcpublic.h"
+
+#include "mozilla/dom/ContentChild.h"
+
+#undef DELAYED_STARTUP_TOPIC
+#define DELAYED_STARTUP_TOPIC "sessionstore-windows-restored"
+
+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;
+
+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 : IterHash(mCachedURLs)) {
+ 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(
+ EmptyCString(), path, KIND_HEAP, UNITS_BYTES,
+ elem->SizeOfIncludingThis(MallocSizeOf),
+ NS_LITERAL_CSTRING("Memory used to hold cache data for files which "
+ "have been read or pre-loaded during this session."),
+ aData);
+ }
+
+ return NS_OK;
+}
+
+
+URLPreloader&
+URLPreloader::GetSingleton()
+{
+ static RefPtr<URLPreloader> singleton;
+
+ if (!singleton) {
+ singleton = new URLPreloader();
+ ClearOnShutdown(&singleton);
+ }
+
+ return *singleton;
+}
+
+
+bool URLPreloader::sInitialized = false;
+
+URLPreloader::URLPreloader()
+{
+ if (InitInternal().isOk()) {
+ sInitialized = true;
+ RegisterWeakMemoryReporter(this);
+ }
+}
+
+URLPreloader::~URLPreloader()
+{
+ if (sInitialized) {
+ UnregisterWeakMemoryReporter(this);
+ }
+}
+
+Result<Ok, nsresult>
+URLPreloader::InitInternal()
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ if (Omnijar::HasOmnijar(Omnijar::GRE)) {
+ NS_TRY(Omnijar::GetURIString(Omnijar::GRE, mGREPrefix));
+ }
+ if (Omnijar::HasOmnijar(Omnijar::APP)) {
+ NS_TRY(Omnijar::GetURIString(Omnijar::APP, mAppPrefix));
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
+ NS_TRY(rv);
+
+ nsCOMPtr<nsIProtocolHandler> ph;
+ NS_TRY(ios->GetProtocolHandler("resource", getter_AddRefs(ph)));
+
+ mResProto = do_QueryInterface(ph, &rv);
+ NS_TRY(rv);
+
+ mChromeReg = services::GetChromeRegistryService();
+ if (!mChromeReg) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+
+ obs->AddObserver(this, DELAYED_STARTUP_TOPIC, false);
+
+ NS_TRY(NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(mProfD)));
+ } else {
+ mStartupFinished = true;
+ mReaderInitialized = true;
+ }
+
+ return Ok();
+}
+
+Result<nsCOMPtr<nsIFile>, nsresult>
+URLPreloader::GetCacheFile(const nsAString& suffix)
+{
+ if (!mProfD) {
+ return Err(NS_ERROR_NOT_INITIALIZED);
+ }
+
+ nsCOMPtr<nsIFile> cacheFile;
+ NS_TRY(mProfD->Clone(getter_AddRefs(cacheFile)));
+
+ NS_TRY(cacheFile->AppendNative(NS_LITERAL_CSTRING("startupCache")));
+ Unused << cacheFile->Create(nsIFile::DIRECTORY_TYPE, 0777);
+
+ NS_TRY(cacheFile->Append(NS_LITERAL_STRING("urlCache") + suffix));
+
+ return Move(cacheFile);
+}
+
+static const uint8_t URL_MAGIC[] = "mozURLcachev001";
+
+Result<nsCOMPtr<nsIFile>, nsresult>
+URLPreloader::FindCacheFile()
+{
+ nsCOMPtr<nsIFile> cacheFile;
+ MOZ_TRY_VAR(cacheFile, GetCacheFile(NS_LITERAL_STRING(".bin")));
+
+ bool exists;
+ NS_TRY(cacheFile->Exists(&exists));
+ if (exists) {
+ NS_TRY(cacheFile->MoveTo(nullptr, NS_LITERAL_STRING("urlCache-current.bin")));
+ } else {
+ NS_TRY(cacheFile->SetLeafName(NS_LITERAL_STRING("urlCache-current.bin")));
+ NS_TRY(cacheFile->Exists(&exists));
+ if (!exists) {
+ return Err(NS_ERROR_FILE_NOT_FOUND);
+ }
+ }
+
+ return Move(cacheFile);
+}
+
+Result<Ok, nsresult>
+URLPreloader::WriteCache()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<nsIFile> cacheFile;
+ MOZ_TRY_VAR(cacheFile, GetCacheFile(NS_LITERAL_STRING("-new.bin")));
+
+ bool exists;
+ NS_TRY(cacheFile->Exists(&exists));
+ if (exists) {
+ NS_TRY(cacheFile->Remove(false));
+ }
+
+ {
+ AutoFDClose fd;
+ NS_TRY(cacheFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, 0644, &fd.rwget()));
+
+ nsTArray<URLEntry*> entries;
+ for (auto& entry : IterHash(mCachedURLs)) {
+ if (entry->mReadTime) {
+ entries.AppendElement(entry);
+ }
+ }
+
+ entries.Sort(URLEntry::Comparator());
+
+ OutputBuffer buf;
+ for (auto entry : entries) {
+ entry->Code(buf);
+ }
+
+ uint8_t headerSize[4];
+ LittleEndian::writeUint32(headerSize, buf.cursor());
+
+ MOZ_TRY(Write(fd, URL_MAGIC, sizeof(URL_MAGIC)));
+ MOZ_TRY(Write(fd, headerSize, sizeof(headerSize)));
+ MOZ_TRY(Write(fd, buf.Get(), buf.cursor()));
+ }
+
+ NS_TRY(cacheFile->MoveTo(nullptr, NS_LITERAL_STRING("urlCache.bin")));
+
+ NS_DispatchToMainThread(
+ NewRunnableMethod("URLPreloader::Cleanup",
+ this,
+ &URLPreloader::Cleanup));
+
+ return Ok();
+}
+
+void
+URLPreloader::Cleanup()
+{
+ mCachedURLs.Clear();
+}
+
+Result<Ok, nsresult>
+URLPreloader::ReadCache(LinkedList<URLEntry>& pendingURLs)
+{
+ nsCOMPtr<nsIFile> cacheFile;
+ MOZ_TRY_VAR(cacheFile, FindCacheFile());
+
+ AutoMemMap cache;
+ MOZ_TRY(cache.init(cacheFile));
+
+ auto size = cache.size();
+
+ uint32_t headerSize;
+ if (size < sizeof(URL_MAGIC) + sizeof(headerSize)) {
+ 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);
+
+ if (data + headerSize > end) {
+ 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);
+
+ auto entry = mCachedURLs.LookupOrAdd(key, key);
+ entry->mResultCode = NS_ERROR_NOT_INITIALIZED;
+
+ pendingURLs.insertBack(entry);
+ }
+
+ if (buf.error()) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ cleanup.release();
+ }
+
+ return Ok();
+}
+
+void
+URLPreloader::BackgroundReadFiles()
+{
+ 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();
+
+ 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;
+
+ 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();
+
+ NS_DispatchToMainThread(
+ NewRunnableMethod("nsIThread::Shutdown",
+ mReaderThread, &nsIThread::Shutdown));
+ mReaderThread = nullptr;
+}
+
+void
+URLPreloader::BeginBackgroundRead()
+{
+ if (!mReaderThread && !mReaderInitialized && sInitialized) {
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod("URLPreloader::BackgroundReadFiles",
+ this,
+ &URLPreloader::BackgroundReadFiles);
+
+ Unused << NS_NewNamedThread(
+ "BGReadURLs", getter_AddRefs(mReaderThread), runnable);
+ }
+}
+
+
+Result<const nsCString, nsresult>
+URLPreloader::ReadInternal(const CacheKey& key, ReadType readType)
+{
+ if (mStartupFinished) {
+ URLEntry entry(key);
+
+ return entry.Read();
+ }
+
+ auto entry = mCachedURLs.LookupOrAdd(key, key);
+
+ entry->UpdateUsedTime();
+
+ return entry->ReadOrWait(readType);
+}
+
+Result<const nsCString, nsresult>
+URLPreloader::ReadURIInternal(nsIURI* uri, ReadType readType)
+{
+ CacheKey key;
+ MOZ_TRY_VAR(key, ResolveURI(uri));
+
+ return ReadInternal(key, readType);
+}
+
+/* static */ Result<const 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<const nsCString, nsresult>
+URLPreloader::ReadURI(nsIURI* uri, ReadType readType)
+{
+ if (!sInitialized) {
+ return Err(NS_ERROR_NOT_INITIALIZED);
+ }
+
+ return GetSingleton().ReadURIInternal(uri, readType);
+}
+
+/* static */ Result<const nsCString, nsresult>
+URLPreloader::ReadFile(nsIFile* file, ReadType readType)
+{
+ return Read(CacheKey(file), readType);
+}
+
+/* static */ Result<const nsCString, nsresult>
+URLPreloader::ReadFile(const nsACString& path, ReadType readType)
+{
+ CacheKey key(CacheKey::TypeFile, path);
+ return Read(key, readType);
+}
+
+/* static */ Result<const 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<const 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, path.BeginReading());
+ return URLEntry::ReadLocation(location);
+}
+
+Result<URLPreloader::CacheKey, nsresult>
+URLPreloader::ResolveURI(nsIURI* uri)
+{
+ nsCString spec;
+ nsCString scheme;
+ NS_TRY(uri->GetSpec(spec));
+ NS_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")) {
+ NS_TRY(mResProto->ResolveURI(uri, spec));
+ NS_TRY(NS_NewURI(getter_AddRefs(resolved), spec));
+ } else if (scheme.EqualsLiteral("chrome")) {
+ NS_TRY(mChromeReg->ConvertChromeURL(uri, getter_AddRefs(resolved)));
+ NS_TRY(resolved->GetSpec(spec));
+ } else {
+ resolved = uri;
+ }
+ NS_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;
+ NS_TRY(fileURL->GetFile(getter_AddRefs(file)));
+
+ nsCString path;
+ NS_TRY(file->GetNativePath(path));
+
+ return CacheKey(CacheKey::TypeFile, 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;
+ NS_TRY(NS_NewNativeLocalFile(mPath, false, getter_AddRefs(file)));
+ return Move(FileLocation(file));
+ }
+
+ RefPtr<nsZipArchive> zip = Archive();
+ return Move(FileLocation(zip, mPath.get()));
+}
+
+Result<const nsCString, nsresult>
+URLPreloader::URLEntry::Read()
+{
+ FileLocation location;
+ MOZ_TRY_VAR(location, ToFileLocation());
+
+ MOZ_TRY_VAR(mData, ReadLocation(location));
+ return mData;
+}
+
+/* static */ Result<const nsCString, nsresult>
+URLPreloader::URLEntry::ReadLocation(FileLocation& location)
+{
+ FileLocation::Data data;
+ NS_TRY(location.GetData(data));
+
+ uint32_t size;
+ NS_TRY(data.GetSize(&size));
+
+ nsCString result;
+ result.SetLength(size);
+ NS_TRY(data.Copy(result.BeginWriting(), size));
+
+ return Move(result);
+}
+
+Result<const 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);
+}
+
+nsresult
+URLPreloader::Observe(nsISupports* subject, const char* topic, const char16_t* data)
+{
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (!strcmp(topic, DELAYED_STARTUP_TOPIC)) {
+ obs->RemoveObserver(this, DELAYED_STARTUP_TOPIC);
+ mStartupFinished = true;
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMPL_ISUPPORTS(URLPreloader, nsIObserver, nsIMemoryReporter)
+
+#undef LOG
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/loader/URLPreloader.h
@@ -0,0 +1,325 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
+/* 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/. */
+
+#ifndef URLPreloader_h
+#define URLPreloader_h
+
+#include "mozilla/FileLocation.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Omnijar.h"
+#include "mozilla/Range.h"
+#include "mozilla/Vector.h"
+#include "mozilla/Result.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsIChromeRegistry.h"
+#include "nsIFile.h"
+#include "nsIURI.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObserver.h"
+#include "nsIResProtocolHandler.h"
+#include "nsIThread.h"
+#include "nsReadableUtils.h"
+
+class nsZipArchive;
+
+namespace mozilla {
+namespace loader {
+ class InputBuffer;
+}
+
+using namespace mozilla::loader;
+
+class ScriptPreloader;
+
+/**
+ * A singleton class to manage loading local URLs during startup, recording
+ * them, and pre-loading them during early startup in the next session. URLs
+ * that are not already loaded (or already being pre-loaded) when required are
+ * read synchronously from disk, and (if startup is not already complete)
+ * added to the pre-load list for the next session.
+ */
+class URLPreloader final : public nsIObserver
+ , public nsIMemoryReporter
+{
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ URLPreloader();
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIMEMORYREPORTER
+
+ static URLPreloader& GetSingleton();
+
+ // The type of read operation to perform.
+ enum ReadType
+ {
+ // Read the file and then immediately forget its data.
+ Forget,
+ // Read the file and retain its data for the next caller.
+ Retain,
+ };
+
+ // Helpers to read the contents of files or JAR archive entries with various
+ // representations. If the preloader has not yet been initialized, or the
+ // given location is not supported by the cache, the entries will be read
+ // synchronously, and not stored in the cache.
+ static Result<const nsCString, nsresult> Read(FileLocation& location, ReadType readType = Forget);
+
+ static Result<const nsCString, nsresult> ReadURI(nsIURI* uri, ReadType readType = Forget);
+
+ static Result<const nsCString, nsresult> ReadFile(nsIFile* file, ReadType readType = Forget);
+
+ static Result<const nsCString, nsresult> ReadFile(const nsACString& path, ReadType readType = Forget);
+
+ static Result<const nsCString, nsresult> ReadZip(nsZipArchive* archive,
+ const nsACString& path,
+ ReadType readType = Forget);
+
+private:
+ struct CacheKey;
+
+ Result<const nsCString, nsresult> ReadInternal(const CacheKey& key, ReadType readType);
+
+ Result<const nsCString, nsresult> ReadURIInternal(nsIURI* uri, ReadType readType);
+
+ Result<const nsCString, nsresult> ReadFileInternal(nsIFile* file, ReadType readType);
+
+ static Result<const nsCString, nsresult> Read(const CacheKey& key, ReadType readType);
+
+ static bool sInitialized;
+
+protected:
+ friend class ScriptPreloader;
+
+ virtual ~URLPreloader();
+
+ Result<Ok, nsresult> WriteCache();
+
+ // Clear leftover entries after the cache has been written.
+ void Cleanup();
+
+ // Begins reading files off-thread, and ensures that initialization has
+ // completed before leaving the current scope. The caller *must* ensure that
+ // no code on the main thread access Omnijar, either directly or indirectly,
+ // for the lifetime of this guard object.
+ struct MOZ_RAII AutoBeginReading final
+ {
+ AutoBeginReading()
+ {
+ GetSingleton().BeginBackgroundRead();
+ }
+
+ ~AutoBeginReading()
+ {
+ auto& reader = GetSingleton();
+
+ MonitorAutoLock mal(reader.mMonitor);
+
+ while (!reader.mReaderInitialized && reader.sInitialized) {
+ mal.Wait();
+ }
+ }
+ };
+
+private:
+ // Represents a key for an entry in the URI cache, based on its file or JAR
+ // location.
+ struct CacheKey
+ {
+ // The type of the entry. TypeAppJar and TypeGREJar entries are in the
+ // app-specific or toolkit Omnijar files, and are handled specially.
+ // TypeFile entries are plain files in the filesystem.
+ enum EntryType : uint8_t
+ {
+ TypeAppJar,
+ TypeGREJar,
+ TypeFile,
+ };
+
+ CacheKey() = default;
+ CacheKey(const CacheKey& other) = default;
+
+ CacheKey(EntryType type, const nsACString& path)
+ : mType(type), mPath(path)
+ {}
+
+ explicit CacheKey(nsIFile* file)
+ : mType(TypeFile)
+ {
+ MOZ_ALWAYS_SUCCEEDS(file->GetNativePath(mPath));
+ }
+
+ explicit inline CacheKey(InputBuffer& buffer);
+
+ // Encodes or decodes the cache key for storage in a session cache file.
+ template <typename Buffer>
+ void Code(Buffer& buffer)
+ {
+ buffer.codeUint8(*reinterpret_cast<uint8_t*>(&mType));
+ buffer.codeString(mPath);
+ }
+
+ uint32_t Hash() const
+ {
+ return HashGeneric(mType, HashString(mPath));
+ }
+
+ bool operator==(const CacheKey& other) const
+ {
+ return mType == other.mType && mPath == other.mPath;
+ }
+
+ // Returns the Omnijar type for this entry. This may *only* be called
+ // for Omnijar entries.
+ Omnijar::Type OmnijarType()
+ {
+ switch (mType) {
+ case TypeAppJar:
+ return Omnijar::APP;
+ case TypeGREJar:
+ return Omnijar::GRE;
+ default:
+ MOZ_CRASH("Unexpected entry type");
+ return Omnijar::GRE;
+ }
+ }
+
+ const char* TypeString()
+ {
+ switch (mType) {
+ case TypeAppJar: return "AppJar";
+ case TypeGREJar: return "GREJar";
+ case TypeFile: return "File";
+ }
+ MOZ_ASSERT_UNREACHABLE("no such type");
+ return "";
+ }
+
+ already_AddRefed<nsZipArchive> Archive()
+ {
+ return Omnijar::GetReader(OmnijarType());
+ }
+
+ Result<FileLocation, nsresult> ToFileLocation();
+
+ EntryType mType = TypeFile;
+
+ // The path of the entry. For Type*Jar entries, this is the path within
+ // the Omnijar archive. For TypeFile entries, this is the full path to
+ // the file.
+ nsCString mPath{};
+ };
+
+ // Represents an entry in the URI cache.
+ struct URLEntry final : public CacheKey
+ , public LinkedListElement<URLEntry>
+ {
+ MOZ_IMPLICIT URLEntry(const CacheKey& key)
+ : CacheKey(key)
+ , mData(NullCString())
+ {}
+
+ explicit URLEntry(nsIFile* file)
+ : CacheKey(file)
+ {}
+
+ // For use with nsTArray::Sort.
+ //
+ // Sorts entries by the time they were initially read during this
+ // session.
+ struct Comparator final
+ {
+ bool Equals(const URLEntry* a, const URLEntry* b) const
+ {
+ return a->mReadTime == b->mReadTime;
+ }
+
+ bool LessThan(const URLEntry* a, const URLEntry* b) const
+ {
+ return a->mReadTime < b->mReadTime;
+ }
+ };
+
+ // Sets the first-used time of this file to the earlier of its current
+ // first-use time or the given timestamp.
+ void UpdateUsedTime(const TimeStamp& time = TimeStamp::Now())
+ {
+ if (!mReadTime || time < mReadTime) {
+ mReadTime = time;
+ }
+ }
+
+ Result<const nsCString, nsresult> Read();
+ static Result<const nsCString, nsresult> ReadLocation(FileLocation& location);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
+ {
+ return (mallocSizeOf(this) +
+ mPath.SizeOfExcludingThisEvenIfShared(mallocSizeOf) +
+ mData.SizeOfExcludingThisEvenIfShared(mallocSizeOf));
+ }
+
+ // Reads the contents of the file referenced by this entry, or wait for
+ // an off-thread read operation to finish if it is currently pending,
+ // and return the file's contents.
+ Result<const nsCString, nsresult> ReadOrWait(ReadType readType);
+
+ nsCString mData;
+
+ TimeStamp mReadTime{};
+
+ nsresult mResultCode = NS_OK;
+ };
+
+ // Resolves the given URI to a CacheKey, if the URI is cacheable.
+ Result<CacheKey, nsresult> ResolveURI(nsIURI* uri);
+
+ Result<Ok, nsresult> InitInternal();
+
+ // Returns a file pointer to the (possibly nonexistent) cache file with the
+ // given suffix.
+ Result<nsCOMPtr<nsIFile>, nsresult> GetCacheFile(const nsAString& suffix);
+ // Finds the correct cache file to use for this session.
+ Result<nsCOMPtr<nsIFile>, nsresult> FindCacheFile();
+
+ Result<Ok, nsresult> ReadCache(LinkedList<URLEntry>& pendingURLs);
+
+ void BackgroundReadFiles();
+ void BeginBackgroundRead();
+
+ using HashType = nsClassHashtable<nsGenericHashKey<CacheKey>, URLEntry>;
+
+ size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
+
+
+ bool mStartupFinished = false;
+ bool mReaderInitialized = false;
+
+ // The prefix URLs for files in the GRE and App omni jar archives.
+ nsCString mGREPrefix;
+ nsCString mAppPrefix;
+
+ nsCOMPtr<nsIResProtocolHandler> mResProto;
+ nsCOMPtr<nsIChromeRegistry> mChromeReg;
+ nsCOMPtr<nsIFile> mProfD;
+
+ nsCOMPtr<nsIThread> mReaderThread;
+
+ // A map of URL entries which have were either read this session, or read
+ // from the last session's cache file.
+ HashType mCachedURLs;
+
+ Monitor mMonitor{"[URLPreloader::mMutex]"};
+};
+
+} // namespace mozilla
+
+#endif // URLPreloader_h
--- a/js/xpconnect/loader/moz.build
+++ b/js/xpconnect/loader/moz.build
@@ -6,30 +6,32 @@
UNIFIED_SOURCES += [
'AutoMemMap.cpp',
'ChromeScriptLoader.cpp',
'mozJSLoaderUtils.cpp',
'mozJSSubScriptLoader.cpp',
'ScriptCacheActors.cpp',
'ScriptPreloader.cpp',
+ 'URLPreloader.cpp',
]
# mozJSComponentLoader.cpp cannot be built in unified mode because it uses
# windows.h
SOURCES += [
'mozJSComponentLoader.cpp'
]
IPDL_SOURCES += [
'PScriptCache.ipdl',
]
EXPORTS.mozilla += [
'ScriptPreloader.h',
+ 'URLPreloader.h',
]
EXPORTS.mozilla.dom += [
'PrecompiledScript.h',
]
EXPORTS.mozilla.loader += [
'AutoMemMap.h',
--- a/xpcom/build/FileLocation.cpp
+++ b/xpcom/build/FileLocation.cpp
@@ -24,16 +24,31 @@ FileLocation::FileLocation(nsIFile* aFil
Init(aFile, aPath);
}
FileLocation::FileLocation(nsZipArchive* aZip, const char* aPath)
{
Init(aZip, aPath);
}
+FileLocation::FileLocation(const FileLocation& aOther)
+ : mBaseFile(aOther.mBaseFile)
+ , mBaseZip(aOther.mBaseZip)
+ , mPath(aOther.mPath)
+{
+}
+
+FileLocation::FileLocation(FileLocation&& aOther)
+ : mBaseFile(Move(aOther.mBaseFile))
+ , mBaseZip(Move(aOther.mBaseZip))
+ , mPath(Move(aOther.mPath))
+{
+ aOther.mPath.Truncate();
+}
+
FileLocation::FileLocation(const FileLocation& aFile, const char* aPath)
{
if (aFile.IsZip()) {
if (aFile.mBaseFile) {
Init(aFile.mBaseFile, aFile.mPath.get());
}
else {
Init(aFile.mBaseZip, aFile.mPath.get());
--- a/xpcom/build/FileLocation.h
+++ b/xpcom/build/FileLocation.h
@@ -30,33 +30,38 @@ public:
* As such, it stores a path within an archive, as well as the archive
* path itself, or the complete file path alone when on a filesystem.
* When the archive is in an archive, an nsZipArchive is stored instead
* of a file path.
*/
FileLocation();
~FileLocation();
+ FileLocation(const FileLocation& aOther);
+ FileLocation(FileLocation&& aOther);
+
+ FileLocation& operator=(const FileLocation&) = default;
+
/**
* Constructor for plain files
*/
explicit FileLocation(nsIFile* aFile);
/**
* Constructors for path within an archive. The archive can be given either
* as nsIFile or nsZipArchive.
*/
FileLocation(nsIFile* aZip, const char* aPath);
FileLocation(nsZipArchive* aZip, const char* aPath);
/**
* Creates a new file location relative to another one.
*/
- FileLocation(const FileLocation& aFile, const char* aPath = nullptr);
+ FileLocation(const FileLocation& aFile, const char* aPath);
/**
* Initialization functions corresponding to constructors
*/
void Init(nsIFile* aFile);
void Init(nsIFile* aZip, const char* aPath);