зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1363482: Part 2 - Allow pre-loading file and JAR entry contents off-thread during startup. r=erahm
MozReview-Commit-ID: 8bKzYpXBQvT --HG-- extra : rebase_source : 6750d471a0a39338b5145e2dab9d953b4c30a63f
This commit is contained in:
Родитель
42402b8f46
Коммит
c4a2fd3bfb
|
@ -25,6 +25,15 @@ 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(); }
|
||||
|
@ -259,8 +268,15 @@ public:
|
|||
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(); }
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include "mozilla/ScriptPreloader.h"
|
||||
#include "mozilla/loader/ScriptCacheActors.h"
|
||||
|
||||
#include "mozilla/URLPreloader.h"
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/FileUtils.h"
|
||||
|
@ -413,6 +415,10 @@ ScriptPreloader::InitCache(const nsAString& basePath)
|
|||
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();
|
||||
|
@ -517,15 +523,6 @@ ScriptPreloader::InitCacheInternal()
|
|||
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()
|
||||
{
|
||||
|
@ -603,6 +600,8 @@ ScriptPreloader::WriteCache()
|
|||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
Unused << URLPreloader::GetSingleton().WriteCache();
|
||||
|
||||
if (!mDataPrepared && !mSaveComplete) {
|
||||
MOZ_ASSERT(!mBlockedOnSyncDispatch);
|
||||
mBlockedOnSyncDispatch = true;
|
||||
|
|
|
@ -0,0 +1,691 @@
|
|||
/* -*- 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 "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/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 "nsPromiseFlatString.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)) {
|
||||
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::GetChromeRegistryService();
|
||||
if (!mChromeReg) {
|
||||
return Err(NS_ERROR_UNEXPECTED);
|
||||
}
|
||||
|
||||
if (XRE_IsParentProcess()) {
|
||||
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
||||
|
||||
obs->AddObserver(this, DELAYED_STARTUP_TOPIC, false);
|
||||
|
||||
MOZ_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;
|
||||
MOZ_TRY(mProfD->Clone(getter_AddRefs(cacheFile)));
|
||||
|
||||
MOZ_TRY(cacheFile->AppendNative(NS_LITERAL_CSTRING("startupCache")));
|
||||
Unused << cacheFile->Create(nsIFile::DIRECTORY_TYPE, 0777);
|
||||
|
||||
MOZ_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;
|
||||
MOZ_TRY(cacheFile->Exists(&exists));
|
||||
if (exists) {
|
||||
MOZ_TRY(cacheFile->MoveTo(nullptr, NS_LITERAL_STRING("urlCache-current.bin")));
|
||||
} else {
|
||||
MOZ_TRY(cacheFile->SetLeafName(NS_LITERAL_STRING("urlCache-current.bin")));
|
||||
MOZ_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;
|
||||
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 (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()));
|
||||
}
|
||||
|
||||
MOZ_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, 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)));
|
||||
|
||||
nsCString path;
|
||||
MOZ_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;
|
||||
MOZ_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;
|
||||
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 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
|
|
@ -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
|
|
@ -11,6 +11,7 @@ UNIFIED_SOURCES += [
|
|||
'mozJSSubScriptLoader.cpp',
|
||||
'ScriptCacheActors.cpp',
|
||||
'ScriptPreloader.cpp',
|
||||
'URLPreloader.cpp',
|
||||
]
|
||||
|
||||
# mozJSComponentLoader.cpp cannot be built in unified mode because it uses
|
||||
|
@ -25,6 +26,7 @@ IPDL_SOURCES += [
|
|||
|
||||
EXPORTS.mozilla += [
|
||||
'ScriptPreloader.h',
|
||||
'URLPreloader.h',
|
||||
]
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
|
|
|
@ -29,6 +29,21 @@ 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()) {
|
||||
|
|
|
@ -35,6 +35,11 @@ public:
|
|||
FileLocation();
|
||||
~FileLocation();
|
||||
|
||||
FileLocation(const FileLocation& aOther);
|
||||
FileLocation(FileLocation&& aOther);
|
||||
|
||||
FileLocation& operator=(const FileLocation&) = default;
|
||||
|
||||
/**
|
||||
* Constructor for plain files
|
||||
*/
|
||||
|
@ -51,7 +56,7 @@ public:
|
|||
/**
|
||||
* 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
|
||||
|
|
Загрузка…
Ссылка в новой задаче