summaryrefslogtreecommitdiffstats
path: root/uriloader/prefetch
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /uriloader/prefetch
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'uriloader/prefetch')
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateChild.cpp472
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateChild.h93
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateGlue.cpp220
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateGlue.h124
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateParent.cpp287
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateParent.h64
-rw-r--r--uriloader/prefetch/POfflineCacheUpdate.ipdl28
-rw-r--r--uriloader/prefetch/moz.build44
-rw-r--r--uriloader/prefetch/nsIOfflineCacheUpdate.idl291
-rw-r--r--uriloader/prefetch/nsIPrefetchService.idl54
-rw-r--r--uriloader/prefetch/nsOfflineCacheUpdate.cpp2332
-rw-r--r--uriloader/prefetch/nsOfflineCacheUpdate.h369
-rw-r--r--uriloader/prefetch/nsOfflineCacheUpdateService.cpp646
-rw-r--r--uriloader/prefetch/nsPrefetchService.cpp889
-rw-r--r--uriloader/prefetch/nsPrefetchService.h130
15 files changed, 6043 insertions, 0 deletions
diff --git a/uriloader/prefetch/OfflineCacheUpdateChild.cpp b/uriloader/prefetch/OfflineCacheUpdateChild.cpp
new file mode 100644
index 0000000000..10907acc34
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateChild.cpp
@@ -0,0 +1,472 @@
+/* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "BackgroundUtils.h"
+#include "OfflineCacheUpdateChild.h"
+#include "nsOfflineCacheUpdate.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/OfflineResourceListBinding.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoCommon.h"
+
+#include "nsIApplicationCacheChannel.h"
+#include "nsIDocShell.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "nsIObserverService.h"
+#include "nsIBrowserChild.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Logging.h"
+#include "nsApplicationCache.h"
+
+using namespace mozilla::ipc;
+using namespace mozilla::net;
+using mozilla::dom::BrowserChild;
+using mozilla::dom::ContentChild;
+using mozilla::dom::Document;
+
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=nsOfflineCacheUpdate:5
+// set MOZ_LOG_FILE=offlineupdate.log
+//
+// this enables LogLevel::Debug level information and places all output in
+// the file offlineupdate.log
+//
+extern mozilla::LazyLogModule gOfflineCacheUpdateLog;
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug, args)
+
+#undef LOG_ENABLED
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug)
+
+namespace mozilla {
+namespace docshell {
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateChild::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_INTERFACE_MAP_BEGIN(OfflineCacheUpdateChild)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIOfflineCacheUpdate)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(OfflineCacheUpdateChild)
+NS_IMPL_RELEASE(OfflineCacheUpdateChild)
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateChild <public>
+//-----------------------------------------------------------------------------
+
+OfflineCacheUpdateChild::OfflineCacheUpdateChild(nsPIDOMWindowInner* aWindow)
+ : mState(STATE_UNINITIALIZED),
+ mIsUpgrade(false),
+ mSucceeded(false),
+ mWindow(aWindow),
+ mByteProgress(0) {}
+
+OfflineCacheUpdateChild::~OfflineCacheUpdateChild() {
+ LOG(("OfflineCacheUpdateChild::~OfflineCacheUpdateChild [%p]", this));
+}
+
+void OfflineCacheUpdateChild::GatherObservers(
+ nsCOMArray<nsIOfflineCacheUpdateObserver>& aObservers) {
+ for (int32_t i = 0; i < mWeakObservers.Count(); i++) {
+ nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
+ do_QueryReferent(mWeakObservers[i]);
+ if (observer)
+ aObservers.AppendObject(observer);
+ else
+ mWeakObservers.RemoveObjectAt(i--);
+ }
+
+ for (int32_t i = 0; i < mObservers.Count(); i++) {
+ aObservers.AppendObject(mObservers[i]);
+ }
+}
+
+void OfflineCacheUpdateChild::SetDocument(Document* aDocument) {
+ // The design is one document for one cache update on the content process.
+ NS_ASSERTION(
+ !mDocument,
+ "Setting more then a single document on a child offline cache update");
+
+ LOG(("Document %p added to update child %p", aDocument, this));
+
+ // Add document only if it was not loaded from an offline cache.
+ // If it were loaded from an offline cache then it has already
+ // been associated with it and must not be again cached as
+ // implicit (which are the reasons we collect documents here).
+ if (!aDocument) return;
+
+ mCookieJarSettings = aDocument->CookieJarSettings();
+
+ nsIChannel* channel = aDocument->GetChannel();
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(channel);
+ if (!appCacheChannel) return;
+
+ bool loadedFromAppCache;
+ appCacheChannel->GetLoadedFromApplicationCache(&loadedFromAppCache);
+ if (loadedFromAppCache) return;
+
+ mDocument = aDocument;
+}
+
+nsresult OfflineCacheUpdateChild::AssociateDocument(
+ Document* aDocument, nsIApplicationCache* aApplicationCache) {
+ // Check that the document that requested this update was
+ // previously associated with an application cache. If not, it
+ // should be associated with the new one.
+ nsCOMPtr<nsIApplicationCache> existingCache;
+ nsresult rv = aDocument->GetApplicationCache(getter_AddRefs(existingCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!existingCache) {
+ if (LOG_ENABLED()) {
+ nsAutoCString clientID;
+ if (aApplicationCache) {
+ aApplicationCache->GetClientID(clientID);
+ }
+ LOG(("Update %p: associating app cache %s to document %p", this,
+ clientID.get(), aDocument));
+ }
+
+ rv = aDocument->SetApplicationCache(aApplicationCache);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateChild::nsIOfflineCacheUpdate
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::Init(nsIURI* aManifestURI, nsIURI* aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ Document* aDocument, nsIFile* aCustomProfileDir) {
+ nsresult rv;
+
+ // Make sure the service has been initialized
+ nsOfflineCacheUpdateService* service =
+ nsOfflineCacheUpdateService::EnsureService();
+ if (!service) return NS_ERROR_FAILURE;
+
+ if (aCustomProfileDir) {
+ NS_ERROR("Custom Offline Cache Update not supported on child process");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ LOG(("OfflineCacheUpdateChild::Init [%p]", this));
+
+ // Only http and https applications are supported.
+ if (!aManifestURI->SchemeIs("http") && !aManifestURI->SchemeIs("https")) {
+ return NS_ERROR_ABORT;
+ }
+
+ mManifestURI = aManifestURI;
+
+ rv = mManifestURI->GetAsciiHost(mUpdateDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDocumentURI = aDocumentURI;
+ mLoadingPrincipal = aLoadingPrincipal;
+
+ mState = STATE_INITIALIZED;
+
+ if (aDocument) SetDocument(aDocument);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::InitPartial(nsIURI* aManifestURI,
+ const nsACString& clientID,
+ nsIURI* aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsICookieJarSettings* aCookieJarSettings) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Not expected to do partial offline cache updates"
+ " on the child process");
+ // For now leaving this method, we may discover we need it.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::InitForUpdateCheck(nsIURI* aManifestURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIObserver* aObserver) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Not expected to do only update checks"
+ " from the child process");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::GetUpdateDomain(nsACString& aUpdateDomain) {
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ aUpdateDomain = mUpdateDomain;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::GetStatus(uint16_t* aStatus) {
+ switch (mState) {
+ case STATE_CHECKING:
+ *aStatus = mozilla::dom::OfflineResourceList_Binding::CHECKING;
+ return NS_OK;
+ case STATE_DOWNLOADING:
+ *aStatus = mozilla::dom::OfflineResourceList_Binding::DOWNLOADING;
+ return NS_OK;
+ default:
+ *aStatus = mozilla::dom::OfflineResourceList_Binding::IDLE;
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::GetPartial(bool* aPartial) {
+ *aPartial = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::GetLoadingPrincipal(nsIPrincipal** aLoadingPrincipal) {
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ NS_IF_ADDREF(*aLoadingPrincipal = mLoadingPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::GetManifestURI(nsIURI** aManifestURI) {
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ NS_IF_ADDREF(*aManifestURI = mManifestURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::GetSucceeded(bool* aSucceeded) {
+ NS_ENSURE_TRUE(mState == STATE_FINISHED, NS_ERROR_NOT_AVAILABLE);
+
+ *aSucceeded = mSucceeded;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::GetIsUpgrade(bool* aIsUpgrade) {
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ *aIsUpgrade = mIsUpgrade;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::AddDynamicURI(nsIURI* aURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::Cancel() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::AddObserver(nsIOfflineCacheUpdateObserver* aObserver,
+ bool aHoldWeak) {
+ LOG(("OfflineCacheUpdateChild::AddObserver [%p]", this));
+
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ if (aHoldWeak) {
+ nsWeakPtr weakRef = do_GetWeakReference(aObserver);
+ mWeakObservers.AppendObject(weakRef);
+ } else {
+ mObservers.AppendObject(aObserver);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::RemoveObserver(
+ nsIOfflineCacheUpdateObserver* aObserver) {
+ LOG(("OfflineCacheUpdateChild::RemoveObserver [%p]", this));
+
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ for (int32_t i = 0; i < mWeakObservers.Count(); i++) {
+ nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
+ do_QueryReferent(mWeakObservers[i]);
+ if (observer == aObserver) {
+ mWeakObservers.RemoveObjectAt(i);
+ return NS_OK;
+ }
+ }
+
+ for (int32_t i = 0; i < mObservers.Count(); i++) {
+ if (mObservers[i] == aObserver) {
+ mObservers.RemoveObjectAt(i);
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::GetByteProgress(uint64_t* _result) {
+ NS_ENSURE_ARG(_result);
+
+ *_result = mByteProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::Schedule() {
+ LOG(("OfflineCacheUpdateChild::Schedule [%p]", this));
+
+ NS_ASSERTION(mWindow,
+ "Window must be provided to the offline cache update child");
+
+ nsCOMPtr<nsPIDOMWindowInner> window = std::move(mWindow);
+ nsCOMPtr<nsIDocShell> docshell = window->GetDocShell();
+ if (!docshell) {
+ NS_WARNING("doc shell tree item is null");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = NS_OK;
+ PrincipalInfo loadingPrincipalInfo;
+ rv = PrincipalToPrincipalInfo(mLoadingPrincipal, &loadingPrincipalInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ LOG(("Calling offline-cache-update-added"));
+ observerService->NotifyObservers(static_cast<nsIOfflineCacheUpdate*>(this),
+ "offline-cache-update-added", nullptr);
+ LOG(("Done offline-cache-update-added"));
+ }
+
+ // mDocument is non-null if both:
+ // 1. this update was initiated by a document that referred a manifest
+ // 2. the document has not already been loaded from the application cache
+ // This tells the update to cache this document even in case the manifest
+ // has not been changed since the last fetch.
+ // See also nsOfflineCacheUpdate::ScheduleImplicit.
+ bool stickDocument = mDocument != nullptr;
+
+ CookieJarSettingsArgs csArgs;
+ if (mCookieJarSettings) {
+ CookieJarSettings::Cast(mCookieJarSettings)->Serialize(csArgs);
+ }
+
+ ContentChild::GetSingleton()->SendPOfflineCacheUpdateConstructor(
+ this, mManifestURI, mDocumentURI, loadingPrincipalInfo, stickDocument,
+ csArgs);
+
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult OfflineCacheUpdateChild::RecvAssociateDocuments(
+ const nsCString& cacheGroupId, const nsCString& cacheClientId) {
+ LOG(("OfflineCacheUpdateChild::RecvAssociateDocuments [%p, cache=%s]", this,
+ cacheClientId.get()));
+
+ nsCOMPtr<nsIApplicationCache> cache = new nsApplicationCache();
+
+ cache->InitAsHandle(cacheGroupId, cacheClientId);
+
+ if (mDocument) {
+ AssociateDocument(mDocument, cache);
+ }
+
+ nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
+ GatherObservers(observers);
+
+ for (int32_t i = 0; i < observers.Count(); i++)
+ observers[i]->ApplicationCacheAvailable(cache);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult OfflineCacheUpdateChild::RecvNotifyStateEvent(
+ const uint32_t& event, const uint64_t& byteProgress) {
+ LOG(("OfflineCacheUpdateChild::RecvNotifyStateEvent [%p]", this));
+
+ mByteProgress = byteProgress;
+
+ // Convert the public observer state to our internal state
+ switch (event) {
+ case nsIOfflineCacheUpdateObserver::STATE_CHECKING:
+ mState = STATE_CHECKING;
+ break;
+
+ case nsIOfflineCacheUpdateObserver::STATE_DOWNLOADING:
+ mState = STATE_DOWNLOADING;
+ break;
+
+ default:
+ break;
+ }
+
+ nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
+ GatherObservers(observers);
+
+ for (int32_t i = 0; i < observers.Count(); i++)
+ observers[i]->UpdateStateChanged(this, event);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult OfflineCacheUpdateChild::RecvFinish(
+ const bool& succeeded, const bool& isUpgrade) {
+ LOG(("OfflineCacheUpdateChild::RecvFinish [%p]", this));
+
+ RefPtr<OfflineCacheUpdateChild> kungFuDeathGrip(this);
+
+ mState = STATE_FINISHED;
+ mSucceeded = succeeded;
+ mIsUpgrade = isUpgrade;
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ LOG(("Calling offline-cache-update-completed"));
+ observerService->NotifyObservers(static_cast<nsIOfflineCacheUpdate*>(this),
+ "offline-cache-update-completed", nullptr);
+ LOG(("Done offline-cache-update-completed"));
+ }
+
+ // This is by contract the last notification from the parent, release
+ // us now. This is corresponding to AddRef in Schedule().
+ // BrowserChild::DeallocPOfflineCacheUpdate will call Release.
+ OfflineCacheUpdateChild::Send__delete__(this);
+
+ return IPC_OK();
+}
+
+} // namespace docshell
+} // namespace mozilla
diff --git a/uriloader/prefetch/OfflineCacheUpdateChild.h b/uriloader/prefetch/OfflineCacheUpdateChild.h
new file mode 100644
index 0000000000..4099c9dfdb
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateChild.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsOfflineCacheUpdateChild_h
+#define nsOfflineCacheUpdateChild_h
+
+#include "mozilla/docshell/POfflineCacheUpdateChild.h"
+#include "nsIOfflineCacheUpdate.h"
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIURI.h"
+#include "nsIWeakReference.h"
+#include "nsString.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+class Document;
+}
+
+namespace docshell {
+
+class OfflineCacheUpdateChild : public nsIOfflineCacheUpdate,
+ public POfflineCacheUpdateChild {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOFFLINECACHEUPDATE
+
+ mozilla::ipc::IPCResult RecvNotifyStateEvent(const uint32_t& stateEvent,
+ const uint64_t& byteProgress);
+
+ mozilla::ipc::IPCResult RecvAssociateDocuments(
+ const nsCString& cacheGroupId, const nsCString& cacheClientId);
+
+ mozilla::ipc::IPCResult RecvFinish(const bool& succeeded,
+ const bool& isUpgrade);
+
+ explicit OfflineCacheUpdateChild(nsPIDOMWindowInner* aWindow);
+
+ void SetDocument(dom::Document* aDocument);
+
+ private:
+ ~OfflineCacheUpdateChild();
+
+ nsresult AssociateDocument(dom::Document* aDocument,
+ nsIApplicationCache* aApplicationCache);
+ void GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver>& aObservers);
+ nsresult Finish();
+
+ enum {
+ STATE_UNINITIALIZED,
+ STATE_INITIALIZED,
+ STATE_CHECKING,
+ STATE_DOWNLOADING,
+ STATE_CANCELLED,
+ STATE_FINISHED
+ } mState;
+
+ bool mIsUpgrade;
+ bool mSucceeded;
+
+ nsCString mUpdateDomain;
+ nsCOMPtr<nsIURI> mManifestURI;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+ nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
+
+ nsCOMPtr<nsIObserverService> mObserverService;
+
+ /* Clients watching this update for changes */
+ nsCOMArray<nsIWeakReference> mWeakObservers;
+ nsCOMArray<nsIOfflineCacheUpdateObserver> mObservers;
+
+ /* Document that requested this update */
+ nsCOMPtr<dom::Document> mDocument;
+
+ /* Keep reference to the window that owns this update to call the
+ parent offline cache update construcor */
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+
+ uint64_t mByteProgress;
+};
+
+} // namespace docshell
+} // namespace mozilla
+
+#endif
diff --git a/uriloader/prefetch/OfflineCacheUpdateGlue.cpp b/uriloader/prefetch/OfflineCacheUpdateGlue.cpp
new file mode 100644
index 0000000000..85515b5547
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateGlue.cpp
@@ -0,0 +1,220 @@
+/* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "OfflineCacheUpdateGlue.h"
+#include "nsOfflineCacheUpdate.h"
+#include "mozilla/Services.h"
+
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIChannel.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/Logging.h"
+
+using mozilla::dom::Document;
+
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=nsOfflineCacheUpdate:5
+// set MOZ_LOG_FILE=offlineupdate.log
+//
+// this enables LogLevel::Info level information and places all output in
+// the file offlineupdate.log
+//
+extern mozilla::LazyLogModule gOfflineCacheUpdateLog;
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug, args)
+
+#undef LOG_ENABLED
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug)
+
+namespace mozilla {
+namespace docshell {
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateGlue::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(OfflineCacheUpdateGlue, nsIOfflineCacheUpdate,
+ nsIOfflineCacheUpdateObserver, nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateGlue <public>
+//-----------------------------------------------------------------------------
+
+OfflineCacheUpdateGlue::OfflineCacheUpdateGlue() : mCoalesced(false) {
+ LOG(("OfflineCacheUpdateGlue::OfflineCacheUpdateGlue [%p]", this));
+}
+
+OfflineCacheUpdateGlue::~OfflineCacheUpdateGlue() {
+ LOG(("OfflineCacheUpdateGlue::~OfflineCacheUpdateGlue [%p]", this));
+}
+
+nsIOfflineCacheUpdate* OfflineCacheUpdateGlue::EnsureUpdate() {
+ if (!mUpdate) {
+ mUpdate = new nsOfflineCacheUpdate();
+ LOG(("OfflineCacheUpdateGlue [%p] is using update [%p]", this,
+ mUpdate.get()));
+
+ mUpdate->SetCookieJarSettings(mCookieJarSettings);
+ }
+
+ return mUpdate;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateGlue::Schedule() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ LOG(("Calling offline-cache-update-added"));
+ observerService->NotifyObservers(static_cast<nsIOfflineCacheUpdate*>(this),
+ "offline-cache-update-added", nullptr);
+ LOG(("Done offline-cache-update-added"));
+ }
+
+ if (!EnsureUpdate()) return NS_ERROR_NULL_POINTER;
+
+ // Do not use weak reference, we must survive!
+ mUpdate->AddObserver(this, false);
+
+ if (mCoalesced) // already scheduled
+ return NS_OK;
+
+ return mUpdate->Schedule();
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateGlue::Init(nsIURI* aManifestURI, nsIURI* aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ Document* aDocument, nsIFile* aCustomProfileDir) {
+ nsresult rv;
+
+ nsAutoCString originSuffix;
+ rv = aLoadingPrincipal->GetOriginSuffix(originSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsOfflineCacheUpdateService* service =
+ nsOfflineCacheUpdateService::EnsureService();
+ if (service) {
+ service->FindUpdate(aManifestURI, originSuffix, aCustomProfileDir,
+ getter_AddRefs(mUpdate));
+ mCoalesced = !!mUpdate;
+ }
+
+ if (!EnsureUpdate()) return NS_ERROR_NULL_POINTER;
+
+ mDocumentURI = aDocumentURI;
+ mLoadingPrincipal = aLoadingPrincipal;
+
+ if (aDocument) SetDocument(aDocument);
+
+ if (mCoalesced) { // already initialized
+ LOG(("OfflineCacheUpdateGlue %p coalesced with update %p", this,
+ mUpdate.get()));
+ return NS_OK;
+ }
+
+ rv = mUpdate->Init(aManifestURI, aDocumentURI, aLoadingPrincipal, nullptr,
+ aCustomProfileDir);
+
+ mUpdate->SetCookieJarSettings(mCookieJarSettings);
+
+ return rv;
+}
+
+void OfflineCacheUpdateGlue::SetDocument(Document* aDocument) {
+ // The design is one document for one cache update on the content process.
+ NS_ASSERTION(!mDocument,
+ "Setting more then a single document on an instance of "
+ "OfflineCacheUpdateGlue");
+
+ LOG(("Document %p added to update glue %p", aDocument, this));
+
+ // Add document only if it was not loaded from an offline cache.
+ // If it were loaded from an offline cache then it has already
+ // been associated with it and must not be again cached as
+ // implicit (which are the reasons we collect documents here).
+ if (!aDocument) return;
+
+ mCookieJarSettings = aDocument->CookieJarSettings();
+
+ nsIChannel* channel = aDocument->GetChannel();
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(channel);
+ if (!appCacheChannel) return;
+
+ bool loadedFromAppCache;
+ appCacheChannel->GetLoadedFromApplicationCache(&loadedFromAppCache);
+ if (loadedFromAppCache) return;
+
+ if (EnsureUpdate()) {
+ mUpdate->StickDocument(mDocumentURI);
+ }
+
+ mDocument = aDocument;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateGlue::UpdateStateChanged(nsIOfflineCacheUpdate* aUpdate,
+ uint32_t state) {
+ if (state == nsIOfflineCacheUpdateObserver::STATE_FINISHED) {
+ LOG(("OfflineCacheUpdateGlue got STATE_FINISHED [%p]", this));
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ LOG(("Calling offline-cache-update-completed"));
+ observerService->NotifyObservers(
+ static_cast<nsIOfflineCacheUpdate*>(this),
+ "offline-cache-update-completed", nullptr);
+ LOG(("Done offline-cache-update-completed"));
+ }
+
+ aUpdate->RemoveObserver(this);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateGlue::ApplicationCacheAvailable(
+ nsIApplicationCache* aApplicationCache) {
+ NS_ENSURE_ARG(aApplicationCache);
+
+ // Check that the document that requested this update was
+ // previously associated with an application cache. If not, it
+ // should be associated with the new one.
+ if (!mDocument) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIApplicationCache> existingCache;
+ nsresult rv = mDocument->GetApplicationCache(getter_AddRefs(existingCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!existingCache) {
+ if (LOG_ENABLED()) {
+ nsAutoCString clientID;
+ if (aApplicationCache) {
+ aApplicationCache->GetClientID(clientID);
+ }
+ LOG(("Update %p: associating app cache %s to document %p", this,
+ clientID.get(), mDocument.get()));
+ }
+
+ rv = mDocument->SetApplicationCache(aApplicationCache);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+} // namespace docshell
+} // namespace mozilla
diff --git a/uriloader/prefetch/OfflineCacheUpdateGlue.h b/uriloader/prefetch/OfflineCacheUpdateGlue.h
new file mode 100644
index 0000000000..7c1d08cc73
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateGlue.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsOfflineCacheUpdateGlue_h
+#define nsOfflineCacheUpdateGlue_h
+
+#include "nsIOfflineCacheUpdate.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+
+class nsOfflineCacheUpdate;
+
+namespace mozilla {
+namespace dom {
+class Document;
+}
+
+namespace docshell {
+
+// Like FORWARD_SAFE except methods:
+// Schedule
+// Init
+#define NS_ADJUSTED_FORWARD_NSIOFFLINECACHEUPDATE(_to) \
+ NS_IMETHOD GetStatus(uint16_t* aStatus) override { \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->GetStatus(aStatus); \
+ } \
+ NS_IMETHOD GetPartial(bool* aPartial) override { \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->GetPartial(aPartial); \
+ } \
+ NS_IMETHOD GetIsUpgrade(bool* aIsUpgrade) override { \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->GetIsUpgrade(aIsUpgrade); \
+ } \
+ NS_IMETHOD GetUpdateDomain(nsACString& aUpdateDomain) override { \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->GetUpdateDomain(aUpdateDomain); \
+ } \
+ NS_IMETHOD GetLoadingPrincipal(nsIPrincipal** aLoadingPrincipal) override { \
+ return !_to ? NS_ERROR_NULL_POINTER \
+ : _to->GetLoadingPrincipal(aLoadingPrincipal); \
+ } \
+ NS_IMETHOD GetManifestURI(nsIURI** aManifestURI) override { \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->GetManifestURI(aManifestURI); \
+ } \
+ NS_IMETHOD GetSucceeded(bool* aSucceeded) override { \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->GetSucceeded(aSucceeded); \
+ } \
+ NS_IMETHOD InitPartial(nsIURI* aManifestURI, const nsACString& aClientID, \
+ nsIURI* aDocumentURI, \
+ nsIPrincipal* aLoadingPrincipal, \
+ nsICookieJarSettings* aCookieJarSettings) override { \
+ return !_to ? NS_ERROR_NULL_POINTER \
+ : _to->InitPartial(aManifestURI, aClientID, aDocumentURI, \
+ aLoadingPrincipal, aCookieJarSettings); \
+ } \
+ NS_IMETHOD InitForUpdateCheck(nsIURI* aManifestURI, \
+ nsIPrincipal* aLoadingPrincipal, \
+ nsIObserver* aObserver) override { \
+ return !_to ? NS_ERROR_NULL_POINTER \
+ : _to->InitForUpdateCheck(aManifestURI, aLoadingPrincipal, \
+ aObserver); \
+ } \
+ NS_IMETHOD AddDynamicURI(nsIURI* aURI) override { \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->AddDynamicURI(aURI); \
+ } \
+ NS_IMETHOD AddObserver(nsIOfflineCacheUpdateObserver* aObserver, \
+ bool aHoldWeak) override { \
+ return !_to ? NS_ERROR_NULL_POINTER \
+ : _to->AddObserver(aObserver, aHoldWeak); \
+ } \
+ NS_IMETHOD RemoveObserver(nsIOfflineCacheUpdateObserver* aObserver) \
+ override { \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->RemoveObserver(aObserver); \
+ } \
+ NS_IMETHOD GetByteProgress(uint64_t* _result) override { \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->GetByteProgress(_result); \
+ } \
+ NS_IMETHOD Cancel() override { \
+ return !_to ? NS_ERROR_NULL_POINTER : _to->Cancel(); \
+ }
+
+class OfflineCacheUpdateGlue final : public nsSupportsWeakReference,
+ public nsIOfflineCacheUpdate,
+ public nsIOfflineCacheUpdateObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ private:
+ nsIOfflineCacheUpdate* EnsureUpdate();
+
+ public:
+ NS_ADJUSTED_FORWARD_NSIOFFLINECACHEUPDATE(EnsureUpdate())
+ NS_IMETHOD Schedule(void) override;
+ NS_IMETHOD Init(nsIURI* aManifestURI, nsIURI* aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ mozilla::dom::Document* aDocument,
+ nsIFile* aCustomProfileDir) override;
+
+ NS_DECL_NSIOFFLINECACHEUPDATEOBSERVER
+
+ OfflineCacheUpdateGlue();
+
+ void SetDocument(mozilla::dom::Document* aDocument);
+
+ private:
+ ~OfflineCacheUpdateGlue();
+
+ RefPtr<nsOfflineCacheUpdate> mUpdate;
+ bool mCoalesced;
+
+ /* Document that requested this update */
+ RefPtr<mozilla::dom::Document> mDocument;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+ nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
+};
+
+} // namespace docshell
+} // namespace mozilla
+
+#endif
diff --git a/uriloader/prefetch/OfflineCacheUpdateParent.cpp b/uriloader/prefetch/OfflineCacheUpdateParent.cpp
new file mode 100644
index 0000000000..3414617385
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateParent.cpp
@@ -0,0 +1,287 @@
+/* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "OfflineCacheUpdateParent.h"
+
+#include "BackgroundUtils.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/Unused.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsOfflineCacheUpdate.h"
+#include "nsIApplicationCache.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla::ipc;
+using mozilla::BasePrincipal;
+using mozilla::OriginAttributes;
+using mozilla::dom::BrowserParent;
+
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=nsOfflineCacheUpdate:5
+// set MOZ_LOG_FILE=offlineupdate.log
+//
+// this enables LogLevel::Debug level information and places all output in
+// the file offlineupdate.log
+//
+extern mozilla::LazyLogModule gOfflineCacheUpdateLog;
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug, args)
+
+#undef LOG_ENABLED
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug)
+
+namespace mozilla {
+namespace docshell {
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateParent::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(OfflineCacheUpdateParent, nsIOfflineCacheUpdateObserver,
+ nsILoadContext)
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateParent <public>
+//-----------------------------------------------------------------------------
+
+OfflineCacheUpdateParent::OfflineCacheUpdateParent() : mIPCClosed(false) {
+ // Make sure the service has been initialized
+ nsOfflineCacheUpdateService::EnsureService();
+
+ LOG(("OfflineCacheUpdateParent::OfflineCacheUpdateParent [%p]", this));
+}
+
+OfflineCacheUpdateParent::~OfflineCacheUpdateParent() {
+ LOG(("OfflineCacheUpdateParent::~OfflineCacheUpdateParent [%p]", this));
+}
+
+void OfflineCacheUpdateParent::ActorDestroy(ActorDestroyReason why) {
+ mIPCClosed = true;
+}
+
+nsresult OfflineCacheUpdateParent::Schedule(
+ nsIURI* aManifestURI, nsIURI* aDocumentURI,
+ const PrincipalInfo& aLoadingPrincipalInfo, const bool& stickDocument,
+ const CookieJarSettingsArgs& aCookieJarSettingsArgs) {
+ LOG(("OfflineCacheUpdateParent::RecvSchedule [%p]", this));
+
+ RefPtr<nsOfflineCacheUpdate> update;
+ if (!aManifestURI) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto loadingPrincipalOrErr = PrincipalInfoToPrincipal(aLoadingPrincipalInfo);
+
+ if (NS_WARN_IF(loadingPrincipalOrErr.isErr())) {
+ return loadingPrincipalOrErr.unwrapErr();
+ }
+
+ mLoadingPrincipal = loadingPrincipalOrErr.unwrap();
+
+ nsOfflineCacheUpdateService* service =
+ nsOfflineCacheUpdateService::EnsureService();
+ if (!service) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool offlinePermissionAllowed = false;
+
+ nsresult rv =
+ service->OfflineAppAllowed(mLoadingPrincipal, &offlinePermissionAllowed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!offlinePermissionAllowed) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ if (!aDocumentURI) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!NS_SecurityCompareURIs(aManifestURI, aDocumentURI, false)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsAutoCString originSuffix;
+ rv = mLoadingPrincipal->GetOriginSuffix(originSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ service->FindUpdate(aManifestURI, originSuffix, nullptr,
+ getter_AddRefs(update));
+ if (!update) {
+ update = new nsOfflineCacheUpdate();
+
+ // Leave aDocument argument null. Only glues and children keep
+ // document instances.
+ rv = update->Init(aManifestURI, aDocumentURI, mLoadingPrincipal, nullptr,
+ nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ update->SetCookieJarSettingsArgs(aCookieJarSettingsArgs);
+
+ // Must add before Schedule() call otherwise we would miss
+ // oncheck event notification.
+ update->AddObserver(this, false);
+
+ rv = update->Schedule();
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ update->AddObserver(this, false);
+ }
+
+ if (stickDocument) {
+ update->StickDocument(aDocumentURI);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::UpdateStateChanged(nsIOfflineCacheUpdate* aUpdate,
+ uint32_t state) {
+ if (mIPCClosed) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ LOG(("OfflineCacheUpdateParent::StateEvent [%p]", this));
+
+ uint64_t byteProgress;
+ aUpdate->GetByteProgress(&byteProgress);
+ Unused << SendNotifyStateEvent(state, byteProgress);
+
+ if (state == nsIOfflineCacheUpdateObserver::STATE_FINISHED) {
+ // Tell the child the particulars after the update has finished.
+ // Sending the Finish event will release the child side of the protocol
+ // and notify "offline-cache-update-completed" on the child process.
+ bool isUpgrade;
+ aUpdate->GetIsUpgrade(&isUpgrade);
+ bool succeeded;
+ aUpdate->GetSucceeded(&succeeded);
+
+ Unused << SendFinish(succeeded, isUpgrade);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::ApplicationCacheAvailable(
+ nsIApplicationCache* aApplicationCache) {
+ if (mIPCClosed) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ NS_ENSURE_ARG(aApplicationCache);
+
+ nsCString cacheClientId;
+ aApplicationCache->GetClientID(cacheClientId);
+ nsCString cacheGroupId;
+ aApplicationCache->GetGroupID(cacheGroupId);
+
+ Unused << SendAssociateDocuments(cacheGroupId, cacheClientId);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateParent::nsILoadContext
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetAssociatedWindow(
+ mozIDOMWindowProxy** aAssociatedWindow) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetTopWindow(mozIDOMWindowProxy** aTopWindow) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetTopFrameElement(dom::Element** aElement) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetIsContent(bool* aIsContent) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+OfflineCacheUpdateParent::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::SetPrivateBrowsing(bool aUsePrivateBrowsing) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetUseRemoteTabs(bool* aUseRemoteTabs) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::SetRemoteTabs(bool aUseRemoteTabs) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetUseRemoteSubframes(bool* aUseRemoteSubframes) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::SetRemoteSubframes(bool aUseRemoteSubframes) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetScriptableOriginAttributes(
+ JSContext* aCx, JS::MutableHandleValue aAttrs) {
+ NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_UNEXPECTED);
+
+ nsresult rv = mLoadingPrincipal->GetOriginAttributes(aCx, aAttrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+OfflineCacheUpdateParent::GetOriginAttributes(
+ mozilla::OriginAttributes& aAttrs) {
+ if (mLoadingPrincipal) {
+ aAttrs = mLoadingPrincipal->OriginAttributesRef();
+ }
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetUseTrackingProtection(
+ bool* aUseTrackingProtection) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::SetUseTrackingProtection(
+ bool aUseTrackingProtection) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace docshell
+} // namespace mozilla
diff --git a/uriloader/prefetch/OfflineCacheUpdateParent.h b/uriloader/prefetch/OfflineCacheUpdateParent.h
new file mode 100644
index 0000000000..b1bc3d90cc
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateParent.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsOfflineCacheUpdateParent_h
+#define nsOfflineCacheUpdateParent_h
+
+#include "mozilla/docshell/POfflineCacheUpdateParent.h"
+#include "mozilla/BasePrincipal.h"
+#include "nsIOfflineCacheUpdate.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsILoadContext.h"
+
+class nsIPrincipal;
+
+namespace mozilla {
+
+namespace ipc {
+class URIParams;
+} // namespace ipc
+
+namespace net {
+class CookieJarSettingsArgs;
+}
+
+namespace docshell {
+
+class OfflineCacheUpdateParent : public POfflineCacheUpdateParent,
+ public nsIOfflineCacheUpdateObserver,
+ public nsILoadContext {
+ typedef mozilla::ipc::URIParams URIParams;
+ typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOFFLINECACHEUPDATEOBSERVER
+ NS_DECL_NSILOADCONTEXT
+
+ nsresult Schedule(nsIURI* manifestURI, nsIURI* documentURI,
+ const PrincipalInfo& loadingPrincipalInfo,
+ const bool& stickDocument,
+ const net::CookieJarSettingsArgs& aCookieJarSettingsArgs);
+
+ void StopSendingMessagesToChild() { mIPCClosed = true; }
+
+ explicit OfflineCacheUpdateParent();
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ private:
+ ~OfflineCacheUpdateParent();
+
+ bool mIPCClosed;
+
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+};
+
+} // namespace docshell
+} // namespace mozilla
+
+#endif
diff --git a/uriloader/prefetch/POfflineCacheUpdate.ipdl b/uriloader/prefetch/POfflineCacheUpdate.ipdl
new file mode 100644
index 0000000000..5b4a0961a0
--- /dev/null
+++ b/uriloader/prefetch/POfflineCacheUpdate.ipdl
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* 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 protocol PContent;
+
+namespace mozilla {
+namespace docshell {
+
+//-------------------------------------------------------------------
+refcounted protocol POfflineCacheUpdate
+{
+ manager PContent;
+
+parent:
+ async __delete__();
+
+child:
+ async NotifyStateEvent(uint32_t stateEvent, uint64_t byteProgress);
+ async AssociateDocuments(nsCString cacheGroupId, nsCString cacheClientId);
+ async Finish(bool succeeded, bool isUpgrade);
+};
+
+}
+}
diff --git a/uriloader/prefetch/moz.build b/uriloader/prefetch/moz.build
new file mode 100644
index 0000000000..9c3a00bac4
--- /dev/null
+++ b/uriloader/prefetch/moz.build
@@ -0,0 +1,44 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking: Cache")
+
+XPIDL_SOURCES += [
+ "nsIOfflineCacheUpdate.idl",
+ "nsIPrefetchService.idl",
+]
+
+XPIDL_MODULE = "prefetch"
+
+EXPORTS.mozilla.docshell += [
+ "OfflineCacheUpdateChild.h",
+ "OfflineCacheUpdateParent.h",
+]
+
+UNIFIED_SOURCES += [
+ "nsOfflineCacheUpdate.cpp",
+ "nsOfflineCacheUpdateService.cpp",
+ "nsPrefetchService.cpp",
+ "OfflineCacheUpdateChild.cpp",
+ "OfflineCacheUpdateGlue.cpp",
+ "OfflineCacheUpdateParent.cpp",
+]
+
+IPDL_SOURCES += [
+ "POfflineCacheUpdate.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+]
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/uriloader/prefetch/nsIOfflineCacheUpdate.idl b/uriloader/prefetch/nsIOfflineCacheUpdate.idl
new file mode 100644
index 0000000000..00dc8c6e19
--- /dev/null
+++ b/uriloader/prefetch/nsIOfflineCacheUpdate.idl
@@ -0,0 +1,291 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindow;
+interface nsIURI;
+interface nsIOfflineCacheUpdate;
+interface nsIPrincipal;
+interface nsIPrefBranch;
+interface nsIApplicationCache;
+interface nsIFile;
+interface nsIObserver;
+interface nsICookieJarSettings;
+webidl Document;
+
+[scriptable, uuid(47360d57-8ef4-4a5d-8865-1a27a739ad1a)]
+interface nsIOfflineCacheUpdateObserver : nsISupports {
+ const unsigned long STATE_ERROR = 1;
+ const unsigned long STATE_CHECKING = 2;
+ const unsigned long STATE_NOUPDATE = 3;
+ const unsigned long STATE_OBSOLETE = 4;
+ const unsigned long STATE_DOWNLOADING = 5;
+ const unsigned long STATE_ITEMSTARTED = 6;
+ const unsigned long STATE_ITEMCOMPLETED = 7;
+ const unsigned long STATE_ITEMPROGRESS = 8;
+ const unsigned long STATE_FINISHED = 10;
+
+ /**
+ * aUpdate has changed its state.
+ *
+ * @param aUpdate
+ * The nsIOfflineCacheUpdate being processed.
+ * @param event
+ * See enumeration above
+ */
+ void updateStateChanged(in nsIOfflineCacheUpdate aUpdate, in uint32_t state);
+
+ /**
+ * Informs the observer about an application being available to associate.
+ *
+ * @param applicationCache
+ * The application cache instance that has been created or found by the
+ * update to associate with
+ */
+ void applicationCacheAvailable(in nsIApplicationCache applicationCache);
+};
+
+/**
+ * An nsIOfflineCacheUpdate is used to update an application's offline
+ * resources.
+ *
+ * It can be used to perform partial or complete updates.
+ *
+ * One update object will be updating at a time. The active object will
+ * load its items one by one, sending itemCompleted() to any registered
+ * observers.
+ */
+[scriptable, uuid(6e3e26ea-45b2-4db7-9e4a-93b965679298)]
+interface nsIOfflineCacheUpdate : nsISupports {
+ /**
+ * Fetch the status of the running update. This will return a value
+ * defined in OfflineResourceList.webidl.
+ */
+ readonly attribute unsigned short status;
+
+ /**
+ * TRUE if the update is being used to add specific resources.
+ * FALSE if the complete cache update process is happening.
+ */
+ readonly attribute boolean partial;
+
+ /**
+ * TRUE if this is an upgrade attempt, FALSE if it is a new cache
+ * attempt.
+ */
+ readonly attribute boolean isUpgrade;
+
+ /**
+ * The domain being updated, and the domain that will own any URIs added
+ * with this update.
+ */
+ readonly attribute ACString updateDomain;
+
+ /**
+ * The manifest for the offline application being updated.
+ */
+ readonly attribute nsIURI manifestURI;
+
+ /**
+ * The principal of the page that is requesting the update.
+ */
+ readonly attribute nsIPrincipal loadingPrincipal;
+
+ /**
+ * TRUE if the cache update completed successfully.
+ */
+ readonly attribute boolean succeeded;
+
+ /**
+ * Initialize the update.
+ *
+ * @param aManifestURI
+ * The manifest URI to be checked.
+ * @param aDocumentURI
+ * The page that is requesting the update.
+ * @param aLoadingPrincipal
+ * The principal of the page that is requesting the update.
+ */
+ void init(in nsIURI aManifestURI,
+ in nsIURI aDocumentURI,
+ in nsIPrincipal aLoadingPrincipal,
+ in Document aDocument,
+ [optional] in nsIFile aCustomProfileDir);
+
+ /**
+ * Initialize the update for partial processing.
+ *
+ * @param aManifestURI
+ * The manifest URI of the related cache.
+ * @param aClientID
+ * Client ID of the cache to store resource to. This ClientID
+ * must be ID of cache in the cache group identified by
+ * the manifest URI passed in the first parameter.
+ * @param aDocumentURI
+ * The page that is requesting the update. May be null
+ * when this information is unknown.
+ * @param aCookieJarSettings
+ * The cookie jar settings belonging to the page that is requesting
+ * the update.
+ */
+ void initPartial(in nsIURI aManifestURI, in ACString aClientID,
+ in nsIURI aDocumentURI, in nsIPrincipal aPrincipal,
+ in nsICookieJarSettings aCookieJarSettings);
+
+ /**
+ * Initialize the update to only check whether there is an update
+ * to the manifest available (if it has actually changed on the server).
+ *
+ * @param aManifestURI
+ * The manifest URI of the related cache.
+ * @param aObserver
+ * nsIObserver implementation that receives the result.
+ * When aTopic == "offline-cache-update-available" there is an update to
+ * to download. Update of the app cache will lead to a new version
+ * download.
+ * When aTopic == "offline-cache-update-unavailable" then there is no
+ * update available (the manifest has not changed on the server).
+ */
+ void initForUpdateCheck(in nsIURI aManifestURI,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIObserver aObserver);
+
+ /**
+ * Add a dynamic URI to the offline cache as part of the update.
+ *
+ * @param aURI
+ * The URI to add.
+ */
+ void addDynamicURI(in nsIURI aURI);
+
+ /**
+ * Add the update to the offline update queue. An offline-cache-update-added
+ * event will be sent to the observer service.
+ */
+ void schedule();
+
+ /**
+ * Observe loads that are added to the update.
+ *
+ * @param aObserver
+ * object that notifications will be sent to.
+ * @param aHoldWeak
+ * TRUE if you want the update to hold a weak reference to the
+ * observer, FALSE for a strong reference.
+ */
+ void addObserver(in nsIOfflineCacheUpdateObserver aObserver,
+ [optional] in boolean aHoldWeak);
+
+ /**
+ * Remove an observer from the update.
+ *
+ * @param aObserver
+ * the observer to remove.
+ */
+ void removeObserver(in nsIOfflineCacheUpdateObserver aObserver);
+
+ /**
+ * Cancel the update when still in progress. This stops all running resource
+ * downloads and discards the downloaded cache version. Throws when update
+ * has already finished and made the new cache version active.
+ */
+ void cancel();
+
+ /**
+ * Return the number of bytes downloaded so far
+ */
+ readonly attribute uint64_t byteProgress;
+};
+
+[scriptable, uuid(44971e74-37e4-4140-8677-a4cf213a3f4b)]
+interface nsIOfflineCacheUpdateService : nsISupports {
+ /**
+ * Constants for the offline-app permission.
+ *
+ * XXX: This isn't a great place for this, but it's really the only
+ * private offline-app-related interface
+ */
+
+ /**
+ * Allow the domain to use offline APIs, and don't warn about excessive
+ * usage.
+ */
+ const unsigned long ALLOW_NO_WARN = 3;
+
+ /**
+ * Access to the list of cache updates that have been scheduled.
+ */
+ readonly attribute unsigned long numUpdates;
+ nsIOfflineCacheUpdate getUpdate(in unsigned long index);
+
+ /**
+ * Schedule a cache update for a given offline manifest. If an
+ * existing update is scheduled or running, that update will be returned.
+ * Otherwise a new update will be scheduled.
+ */
+ nsIOfflineCacheUpdate scheduleUpdate(in nsIURI aManifestURI,
+ in nsIURI aDocumentURI,
+ in nsIPrincipal aLoadingPrincipal,
+ in mozIDOMWindow aWindow);
+
+ /**
+ * Schedule a cache update for a given offline manifest using app cache
+ * bound to the given appID flag. If an existing update is scheduled or
+ * running, that update will be returned. Otherwise a new update will be
+ * scheduled.
+ */
+ nsIOfflineCacheUpdate scheduleAppUpdate(in nsIURI aManifestURI,
+ in nsIURI aDocumentURI,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIFile aProfileDir);
+
+ /**
+ * Schedule a cache update for a manifest when the document finishes
+ * loading.
+ */
+ void scheduleOnDocumentStop(in nsIURI aManifestURI,
+ in nsIURI aDocumentURI,
+ in nsIPrincipal aLoadingPrincipal,
+ in Document aDocument);
+
+ /**
+ * Schedule a check to see if an update is available.
+ *
+ * This will not update or make any changes to the appcache.
+ * It only notifies the observer to indicate whether the manifest has
+ * changed on the server (or not): a changed manifest means that an
+ * update is available.
+ *
+ * For arguments see nsIOfflineCacheUpdate.initForUpdateCheck() method
+ * description.
+ */
+ void checkForUpdate(in nsIURI aManifestURI,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIObserver aObserver);
+
+ /**
+ * Checks whether a principal should have access to the offline
+ * cache.
+ * @param aPrincipal
+ * The principal to check.
+ */
+ boolean offlineAppAllowed(in nsIPrincipal aPrincipal);
+
+ /**
+ * Checks whether a document at the given URI should have access
+ * to the offline cache.
+ * @param aURI
+ * The URI to check
+ */
+ boolean offlineAppAllowedForURI(in nsIURI aURI);
+
+ /**
+ * Sets the "offline-app" permission for the principal.
+ * In the single process model calls directly on permission manager.
+ * In the multi process model dispatches to the parent process.
+ */
+ void allowOfflineApp(in nsIPrincipal aPrincipal);
+};
diff --git a/uriloader/prefetch/nsIPrefetchService.idl b/uriloader/prefetch/nsIPrefetchService.idl
new file mode 100644
index 0000000000..160d051116
--- /dev/null
+++ b/uriloader/prefetch/nsIPrefetchService.idl
@@ -0,0 +1,54 @@
+/* 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 "nsISupports.idl"
+#include "nsIContentPolicy.idl"
+
+interface nsIURI;
+interface nsISimpleEnumerator;
+interface nsIReferrerInfo;
+
+webidl Node;
+
+[scriptable, uuid(422a1807-4e7f-463d-b8d7-ca2ceb9b5d53)]
+interface nsIPrefetchService : nsISupports
+{
+ /**
+ * Enqueue a request to prefetch the specified URI.
+ *
+ * @param aURI the URI of the document to prefetch
+ * @param aReferrerInfo the referrerInfo of the request
+ * @param aSource the DOM node (such as a <link> tag) that requested this
+ * fetch, or null if the prefetch was not requested by a DOM node.
+ * @param aExplicit the link element has an explicit prefetch link type
+ */
+ void prefetchURI(in nsIURI aURI,
+ in nsIReferrerInfo aReferrerInfo,
+ in Node aSource,
+ in boolean aExplicit);
+
+ /**
+ * Start a preload of the specified URI.
+ *
+ * @param aURI the URI of the document to preload
+ * @param aReferrerInfo the referrerInfo of the request
+ * @param aSource the DOM node (such as a <link> tag) that requested this
+ * fetch, or null if the prefetch was not requested by a DOM node.
+ * @param aPolicyType content policy to be used for this load.
+ */
+ void preloadURI(in nsIURI aURI,
+ in nsIReferrerInfo aReferrerInfo,
+ in Node aSource,
+ in nsContentPolicyType aPolicyType);
+
+ /**
+ * Find out if there are any prefetches running or queued
+ */
+ boolean hasMoreElements();
+
+ /**
+ * Cancel prefetch or preload for a Node.
+ */
+ void cancelPrefetchPreloadURI(in nsIURI aURI, in Node aSource);
+};
diff --git a/uriloader/prefetch/nsOfflineCacheUpdate.cpp b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
new file mode 100644
index 0000000000..62561cdaa3
--- /dev/null
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
@@ -0,0 +1,2332 @@
+/* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsOfflineCacheUpdate.h"
+
+#include "nsCURILoader.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIApplicationCacheService.h"
+#include "nsICachingChannel.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/OfflineResourceListBinding.h"
+#include "mozilla/dom/Document.h"
+#include "nsIURL.h"
+#include "nsICryptoHash.h"
+#include "nsICacheEntry.h"
+#include "nsIHttpChannel.h"
+#include "nsIPrincipal.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "nsIConsoleService.h"
+#include "mozilla/Logging.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Attributes.h"
+#include "nsContentUtils.h"
+#include "nsIPrincipal.h"
+#include "nsDiskCacheDeviceSQL.h"
+#include "ReferrerInfo.h"
+
+#include "nsXULAppAPI.h"
+
+using namespace mozilla;
+
+static const uint32_t kRescheduleLimit = 3;
+// Max number of retries for every entry of pinned app.
+static const uint32_t kPinnedEntryRetriesLimit = 3;
+// Maximum number of parallel items loads
+static const uint32_t kParallelLoadLimit = 15;
+
+// Quota for offline apps when preloading
+static const int32_t kCustomProfileQuota = 512000;
+
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=nsOfflineCacheUpdate:5
+// set MOZ_LOG_FILE=offlineupdate.log
+//
+// this enables LogLevel::Debug level information and places all output in
+// the file offlineupdate.log
+//
+extern LazyLogModule gOfflineCacheUpdateLog;
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug, args)
+
+#undef LOG_ENABLED
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug)
+
+namespace {
+
+nsresult DropReferenceFromURL(nsCOMPtr<nsIURI>& aURI) {
+ // XXXdholbert If this SetRef fails, callers of this method probably
+ // want to call aURI->CloneIgnoringRef() and use the result of that.
+ nsCOMPtr<nsIURI> uri(aURI);
+ return NS_GetURIWithoutRef(uri, getter_AddRefs(aURI));
+}
+
+void LogToConsole(const char* message,
+ nsOfflineCacheUpdateItem* item = nullptr) {
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService) {
+ nsAutoString messageUTF16 = NS_ConvertUTF8toUTF16(message);
+ if (item && item->mURI) {
+ messageUTF16.AppendLiteral(", URL=");
+ messageUTF16.Append(
+ NS_ConvertUTF8toUTF16(item->mURI->GetSpecOrDefault()));
+ }
+ consoleService->LogStringMessage(messageUTF16.get());
+ }
+}
+
+} // namespace
+
+//-----------------------------------------------------------------------------
+// nsManifestCheck
+//-----------------------------------------------------------------------------
+
+class nsManifestCheck final : public nsIStreamListener,
+ public nsIChannelEventSink,
+ public nsIInterfaceRequestor {
+ public:
+ nsManifestCheck(nsOfflineCacheUpdate* aUpdate, nsIURI* aURI,
+ nsIURI* aReferrerURI, nsIPrincipal* aLoadingPrincipal)
+ : mUpdate(aUpdate),
+ mURI(aURI),
+ mReferrerURI(aReferrerURI),
+ mLoadingPrincipal(aLoadingPrincipal) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ nsresult Begin();
+
+ private:
+ ~nsManifestCheck() {}
+
+ static nsresult ReadManifest(nsIInputStream* aInputStream, void* aClosure,
+ const char* aFromSegment, uint32_t aOffset,
+ uint32_t aCount, uint32_t* aBytesConsumed);
+
+ RefPtr<nsOfflineCacheUpdate> mUpdate;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mReferrerURI;
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+ nsCOMPtr<nsICryptoHash> mManifestHash;
+ nsCOMPtr<nsIChannel> mChannel;
+};
+
+//-----------------------------------------------------------------------------
+// nsManifestCheck::nsISupports
+//-----------------------------------------------------------------------------
+NS_IMPL_ISUPPORTS(nsManifestCheck, nsIRequestObserver, nsIStreamListener,
+ nsIChannelEventSink, nsIInterfaceRequestor)
+
+//-----------------------------------------------------------------------------
+// nsManifestCheck <public>
+//-----------------------------------------------------------------------------
+
+nsresult nsManifestCheck::Begin() {
+ nsresult rv;
+ mManifestHash = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mManifestHash->Init(nsICryptoHash::MD5);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NS_NewChannel(getter_AddRefs(mChannel), mURI, mLoadingPrincipal,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+ nsIContentPolicy::TYPE_OTHER, mUpdate->CookieJarSettings(),
+ nullptr, // PerformanceStorage
+ nullptr, // loadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_BYPASS_CACHE);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // configure HTTP specific stuff
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (httpChannel) {
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ new mozilla::dom::ReferrerInfo(mReferrerURI);
+ rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv =
+ httpChannel->SetRequestHeader("X-Moz"_ns, "offline-resource"_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ return mChannel->AsyncOpen(this);
+}
+
+//-----------------------------------------------------------------------------
+// nsManifestCheck <public>
+//-----------------------------------------------------------------------------
+
+/* static */
+nsresult nsManifestCheck::ReadManifest(nsIInputStream* aInputStream,
+ void* aClosure, const char* aFromSegment,
+ uint32_t aOffset, uint32_t aCount,
+ uint32_t* aBytesConsumed) {
+ nsManifestCheck* manifestCheck = static_cast<nsManifestCheck*>(aClosure);
+
+ nsresult rv;
+ *aBytesConsumed = aCount;
+
+ rv = manifestCheck->mManifestHash->Update(
+ reinterpret_cast<const uint8_t*>(aFromSegment), aCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsManifestCheck::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsManifestCheck::OnStartRequest(nsIRequest* aRequest) { return NS_OK; }
+
+NS_IMETHODIMP
+nsManifestCheck::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aCount) {
+ uint32_t bytesRead;
+ aStream->ReadSegments(ReadManifest, this, aCount, &bytesRead);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsManifestCheck::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ nsAutoCString manifestHash;
+ if (NS_SUCCEEDED(aStatus)) {
+ mManifestHash->Finish(true, manifestHash);
+ }
+
+ mUpdate->ManifestCheckCompleted(aStatus, manifestHash);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsManifestCheck::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsManifestCheck::GetInterface(const nsIID& aIID, void** aResult) {
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *aResult = static_cast<nsIChannelEventSink*>(this);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+//-----------------------------------------------------------------------------
+// nsManifestCheck::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsManifestCheck::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ // Redirects should cause the load (and therefore the update) to fail.
+ if (aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+ }
+
+ LogToConsole("Manifest check failed because its response is a redirect");
+
+ aOldChannel->Cancel(NS_ERROR_ABORT);
+ return NS_ERROR_ABORT;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheUpdateItem, nsIRequestObserver,
+ nsIStreamListener, nsIRunnable, nsIInterfaceRequestor,
+ nsIChannelEventSink)
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem <public>
+//-----------------------------------------------------------------------------
+
+nsOfflineCacheUpdateItem::nsOfflineCacheUpdateItem(
+ nsIURI* aURI, nsIURI* aReferrerURI, nsIPrincipal* aLoadingPrincipal,
+ nsIApplicationCache* aApplicationCache,
+ nsIApplicationCache* aPreviousApplicationCache, uint32_t type,
+ uint32_t loadFlags)
+ : mURI(aURI),
+ mReferrerURI(aReferrerURI),
+ mLoadingPrincipal(aLoadingPrincipal),
+ mApplicationCache(aApplicationCache),
+ mPreviousApplicationCache(aPreviousApplicationCache),
+ mItemType(type),
+ mLoadFlags(loadFlags),
+ mChannel(nullptr),
+ mState(LoadStatus::UNINITIALIZED),
+ mBytesRead(0) {}
+
+nsOfflineCacheUpdateItem::~nsOfflineCacheUpdateItem() {}
+
+nsresult nsOfflineCacheUpdateItem::OpenChannel(nsOfflineCacheUpdate* aUpdate) {
+ if (LOG_ENABLED()) {
+ LOG(("%p: Opening channel for %s", this, mURI->GetSpecOrDefault().get()));
+ }
+
+ if (mUpdate) {
+ // Holding a reference to the update means this item is already
+ // in progress (has a channel, or is just in between OnStopRequest()
+ // and its Run() call. We must never open channel on this item again.
+ LOG((" %p is already running! ignoring", this));
+ return NS_ERROR_ALREADY_OPENED;
+ }
+
+ nsresult rv = nsOfflineCacheUpdate::GetCacheKey(mURI, mCacheKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t flags =
+ nsIRequest::LOAD_BACKGROUND | nsICachingChannel::LOAD_ONLY_IF_MODIFIED;
+
+ if (mApplicationCache == mPreviousApplicationCache) {
+ // Same app cache to read from and to write to is used during
+ // an only-update-check procedure. Here we protect the existing
+ // cache from being modified.
+ flags |= nsIRequest::INHIBIT_CACHING;
+ }
+
+ flags |= mLoadFlags;
+
+ rv = NS_NewChannel(getter_AddRefs(mChannel), mURI, mLoadingPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER, aUpdate->CookieJarSettings(),
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ this, // aCallbacks
+ flags);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(mChannel, &rv);
+
+ // Support for nsIApplicationCacheChannel is required.
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Use the existing application cache as the cache to check.
+ rv = appCacheChannel->SetApplicationCache(mPreviousApplicationCache);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set the new application cache as the target for write.
+ rv = appCacheChannel->SetApplicationCacheForWrite(mApplicationCache);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // configure HTTP specific stuff
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (httpChannel) {
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ new mozilla::dom::ReferrerInfo(mReferrerURI);
+ rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv =
+ httpChannel->SetRequestHeader("X-Moz"_ns, "offline-resource"_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ rv = mChannel->AsyncOpen(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mUpdate = aUpdate;
+
+ mState = LoadStatus::REQUESTED;
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheUpdateItem::Cancel() {
+ if (mChannel) {
+ mChannel->Cancel(NS_ERROR_ABORT);
+ mChannel = nullptr;
+ }
+
+ mState = LoadStatus::UNINITIALIZED;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::OnStartRequest(nsIRequest* aRequest) {
+ mState = LoadStatus::RECEIVING;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aCount) {
+ uint32_t bytesRead = 0;
+ aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &bytesRead);
+ mBytesRead += bytesRead;
+ LOG(("loaded %u bytes into offline cache [offset=%" PRIu64 "]\n", bytesRead,
+ aOffset));
+
+ mUpdate->OnByteProgress(bytesRead);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatus) {
+ if (LOG_ENABLED()) {
+ LOG(("%p: Done fetching offline item %s [status=%" PRIx32 "]\n", this,
+ mURI->GetSpecOrDefault().get(), static_cast<uint32_t>(aStatus)));
+ }
+
+ if (mBytesRead == 0 && aStatus == NS_OK) {
+ // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
+ // specified), but the object should report loadedSize as if it
+ // did.
+ mChannel->GetContentLength(&mBytesRead);
+ mUpdate->OnByteProgress(mBytesRead);
+ }
+
+ if (NS_FAILED(aStatus)) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (httpChannel) {
+ bool isNoStore;
+ if (NS_SUCCEEDED(httpChannel->IsNoStoreResponse(&isNoStore)) &&
+ isNoStore) {
+ LogToConsole(
+ "Offline cache manifest item has Cache-control: no-store header",
+ this);
+ }
+ }
+ }
+
+ // We need to notify the update that the load is complete, but we
+ // want to give the channel a chance to close the cache entries.
+ NS_DispatchToCurrentThread(this);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem::nsIRunnable
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::Run() {
+ // Set mState to LOADED here rather than in OnStopRequest to prevent
+ // race condition when checking state of all mItems in ProcessNextURI().
+ // If state would have been set in OnStopRequest we could mistakenly
+ // take this item as already finished and finish the update process too
+ // early when ProcessNextURI() would get called between OnStopRequest()
+ // and Run() of this item. Finish() would then have been called twice.
+ mState = LoadStatus::LOADED;
+
+ RefPtr<nsOfflineCacheUpdate> update;
+ update.swap(mUpdate);
+ update->LoadCompleted(this);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::GetInterface(const nsIID& aIID, void** aResult) {
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *aResult = static_cast<nsIChannelEventSink*>(this);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateItem::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* cb) {
+ if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
+ // Don't allow redirect in case of non-internal redirect and cancel
+ // the channel to clean the cache entry.
+ LogToConsole("Offline cache manifest failed because an item redirects",
+ this);
+
+ aOldChannel->Cancel(NS_ERROR_ABORT);
+ return NS_ERROR_ABORT;
+ }
+
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(aNewChannel);
+ if (appCacheChannel) {
+ rv = appCacheChannel->SetApplicationCacheForWrite(mApplicationCache);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsAutoCString oldScheme;
+ mURI->GetScheme(oldScheme);
+
+ if (!newURI->SchemeIs(oldScheme.get())) {
+ LOG(("rejected: redirected to a different scheme\n"));
+ return NS_ERROR_ABORT;
+ }
+
+ // HTTP request headers are not automatically forwarded to the new channel.
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
+ NS_ENSURE_STATE(httpChannel);
+
+ rv = httpChannel->SetRequestHeader("X-Moz"_ns, "offline-resource"_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ mChannel = aNewChannel;
+
+ cb->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheUpdateItem::GetRequestSucceeded(bool* succeeded) {
+ *succeeded = false;
+
+ if (!mChannel) return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool reqSucceeded;
+ rv = httpChannel->GetRequestSucceeded(&reqSucceeded);
+ if (NS_ERROR_NOT_AVAILABLE == rv) return NS_OK;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!reqSucceeded) {
+ LOG(("Request failed"));
+ return NS_OK;
+ }
+
+ nsresult channelStatus;
+ rv = httpChannel->GetStatus(&channelStatus);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (NS_FAILED(channelStatus)) {
+ LOG(("Channel status=0x%08" PRIx32, static_cast<uint32_t>(channelStatus)));
+ return NS_OK;
+ }
+
+ *succeeded = true;
+ return NS_OK;
+}
+
+bool nsOfflineCacheUpdateItem::IsScheduled() {
+ return mState == LoadStatus::UNINITIALIZED;
+}
+
+bool nsOfflineCacheUpdateItem::IsInProgress() {
+ return mState == LoadStatus::REQUESTED || mState == LoadStatus::RECEIVING;
+}
+
+bool nsOfflineCacheUpdateItem::IsCompleted() {
+ return mState == LoadStatus::LOADED;
+}
+
+nsresult nsOfflineCacheUpdateItem::GetStatus(uint16_t* aStatus) {
+ if (!mChannel) {
+ *aStatus = 0;
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t httpStatus;
+ rv = httpChannel->GetResponseStatus(&httpStatus);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ *aStatus = 0;
+ return NS_OK;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aStatus = uint16_t(httpStatus);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineManifestItem
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// nsOfflineManifestItem <public>
+//-----------------------------------------------------------------------------
+
+nsOfflineManifestItem::nsOfflineManifestItem(
+ nsIURI* aURI, nsIURI* aReferrerURI, nsIPrincipal* aLoadingPrincipal,
+ nsIApplicationCache* aApplicationCache,
+ nsIApplicationCache* aPreviousApplicationCache)
+ : nsOfflineCacheUpdateItem(aURI, aReferrerURI, aLoadingPrincipal,
+ aApplicationCache, aPreviousApplicationCache,
+ nsIApplicationCache::ITEM_MANIFEST, 0),
+ mParserState(PARSE_INIT),
+ mNeedsUpdate(true),
+ mStrictFileOriginPolicy(false),
+ mManifestHashInitialized(false) {
+ ReadStrictFileOriginPolicyPref();
+}
+
+nsOfflineManifestItem::~nsOfflineManifestItem() {}
+
+//-----------------------------------------------------------------------------
+// nsOfflineManifestItem <private>
+//-----------------------------------------------------------------------------
+
+/* static */
+nsresult nsOfflineManifestItem::ReadManifest(nsIInputStream* aInputStream,
+ void* aClosure,
+ const char* aFromSegment,
+ uint32_t aOffset, uint32_t aCount,
+ uint32_t* aBytesConsumed) {
+ nsOfflineManifestItem* manifest =
+ static_cast<nsOfflineManifestItem*>(aClosure);
+
+ nsresult rv;
+
+ *aBytesConsumed = aCount;
+
+ if (manifest->mParserState == PARSE_ERROR) {
+ // parse already failed, ignore this
+ return NS_OK;
+ }
+
+ if (!manifest->mManifestHashInitialized) {
+ // Avoid re-creation of crypto hash when it fails from some reason the first
+ // time
+ manifest->mManifestHashInitialized = true;
+
+ manifest->mManifestHash =
+ do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = manifest->mManifestHash->Init(nsICryptoHash::MD5);
+ if (NS_FAILED(rv)) {
+ manifest->mManifestHash = nullptr;
+ LOG(
+ ("Could not initialize manifest hash for byte-to-byte check, "
+ "rv=%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+
+ if (manifest->mManifestHash) {
+ rv = manifest->mManifestHash->Update(
+ reinterpret_cast<const uint8_t*>(aFromSegment), aCount);
+ if (NS_FAILED(rv)) {
+ manifest->mManifestHash = nullptr;
+ LOG(("Could not update manifest hash, rv=%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ manifest->mReadBuf.Append(aFromSegment, aCount);
+
+ nsCString::const_iterator begin, iter, end;
+ manifest->mReadBuf.BeginReading(begin);
+ manifest->mReadBuf.EndReading(end);
+
+ for (iter = begin; iter != end; iter++) {
+ if (*iter == '\r' || *iter == '\n') {
+ rv = manifest->HandleManifestLine(begin, iter);
+
+ if (NS_FAILED(rv)) {
+ LOG(("HandleManifestLine failed with 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ *aBytesConsumed = 0; // Avoid assertion failure in stream tee
+ return NS_ERROR_ABORT;
+ }
+
+ begin = iter;
+ begin++;
+ }
+ }
+
+ // any leftovers are saved for next time
+ manifest->mReadBuf = Substring(begin, end);
+
+ return NS_OK;
+}
+
+nsresult nsOfflineManifestItem::AddNamespace(uint32_t namespaceType,
+ const nsCString& namespaceSpec,
+ const nsCString& data)
+
+{
+ nsresult rv;
+ if (!mNamespaces) {
+ mNamespaces = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIApplicationCacheNamespace> ns = new nsApplicationCacheNamespace();
+
+ rv = ns->Init(namespaceType, namespaceSpec, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mNamespaces->AppendElement(ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+static nsresult GetURIDirectory(nsIURI* uri, nsAutoCString& directory) {
+ nsresult rv;
+
+ nsAutoCString path;
+ uri->GetFilePath(path);
+ if (path.Find("%2f") != kNotFound || path.Find("%2F") != kNotFound) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsCOMPtr<nsIURL> url(do_QueryInterface(uri, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = url->GetDirectory(directory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+static nsresult CheckFileContainedInPath(nsIURI* file,
+ nsACString const& masterDirectory) {
+ nsresult rv;
+
+ nsAutoCString directory;
+ rv = GetURIDirectory(file, directory);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool contains = StringBeginsWith(directory, masterDirectory);
+ if (!contains) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsOfflineManifestItem::HandleManifestLine(
+ const nsCString::const_iterator& aBegin,
+ const nsCString::const_iterator& aEnd) {
+ nsCString::const_iterator begin = aBegin;
+ nsCString::const_iterator end = aEnd;
+
+ // all lines ignore trailing spaces and tabs
+ nsCString::const_iterator last = end;
+ --last;
+ while (end != begin && (*last == ' ' || *last == '\t')) {
+ --end;
+ --last;
+ }
+
+ if (mParserState == PARSE_INIT) {
+ // Allow a UTF-8 BOM
+ if (begin != end && static_cast<unsigned char>(*begin) == 0xef) {
+ if (++begin == end || static_cast<unsigned char>(*begin) != 0xbb ||
+ ++begin == end || static_cast<unsigned char>(*begin) != 0xbf) {
+ mParserState = PARSE_ERROR;
+ LogToConsole("Offline cache manifest BOM error", this);
+ return NS_OK;
+ }
+ ++begin;
+ }
+
+ const nsACString& magic = Substring(begin, end);
+
+ if (!magic.EqualsLiteral("CACHE MANIFEST")) {
+ mParserState = PARSE_ERROR;
+ LogToConsole("Offline cache manifest magic incorrect", this);
+ return NS_OK;
+ }
+
+ mParserState = PARSE_CACHE_ENTRIES;
+ return NS_OK;
+ }
+
+ // lines other than the first ignore leading spaces and tabs
+ while (begin != end && (*begin == ' ' || *begin == '\t')) begin++;
+
+ // ignore blank lines and comments
+ if (begin == end || *begin == '#') return NS_OK;
+
+ const nsACString& line = Substring(begin, end);
+
+ if (line.EqualsLiteral("CACHE:")) {
+ mParserState = PARSE_CACHE_ENTRIES;
+ return NS_OK;
+ }
+
+ if (line.EqualsLiteral("FALLBACK:")) {
+ mParserState = PARSE_FALLBACK_ENTRIES;
+ return NS_OK;
+ }
+
+ if (line.EqualsLiteral("NETWORK:")) {
+ mParserState = PARSE_BYPASS_ENTRIES;
+ return NS_OK;
+ }
+
+ // Every other section type we don't know must be silently ignored.
+ nsCString::const_iterator lastChar = end;
+ if (*(--lastChar) == ':') {
+ mParserState = PARSE_UNKNOWN_SECTION;
+ return NS_OK;
+ }
+
+ nsresult rv;
+
+ switch (mParserState) {
+ case PARSE_INIT:
+ case PARSE_ERROR: {
+ // this should have been dealt with earlier
+ return NS_ERROR_FAILURE;
+ }
+
+ case PARSE_UNKNOWN_SECTION: {
+ // just jump over
+ return NS_OK;
+ }
+
+ case PARSE_CACHE_ENTRIES: {
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), line, nullptr, mURI);
+ if (NS_FAILED(rv)) break;
+ if (NS_FAILED(DropReferenceFromURL(uri))) break;
+
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+
+ // Manifest URIs must have the same scheme as the manifest.
+ if (!mURI->SchemeIs(scheme.get())) {
+ break;
+ }
+
+ mExplicitURIs.AppendObject(uri);
+
+ if (!NS_SecurityCompareURIs(mURI, uri, mStrictFileOriginPolicy)) {
+ mAnonymousURIs.AppendObject(uri);
+ }
+
+ break;
+ }
+
+ case PARSE_FALLBACK_ENTRIES: {
+ int32_t separator = line.FindChar(' ');
+ if (separator == kNotFound) {
+ separator = line.FindChar('\t');
+ if (separator == kNotFound) break;
+ }
+
+ nsCString namespaceSpec(Substring(line, 0, separator));
+ nsCString fallbackSpec(Substring(line, separator + 1));
+ namespaceSpec.CompressWhitespace();
+ fallbackSpec.CompressWhitespace();
+
+ nsCOMPtr<nsIURI> namespaceURI;
+ rv =
+ NS_NewURI(getter_AddRefs(namespaceURI), namespaceSpec, nullptr, mURI);
+ if (NS_FAILED(rv)) break;
+ if (NS_FAILED(DropReferenceFromURL(namespaceURI))) break;
+ rv = namespaceURI->GetAsciiSpec(namespaceSpec);
+ if (NS_FAILED(rv)) break;
+
+ nsCOMPtr<nsIURI> fallbackURI;
+ rv = NS_NewURI(getter_AddRefs(fallbackURI), fallbackSpec, nullptr, mURI);
+ if (NS_FAILED(rv)) break;
+ if (NS_FAILED(DropReferenceFromURL(fallbackURI))) break;
+ rv = fallbackURI->GetAsciiSpec(fallbackSpec);
+ if (NS_FAILED(rv)) break;
+
+ // The following set of checks is preventing a website under
+ // a subdirectory to add fallback pages for the whole origin
+ // (or a parent directory) to prevent fallback attacks.
+ nsAutoCString manifestDirectory;
+ rv = GetURIDirectory(mURI, manifestDirectory);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ rv = CheckFileContainedInPath(namespaceURI, manifestDirectory);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ rv = CheckFileContainedInPath(fallbackURI, manifestDirectory);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ // Manifest and namespace must be same origin
+ if (!NS_SecurityCompareURIs(mURI, namespaceURI, mStrictFileOriginPolicy))
+ break;
+
+ // Fallback and namespace must be same origin
+ if (!NS_SecurityCompareURIs(namespaceURI, fallbackURI,
+ mStrictFileOriginPolicy))
+ break;
+
+ mFallbackURIs.AppendObject(fallbackURI);
+
+ AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_FALLBACK,
+ namespaceSpec, fallbackSpec);
+ break;
+ }
+
+ case PARSE_BYPASS_ENTRIES: {
+ if (line[0] == '*' &&
+ (line.Length() == 1 || line[1] == ' ' || line[1] == '\t')) {
+ // '*' indicates to make the online whitelist wildcard flag open,
+ // i.e. do allow load of resources not present in the offline cache
+ // or not conforming any namespace.
+ // We achive that simply by adding an 'empty' - i.e. universal
+ // namespace of BYPASS type into the cache.
+ AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_BYPASS, ""_ns,
+ ""_ns);
+ break;
+ }
+
+ nsCOMPtr<nsIURI> bypassURI;
+ rv = NS_NewURI(getter_AddRefs(bypassURI), line, nullptr, mURI);
+ if (NS_FAILED(rv)) break;
+
+ nsAutoCString scheme;
+ bypassURI->GetScheme(scheme);
+ if (!mURI->SchemeIs(scheme.get())) {
+ break;
+ }
+ if (NS_FAILED(DropReferenceFromURL(bypassURI))) break;
+ nsCString spec;
+ if (NS_FAILED(bypassURI->GetAsciiSpec(spec))) break;
+
+ AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_BYPASS, spec, ""_ns);
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsOfflineManifestItem::GetOldManifestContentHash(
+ nsIRequest* aRequest) {
+ nsresult rv;
+
+ nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aRequest, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // load the main cache token that is actually the old offline cache token and
+ // read previous manifest content hash value
+ nsCOMPtr<nsISupports> cacheToken;
+ cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
+ if (cacheToken) {
+ nsCOMPtr<nsICacheEntry> cacheDescriptor(do_QueryInterface(cacheToken, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheDescriptor->GetMetaDataElement(
+ "offline-manifest-hash", getter_Copies(mOldManifestHashValue));
+ if (NS_FAILED(rv)) mOldManifestHashValue.Truncate();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsOfflineManifestItem::CheckNewManifestContentHash(
+ nsIRequest* aRequest) {
+ nsresult rv;
+
+ if (!mManifestHash) {
+ // Nothing to compare against...
+ return NS_OK;
+ }
+
+ nsCString newManifestHashValue;
+ rv = mManifestHash->Finish(true, mManifestHashValue);
+ mManifestHash = nullptr;
+
+ if (NS_FAILED(rv)) {
+ LOG(("Could not finish manifest hash, rv=%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ // This is not critical error
+ return NS_OK;
+ }
+
+ if (!ParseSucceeded()) {
+ // Parsing failed, the hash is not valid
+ return NS_OK;
+ }
+
+ if (mOldManifestHashValue == mManifestHashValue) {
+ LOG(
+ ("Update not needed, downloaded manifest content is byte-for-byte "
+ "identical"));
+ mNeedsUpdate = false;
+ }
+
+ // Store the manifest content hash value to the new
+ // offline cache token
+ nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aRequest, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> cacheToken;
+ cachingChannel->GetOfflineCacheToken(getter_AddRefs(cacheToken));
+ if (cacheToken) {
+ nsCOMPtr<nsICacheEntry> cacheDescriptor(do_QueryInterface(cacheToken, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheDescriptor->SetMetaDataElement("offline-manifest-hash",
+ mManifestHashValue.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+void nsOfflineManifestItem::ReadStrictFileOriginPolicyPref() {
+ mStrictFileOriginPolicy =
+ Preferences::GetBool("security.fileuri.strict_origin_policy", true);
+}
+
+NS_IMETHODIMP
+nsOfflineManifestItem::OnStartRequest(nsIRequest* aRequest) {
+ nsresult rv;
+
+ nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool succeeded;
+ rv = channel->GetRequestSucceeded(&succeeded);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!succeeded) {
+ LOG(("HTTP request failed"));
+ LogToConsole("Offline cache manifest HTTP request failed", this);
+ mParserState = PARSE_ERROR;
+ return NS_ERROR_ABORT;
+ }
+
+ rv = GetOldManifestContentHash(aRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nsOfflineCacheUpdateItem::OnStartRequest(aRequest);
+}
+
+NS_IMETHODIMP
+nsOfflineManifestItem::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aCount) {
+ uint32_t bytesRead = 0;
+ aStream->ReadSegments(ReadManifest, this, aCount, &bytesRead);
+ mBytesRead += bytesRead;
+
+ if (mParserState == PARSE_ERROR) {
+ LOG(("OnDataAvailable is canceling the request due a parse error\n"));
+ return NS_ERROR_ABORT;
+ }
+
+ LOG(("loaded %u bytes into offline cache [offset=%" PRIu64 "]\n", bytesRead,
+ aOffset));
+
+ // All the parent method does is read and discard, don't bother
+ // chaining up.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineManifestItem::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ if (mBytesRead == 0) {
+ // We didn't need to read (because LOAD_ONLY_IF_MODIFIED was
+ // specified).
+ mNeedsUpdate = false;
+ } else {
+ // Handle any leftover manifest data.
+ nsCString::const_iterator begin, end;
+ mReadBuf.BeginReading(begin);
+ mReadBuf.EndReading(end);
+ nsresult rv = HandleManifestLine(begin, end);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CheckNewManifestContentHash(aRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return nsOfflineCacheUpdateItem::OnStopRequest(aRequest, aStatus);
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdate::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheUpdate, nsIOfflineCacheUpdateObserver,
+ nsIOfflineCacheUpdate, nsIRunnable)
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdate <public>
+//-----------------------------------------------------------------------------
+
+nsOfflineCacheUpdate::nsOfflineCacheUpdate()
+ : mState(STATE_UNINITIALIZED),
+ mAddedItems(false),
+ mPartialUpdate(false),
+ mOnlyCheckUpdate(false),
+ mSucceeded(true),
+ mObsolete(false),
+ mItemsInProgress(0),
+ mRescheduleCount(0),
+ mPinnedEntryRetriesCount(0),
+ mPinned(false),
+ mByteProgress(0) {}
+
+nsOfflineCacheUpdate::~nsOfflineCacheUpdate() {
+ LOG(("nsOfflineCacheUpdate::~nsOfflineCacheUpdate [%p]", this));
+}
+
+/* static */
+nsresult nsOfflineCacheUpdate::GetCacheKey(nsIURI* aURI, nsACString& aKey) {
+ aKey.Truncate();
+
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(newURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = newURI->GetAsciiSpec(aKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheUpdate::InitInternal(nsIURI* aManifestURI,
+ nsIPrincipal* aLoadingPrincipal) {
+ nsresult rv;
+
+ // Only http and https applications are supported.
+ if (!aManifestURI->SchemeIs("http") && !aManifestURI->SchemeIs("https")) {
+ return NS_ERROR_ABORT;
+ }
+
+ mManifestURI = aManifestURI;
+ mLoadingPrincipal = aLoadingPrincipal;
+
+ rv = mManifestURI->GetAsciiHost(mUpdateDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mPartialUpdate = false;
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheUpdate::Init(nsIURI* aManifestURI, nsIURI* aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ dom::Document* aDocument,
+ nsIFile* aCustomProfileDir) {
+ nsresult rv;
+
+ // Make sure the service has been initialized
+ nsOfflineCacheUpdateService* service =
+ nsOfflineCacheUpdateService::EnsureService();
+ if (!service) return NS_ERROR_FAILURE;
+
+ LOG(("nsOfflineCacheUpdate::Init [%p]", this));
+
+ rv = InitInternal(aManifestURI, aLoadingPrincipal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIApplicationCacheService> cacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString originSuffix;
+ rv = aLoadingPrincipal->GetOriginSuffix(originSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDocumentURI = aDocumentURI;
+
+ if (aDocument) {
+ mCookieJarSettings = aDocument->CookieJarSettings();
+ }
+
+ if (aCustomProfileDir) {
+ rv = cacheService->BuildGroupIDForSuffix(aManifestURI, originSuffix,
+ mGroupID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create only a new offline application cache in the custom profile
+ // This is a preload of a new cache.
+
+ // XXX Custom updates don't support "updating" of an existing cache
+ // in the custom profile at the moment. This support can be, though,
+ // simply added as well when needed.
+ mPreviousApplicationCache = nullptr;
+
+ rv = cacheService->CreateCustomApplicationCache(
+ mGroupID, aCustomProfileDir, kCustomProfileQuota,
+ getter_AddRefs(mApplicationCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCustomProfileDir = aCustomProfileDir;
+ } else {
+ rv = cacheService->BuildGroupIDForSuffix(aManifestURI, originSuffix,
+ mGroupID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheService->GetActiveCache(
+ mGroupID, getter_AddRefs(mPreviousApplicationCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheService->CreateApplicationCache(
+ mGroupID, getter_AddRefs(mApplicationCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aDocumentURI,
+ &mPinned);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mState = STATE_INITIALIZED;
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheUpdate::InitForUpdateCheck(
+ nsIURI* aManifestURI, nsIPrincipal* aLoadingPrincipal,
+ nsIObserver* aObserver) {
+ nsresult rv;
+
+ // Make sure the service has been initialized
+ nsOfflineCacheUpdateService* service =
+ nsOfflineCacheUpdateService::EnsureService();
+ if (!service) return NS_ERROR_FAILURE;
+
+ LOG(("nsOfflineCacheUpdate::InitForUpdateCheck [%p]", this));
+
+ rv = InitInternal(aManifestURI, aLoadingPrincipal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIApplicationCacheService> cacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString originSuffix;
+ rv = aLoadingPrincipal->GetOriginSuffix(originSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv =
+ cacheService->BuildGroupIDForSuffix(aManifestURI, originSuffix, mGroupID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheService->GetActiveCache(mGroupID,
+ getter_AddRefs(mPreviousApplicationCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // To load the manifest properly using current app cache to satisfy and
+ // also to compare the cached content hash value we have to set 'some'
+ // app cache to write to on the channel. Otherwise the cached version will
+ // be used and no actual network request will be made. We use the same
+ // app cache here. OpenChannel prevents caching in this case using
+ // INHIBIT_CACHING load flag.
+ mApplicationCache = mPreviousApplicationCache;
+
+ rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aManifestURI,
+ &mPinned);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mUpdateAvailableObserver = aObserver;
+ mOnlyCheckUpdate = true;
+
+ mState = STATE_INITIALIZED;
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheUpdate::InitPartial(
+ nsIURI* aManifestURI, const nsACString& clientID, nsIURI* aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal, nsICookieJarSettings* aCookieJarSettings) {
+ nsresult rv;
+
+ // Make sure the service has been initialized
+ nsOfflineCacheUpdateService* service =
+ nsOfflineCacheUpdateService::EnsureService();
+ if (!service) return NS_ERROR_FAILURE;
+
+ LOG(("nsOfflineCacheUpdate::InitPartial [%p]", this));
+
+ mPartialUpdate = true;
+ mDocumentURI = aDocumentURI;
+ mLoadingPrincipal = aLoadingPrincipal;
+
+ mManifestURI = aManifestURI;
+ rv = mManifestURI->GetAsciiHost(mUpdateDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIApplicationCacheService> cacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheService->GetApplicationCache(clientID,
+ getter_AddRefs(mApplicationCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mApplicationCache) {
+ nsAutoCString manifestSpec;
+ rv = GetCacheKey(mManifestURI, manifestSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheService->CreateApplicationCache(
+ manifestSpec, getter_AddRefs(mApplicationCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mApplicationCache->GetManifestURI(getter_AddRefs(mManifestURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString groupID;
+ rv = mApplicationCache->GetGroupID(groupID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aDocumentURI,
+ &mPinned);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCookieJarSettings = aCookieJarSettings;
+
+ mState = STATE_INITIALIZED;
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheUpdate::HandleManifest(bool* aDoUpdate) {
+ // Be pessimistic
+ *aDoUpdate = false;
+
+ bool succeeded;
+ nsresult rv = mManifestItem->GetRequestSucceeded(&succeeded);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!succeeded || !mManifestItem->ParseSucceeded()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mManifestItem->NeedsUpdate()) {
+ return NS_OK;
+ }
+
+ // Add items requested by the manifest.
+ const nsCOMArray<nsIURI>& manifestURIs = mManifestItem->GetExplicitURIs();
+ for (int32_t i = 0; i < manifestURIs.Count(); i++) {
+ rv = AddURI(manifestURIs[i], nsIApplicationCache::ITEM_EXPLICIT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ const nsCOMArray<nsIURI>& anonURIs = mManifestItem->GetAnonymousURIs();
+ for (int32_t i = 0; i < anonURIs.Count(); i++) {
+ rv = AddURI(anonURIs[i], nsIApplicationCache::ITEM_EXPLICIT,
+ nsIRequest::LOAD_ANONYMOUS);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ const nsCOMArray<nsIURI>& fallbackURIs = mManifestItem->GetFallbackURIs();
+ for (int32_t i = 0; i < fallbackURIs.Count(); i++) {
+ rv = AddURI(fallbackURIs[i], nsIApplicationCache::ITEM_FALLBACK);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // The document that requested the manifest is implicitly included
+ // as part of that manifest update.
+ rv = AddURI(mDocumentURI, nsIApplicationCache::ITEM_IMPLICIT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add items previously cached implicitly
+ rv = AddExistingItems(nsIApplicationCache::ITEM_IMPLICIT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add items requested by the script API
+ rv = AddExistingItems(nsIApplicationCache::ITEM_DYNAMIC);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add opportunistically cached items conforming current opportunistic
+ // namespace list
+ rv = AddExistingItems(nsIApplicationCache::ITEM_OPPORTUNISTIC,
+ &mManifestItem->GetOpportunisticNamespaces());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aDoUpdate = true;
+
+ return NS_OK;
+}
+
+bool nsOfflineCacheUpdate::CheckUpdateAvailability() {
+ nsresult rv;
+
+ bool succeeded;
+ rv = mManifestItem->GetRequestSucceeded(&succeeded);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (!succeeded || !mManifestItem->ParseSucceeded()) {
+ return false;
+ }
+
+ if (!mPinned) {
+ uint16_t status;
+ rv = mManifestItem->GetStatus(&status);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Treat these as there would be an update available,
+ // since this is indication of demand to remove this
+ // offline cache.
+ if (status == 404 || status == 410) {
+ return true;
+ }
+ }
+
+ return mManifestItem->NeedsUpdate();
+}
+
+void nsOfflineCacheUpdate::LoadCompleted(nsOfflineCacheUpdateItem* aItem) {
+ nsresult rv;
+
+ LOG(("nsOfflineCacheUpdate::LoadCompleted [%p]", this));
+
+ if (mState == STATE_FINISHED) {
+ LOG((" after completion, ignoring"));
+ return;
+ }
+
+ // Keep the object alive through a Finish() call.
+ nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
+
+ if (mState == STATE_CANCELLED) {
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ Finish();
+ return;
+ }
+
+ if (mState == STATE_CHECKING) {
+ // Manifest load finished.
+
+ if (mOnlyCheckUpdate) {
+ Finish();
+ NotifyUpdateAvailability(CheckUpdateAvailability());
+ return;
+ }
+
+ NS_ASSERTION(mManifestItem, "Must have a manifest item in STATE_CHECKING.");
+ NS_ASSERTION(mManifestItem == aItem,
+ "Unexpected aItem in nsOfflineCacheUpdate::LoadCompleted");
+
+ // A 404 or 410 is interpreted as an intentional removal of
+ // the manifest file, rather than a transient server error.
+ // Obsolete this cache group if one of these is returned.
+ uint16_t status;
+ rv = mManifestItem->GetStatus(&status);
+ if (NS_FAILED(rv)) {
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ Finish();
+ return;
+ }
+ if (status == 404 || status == 410) {
+ LogToConsole("Offline cache manifest removed, cache cleared",
+ mManifestItem);
+ mSucceeded = false;
+ if (mPreviousApplicationCache) {
+ if (mPinned) {
+ // Do not obsolete a pinned application.
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_NOUPDATE);
+ } else {
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_OBSOLETE);
+ mObsolete = true;
+ }
+ } else {
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ mObsolete = true;
+ }
+ Finish();
+ return;
+ }
+
+ bool doUpdate;
+ if (NS_FAILED(HandleManifest(&doUpdate))) {
+ mSucceeded = false;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ Finish();
+ return;
+ }
+
+ if (!doUpdate) {
+ LogToConsole("Offline cache doesn't need to update", mManifestItem);
+
+ mSucceeded = false;
+
+ AssociateDocuments(mPreviousApplicationCache);
+
+ ScheduleImplicit();
+
+ // If we didn't need an implicit update, we can
+ // send noupdate and end the update now.
+ if (!mImplicitUpdate) {
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_NOUPDATE);
+ Finish();
+ }
+ return;
+ }
+
+ rv = mApplicationCache->MarkEntry(mManifestItem->mCacheKey,
+ mManifestItem->mItemType);
+ if (NS_FAILED(rv)) {
+ mSucceeded = false;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ Finish();
+ return;
+ }
+
+ mState = STATE_DOWNLOADING;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_DOWNLOADING);
+
+ // Start fetching resources.
+ ProcessNextURI();
+
+ return;
+ }
+
+ // Normal load finished.
+ if (mItemsInProgress) // Just to be safe here!
+ --mItemsInProgress;
+
+ bool succeeded;
+ rv = aItem->GetRequestSucceeded(&succeeded);
+
+ if (mPinned && NS_SUCCEEDED(rv) && succeeded) {
+ uint32_t dummy_cache_type;
+ rv = mApplicationCache->GetTypes(aItem->mCacheKey, &dummy_cache_type);
+ bool item_doomed = NS_FAILED(rv); // can not find it? -> doomed
+
+ if (item_doomed && mPinnedEntryRetriesCount < kPinnedEntryRetriesLimit &&
+ (aItem->mItemType & (nsIApplicationCache::ITEM_EXPLICIT |
+ nsIApplicationCache::ITEM_FALLBACK))) {
+ rv = EvictOneNonPinned();
+ if (NS_FAILED(rv)) {
+ mSucceeded = false;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ Finish();
+ return;
+ }
+
+ // This reverts the item state to UNINITIALIZED that makes it to
+ // be scheduled for download again.
+ rv = aItem->Cancel();
+ if (NS_FAILED(rv)) {
+ mSucceeded = false;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ Finish();
+ return;
+ }
+
+ mPinnedEntryRetriesCount++;
+
+ LogToConsole("An unpinned offline cache deleted");
+
+ // Retry this item.
+ ProcessNextURI();
+ return;
+ }
+ }
+
+ // According to parallelism this may imply more pinned retries count,
+ // but that is not critical, since at one moment the algorithm will
+ // stop anyway. Also, this code may soon be completely removed
+ // after we have a separate storage for pinned apps.
+ mPinnedEntryRetriesCount = 0;
+
+ // Check for failures. 3XX, 4XX and 5XX errors on items explicitly
+ // listed in the manifest will cause the update to fail.
+ if (NS_FAILED(rv) || !succeeded) {
+ if (aItem->mItemType & (nsIApplicationCache::ITEM_EXPLICIT |
+ nsIApplicationCache::ITEM_FALLBACK)) {
+ LogToConsole("Offline cache manifest item failed to load", aItem);
+ mSucceeded = false;
+ }
+ } else {
+ rv = mApplicationCache->MarkEntry(aItem->mCacheKey, aItem->mItemType);
+ if (NS_FAILED(rv)) {
+ mSucceeded = false;
+ }
+ }
+
+ if (!mSucceeded) {
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ Finish();
+ return;
+ }
+
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ITEMCOMPLETED);
+
+ ProcessNextURI();
+}
+
+void nsOfflineCacheUpdate::ManifestCheckCompleted(
+ nsresult aStatus, const nsCString& aManifestHash) {
+ // Keep the object alive through a Finish() call.
+ nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
+
+ if (NS_SUCCEEDED(aStatus)) {
+ nsAutoCString firstManifestHash;
+ mManifestItem->GetManifestHash(firstManifestHash);
+ if (aManifestHash != firstManifestHash) {
+ LOG(("Manifest has changed during cache items download [%p]", this));
+ LogToConsole("Offline cache manifest changed during update",
+ mManifestItem);
+ aStatus = NS_ERROR_FAILURE;
+ }
+ }
+
+ if (NS_FAILED(aStatus)) {
+ mSucceeded = false;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ }
+
+ if (NS_FAILED(aStatus) && mRescheduleCount < kRescheduleLimit) {
+ // Do the final stuff but prevent notification of STATE_FINISHED.
+ // That would disconnect listeners that are responsible for document
+ // association after a successful update. Forwarding notifications
+ // from a new update through this dead update to them is absolutely
+ // correct.
+ FinishNoNotify();
+
+ RefPtr<nsOfflineCacheUpdate> newUpdate = new nsOfflineCacheUpdate();
+ // Leave aDocument argument null. Only glues and children keep
+ // document instances.
+ newUpdate->Init(mManifestURI, mDocumentURI, mLoadingPrincipal, nullptr,
+ mCustomProfileDir);
+
+ newUpdate->SetCookieJarSettings(mCookieJarSettings);
+
+ // In a rare case the manifest will not be modified on the next refetch
+ // transfer all master document URIs to the new update to ensure that
+ // all documents refering it will be properly cached.
+ for (int32_t i = 0; i < mDocumentURIs.Count(); i++) {
+ newUpdate->StickDocument(mDocumentURIs[i]);
+ }
+
+ newUpdate->mRescheduleCount = mRescheduleCount + 1;
+ newUpdate->AddObserver(this, false);
+ newUpdate->Schedule();
+ } else {
+ LogToConsole("Offline cache update done", mManifestItem);
+ Finish();
+ }
+}
+
+nsresult nsOfflineCacheUpdate::Begin() {
+ LOG(("nsOfflineCacheUpdate::Begin [%p]", this));
+
+ // Keep the object alive through a ProcessNextURI()/Finish() call.
+ nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
+
+ mItemsInProgress = 0;
+
+ if (mState == STATE_CANCELLED) {
+ nsresult rv = NS_DispatchToMainThread(
+ NewRunnableMethod("nsOfflineCacheUpdate::AsyncFinishWithError", this,
+ &nsOfflineCacheUpdate::AsyncFinishWithError));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ if (mPartialUpdate) {
+ mState = STATE_DOWNLOADING;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_DOWNLOADING);
+ ProcessNextURI();
+ return NS_OK;
+ }
+
+ // Start checking the manifest.
+ mManifestItem =
+ new nsOfflineManifestItem(mManifestURI, mDocumentURI, mLoadingPrincipal,
+ mApplicationCache, mPreviousApplicationCache);
+ if (!mManifestItem) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mState = STATE_CHECKING;
+ mByteProgress = 0;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_CHECKING);
+
+ nsresult rv = mManifestItem->OpenChannel(this);
+ if (NS_FAILED(rv)) {
+ LoadCompleted(mManifestItem);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdate <private>
+//-----------------------------------------------------------------------------
+
+nsresult nsOfflineCacheUpdate::AddExistingItems(
+ uint32_t aType, nsTArray<nsCString>* namespaceFilter) {
+ if (!mPreviousApplicationCache) {
+ return NS_OK;
+ }
+
+ if (namespaceFilter && namespaceFilter->Length() == 0) {
+ // Don't bother to walk entries when there are no namespaces
+ // defined.
+ return NS_OK;
+ }
+
+ nsTArray<nsCString> keys;
+ nsresult rv = mPreviousApplicationCache->GatherEntries(aType, keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto& key : keys) {
+ if (namespaceFilter) {
+ bool found = false;
+ for (uint32_t j = 0; j < namespaceFilter->Length() && !found; j++) {
+ found = StringBeginsWith(key, namespaceFilter->ElementAt(j));
+ }
+
+ if (!found) continue;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), key))) {
+ rv = AddURI(uri, aType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheUpdate::ProcessNextURI() {
+ // Keep the object alive through a Finish() call.
+ nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
+
+ LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p, inprogress=%d, numItems=%zu]",
+ this, mItemsInProgress, mItems.Length()));
+
+ if (mState != STATE_DOWNLOADING) {
+ LOG((" should only be called from the DOWNLOADING state, ignoring"));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsOfflineCacheUpdateItem* runItem = nullptr;
+ uint32_t completedItems = 0;
+ for (uint32_t i = 0; i < mItems.Length(); ++i) {
+ nsOfflineCacheUpdateItem* item = mItems[i];
+
+ if (item->IsScheduled()) {
+ runItem = item;
+ break;
+ }
+
+ if (item->IsCompleted()) ++completedItems;
+ }
+
+ if (completedItems == mItems.Length()) {
+ LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p]: all items loaded", this));
+
+ if (mPartialUpdate) {
+ return Finish();
+ } else {
+ // Verify that the manifest wasn't changed during the
+ // update, to prevent capturing a cache while the server
+ // is being updated. The check will call
+ // ManifestCheckCompleted() when it's done.
+ RefPtr<nsManifestCheck> manifestCheck = new nsManifestCheck(
+ this, mManifestURI, mDocumentURI, mLoadingPrincipal);
+ if (NS_FAILED(manifestCheck->Begin())) {
+ mSucceeded = false;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ return Finish();
+ }
+
+ return NS_OK;
+ }
+ }
+
+ if (!runItem) {
+ LOG(
+ ("nsOfflineCacheUpdate::ProcessNextURI [%p]:"
+ " No more items to include in parallel load",
+ this));
+ return NS_OK;
+ }
+
+ if (LOG_ENABLED()) {
+ LOG(("%p: Opening channel for %s", this,
+ runItem->mURI->GetSpecOrDefault().get()));
+ }
+
+ ++mItemsInProgress;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ITEMSTARTED);
+
+ nsresult rv = runItem->OpenChannel(this);
+ if (NS_FAILED(rv)) {
+ LoadCompleted(runItem);
+ return rv;
+ }
+
+ if (mItemsInProgress >= kParallelLoadLimit) {
+ LOG(
+ ("nsOfflineCacheUpdate::ProcessNextURI [%p]:"
+ " At parallel load limit",
+ this));
+ return NS_OK;
+ }
+
+ // This calls this method again via a post triggering
+ // a parallel item load
+ return NS_DispatchToCurrentThread(this);
+}
+
+void nsOfflineCacheUpdate::GatherObservers(
+ nsCOMArray<nsIOfflineCacheUpdateObserver>& aObservers) {
+ for (int32_t i = 0; i < mWeakObservers.Count(); i++) {
+ nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
+ do_QueryReferent(mWeakObservers[i]);
+ if (observer)
+ aObservers.AppendObject(observer);
+ else
+ mWeakObservers.RemoveObjectAt(i--);
+ }
+
+ for (int32_t i = 0; i < mObservers.Count(); i++) {
+ aObservers.AppendObject(mObservers[i]);
+ }
+}
+
+void nsOfflineCacheUpdate::NotifyState(uint32_t state) {
+ LOG(("nsOfflineCacheUpdate::NotifyState [%p, %d]", this, state));
+
+ if (state == STATE_ERROR) {
+ LogToConsole("Offline cache update error", mManifestItem);
+ }
+
+ nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
+ GatherObservers(observers);
+
+ for (int32_t i = 0; i < observers.Count(); i++) {
+ observers[i]->UpdateStateChanged(this, state);
+ }
+}
+
+void nsOfflineCacheUpdate::NotifyUpdateAvailability(bool updateAvailable) {
+ if (!mUpdateAvailableObserver) return;
+
+ LOG(("nsOfflineCacheUpdate::NotifyUpdateAvailability [this=%p, avail=%d]",
+ this, updateAvailable));
+
+ const char* topic = updateAvailable ? "offline-cache-update-available"
+ : "offline-cache-update-unavailable";
+
+ nsCOMPtr<nsIObserver> observer;
+ observer.swap(mUpdateAvailableObserver);
+ observer->Observe(mManifestURI, topic, nullptr);
+}
+
+void nsOfflineCacheUpdate::AssociateDocuments(nsIApplicationCache* cache) {
+ if (!cache) {
+ LOG(
+ ("nsOfflineCacheUpdate::AssociateDocuments bypassed"
+ ", no cache provided [this=%p]",
+ this));
+ return;
+ }
+
+ nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
+ GatherObservers(observers);
+
+ for (int32_t i = 0; i < observers.Count(); i++) {
+ observers[i]->ApplicationCacheAvailable(cache);
+ }
+}
+
+void nsOfflineCacheUpdate::StickDocument(nsIURI* aDocumentURI) {
+ if (!aDocumentURI) return;
+
+ mDocumentURIs.AppendObject(aDocumentURI);
+}
+
+void nsOfflineCacheUpdate::SetOwner(nsOfflineCacheUpdateOwner* aOwner) {
+ NS_ASSERTION(!mOwner, "Tried to set cache update owner twice.");
+ mOwner = aOwner;
+}
+
+bool nsOfflineCacheUpdate::IsForGroupID(const nsACString& groupID) {
+ return mGroupID == groupID;
+}
+
+bool nsOfflineCacheUpdate::IsForProfile(nsIFile* aCustomProfileDir) {
+ if (!mCustomProfileDir && !aCustomProfileDir) return true;
+ if (!mCustomProfileDir || !aCustomProfileDir) return false;
+
+ bool equals;
+ nsresult rv = mCustomProfileDir->Equals(aCustomProfileDir, &equals);
+
+ return NS_SUCCEEDED(rv) && equals;
+}
+
+nsresult nsOfflineCacheUpdate::UpdateFinished(nsOfflineCacheUpdate* aUpdate) {
+ // Keep the object alive through a Finish() call.
+ nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
+
+ mImplicitUpdate = nullptr;
+
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_NOUPDATE);
+ Finish();
+
+ return NS_OK;
+}
+
+void nsOfflineCacheUpdate::OnByteProgress(uint64_t byteIncrement) {
+ mByteProgress += byteIncrement;
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ITEMPROGRESS);
+}
+
+nsresult nsOfflineCacheUpdate::ScheduleImplicit() {
+ if (mDocumentURIs.Count() == 0) return NS_OK;
+
+ nsresult rv;
+
+ RefPtr<nsOfflineCacheUpdate> update = new nsOfflineCacheUpdate();
+ NS_ENSURE_TRUE(update, NS_ERROR_OUT_OF_MEMORY);
+
+ nsAutoCString clientID;
+ if (mPreviousApplicationCache) {
+ rv = mPreviousApplicationCache->GetClientID(clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (mApplicationCache) {
+ rv = mApplicationCache->GetClientID(clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NS_ERROR("Offline cache update not having set mApplicationCache?");
+ }
+
+ rv = update->InitPartial(mManifestURI, clientID, mDocumentURI,
+ mLoadingPrincipal, mCookieJarSettings);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (int32_t i = 0; i < mDocumentURIs.Count(); i++) {
+ rv = update->AddURI(mDocumentURIs[i], nsIApplicationCache::ITEM_IMPLICIT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ update->SetOwner(this);
+ rv = update->Begin();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mImplicitUpdate = update;
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheUpdate::FinishNoNotify() {
+ LOG(("nsOfflineCacheUpdate::Finish [%p]", this));
+
+ mState = STATE_FINISHED;
+
+ if (!mPartialUpdate && !mOnlyCheckUpdate) {
+ if (mSucceeded) {
+ nsIArray* namespaces = mManifestItem->GetNamespaces();
+ nsresult rv = mApplicationCache->AddNamespaces(namespaces);
+ if (NS_FAILED(rv)) {
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ mSucceeded = false;
+ }
+
+ rv = mApplicationCache->Activate();
+ if (NS_FAILED(rv)) {
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
+ mSucceeded = false;
+ }
+
+ AssociateDocuments(mApplicationCache);
+ }
+
+ if (mObsolete) {
+ nsCOMPtr<nsIApplicationCacheService> appCacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
+ if (appCacheService) {
+ nsAutoCString groupID;
+ mApplicationCache->GetGroupID(groupID);
+ appCacheService->DeactivateGroup(groupID);
+ }
+ }
+
+ if (!mSucceeded) {
+ // Update was not merged, mark all the loads as failures
+ for (uint32_t i = 0; i < mItems.Length(); i++) {
+ mItems[i]->Cancel();
+ }
+
+ mApplicationCache->Discard();
+ }
+ }
+
+ nsresult rv = NS_OK;
+
+ if (mOwner) {
+ rv = mOwner->UpdateFinished(this);
+ // mozilla::WeakPtr is missing some key features, like setting it to
+ // null explicitly.
+ mOwner = mozilla::WeakPtr<nsOfflineCacheUpdateOwner>();
+ }
+
+ return rv;
+}
+
+nsresult nsOfflineCacheUpdate::Finish() {
+ nsresult rv = FinishNoNotify();
+
+ NotifyState(nsIOfflineCacheUpdateObserver::STATE_FINISHED);
+
+ return rv;
+}
+
+void nsOfflineCacheUpdate::AsyncFinishWithError() {
+ NotifyState(nsOfflineCacheUpdate::STATE_ERROR);
+ Finish();
+}
+
+static nsresult EvictOneOfCacheGroups(nsIApplicationCacheService* cacheService,
+ const nsTArray<nsCString>& groups) {
+ nsresult rv;
+
+ for (auto& group : groups) {
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), group);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIApplicationCache> cache;
+ rv = cacheService->GetActiveCache(group, getter_AddRefs(cache));
+ // Maybe someone in another thread or process have deleted it.
+ if (NS_FAILED(rv) || !cache) continue;
+
+ bool pinned;
+ rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(uri, &pinned);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!pinned) {
+ rv = cache->Discard();
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FILE_NOT_FOUND;
+}
+
+nsresult nsOfflineCacheUpdate::EvictOneNonPinned() {
+ nsresult rv;
+
+ nsCOMPtr<nsIApplicationCacheService> cacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<nsCString> groups;
+ rv = cacheService->GetGroupsTimeOrdered(groups);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return EvictOneOfCacheGroups(cacheService, groups);
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdate::nsIOfflineCacheUpdate
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetUpdateDomain(nsACString& aUpdateDomain) {
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ aUpdateDomain = mUpdateDomain;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetStatus(uint16_t* aStatus) {
+ switch (mState) {
+ case STATE_CHECKING:
+ *aStatus = dom::OfflineResourceList_Binding::CHECKING;
+ return NS_OK;
+ case STATE_DOWNLOADING:
+ *aStatus = dom::OfflineResourceList_Binding::DOWNLOADING;
+ return NS_OK;
+ default:
+ *aStatus = dom::OfflineResourceList_Binding::IDLE;
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetPartial(bool* aPartial) {
+ *aPartial = mPartialUpdate || mOnlyCheckUpdate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetManifestURI(nsIURI** aManifestURI) {
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ NS_IF_ADDREF(*aManifestURI = mManifestURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetLoadingPrincipal(nsIPrincipal** aLoadingPrincipal) {
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ NS_IF_ADDREF(*aLoadingPrincipal = mLoadingPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetSucceeded(bool* aSucceeded) {
+ NS_ENSURE_TRUE(mState == STATE_FINISHED, NS_ERROR_NOT_AVAILABLE);
+
+ *aSucceeded = mSucceeded;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetIsUpgrade(bool* aIsUpgrade) {
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ *aIsUpgrade = (mPreviousApplicationCache != nullptr);
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheUpdate::AddURI(nsIURI* aURI, uint32_t aType,
+ uint32_t aLoadFlags) {
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ if (mState >= STATE_DOWNLOADING) return NS_ERROR_NOT_AVAILABLE;
+
+ // Resource URIs must have the same scheme as the manifest.
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+
+ if (!mManifestURI->SchemeIs(scheme.get())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Don't fetch the same URI twice.
+ for (uint32_t i = 0; i < mItems.Length(); i++) {
+ bool equals;
+ if (NS_SUCCEEDED(mItems[i]->mURI->Equals(aURI, &equals)) && equals &&
+ mItems[i]->mLoadFlags == aLoadFlags) {
+ // retain both types.
+ mItems[i]->mItemType |= aType;
+ return NS_OK;
+ }
+ }
+
+ RefPtr<nsOfflineCacheUpdateItem> item = new nsOfflineCacheUpdateItem(
+ aURI, mDocumentURI, mLoadingPrincipal, mApplicationCache,
+ mPreviousApplicationCache, aType, aLoadFlags);
+ if (!item) return NS_ERROR_OUT_OF_MEMORY;
+
+ mItems.AppendElement(item);
+ mAddedItems = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::AddDynamicURI(nsIURI* aURI) {
+ if (GeckoProcessType_Default != XRE_GetProcessType())
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ // If this is a partial update and the resource is already in the
+ // cache, we should only mark the entry, not fetch it again.
+ if (mPartialUpdate) {
+ nsAutoCString key;
+ GetCacheKey(aURI, key);
+
+ uint32_t types;
+ nsresult rv = mApplicationCache->GetTypes(key, &types);
+ if (NS_SUCCEEDED(rv)) {
+ if (!(types & nsIApplicationCache::ITEM_DYNAMIC)) {
+ mApplicationCache->MarkEntry(key, nsIApplicationCache::ITEM_DYNAMIC);
+ }
+ return NS_OK;
+ }
+ }
+
+ return AddURI(aURI, nsIApplicationCache::ITEM_DYNAMIC);
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::Cancel() {
+ LOG(("nsOfflineCacheUpdate::Cancel [%p]", this));
+
+ if ((mState == STATE_FINISHED) || (mState == STATE_CANCELLED)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mState = STATE_CANCELLED;
+ mSucceeded = false;
+
+ // Cancel all running downloads
+ for (uint32_t i = 0; i < mItems.Length(); ++i) {
+ nsOfflineCacheUpdateItem* item = mItems[i];
+
+ if (item->IsInProgress()) item->Cancel();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::AddObserver(nsIOfflineCacheUpdateObserver* aObserver,
+ bool aHoldWeak) {
+ LOG(("nsOfflineCacheUpdate::AddObserver [%p] to update [%p]", aObserver,
+ this));
+
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ if (aHoldWeak) {
+ nsWeakPtr weakRef = do_GetWeakReference(aObserver);
+ mWeakObservers.AppendObject(weakRef);
+ } else {
+ mObservers.AppendObject(aObserver);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::RemoveObserver(nsIOfflineCacheUpdateObserver* aObserver) {
+ LOG(("nsOfflineCacheUpdate::RemoveObserver [%p] from update [%p]", aObserver,
+ this));
+
+ NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
+
+ for (int32_t i = 0; i < mWeakObservers.Count(); i++) {
+ nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
+ do_QueryReferent(mWeakObservers[i]);
+ if (observer == aObserver) {
+ mWeakObservers.RemoveObjectAt(i);
+ return NS_OK;
+ }
+ }
+
+ for (int32_t i = 0; i < mObservers.Count(); i++) {
+ if (mObservers[i] == aObserver) {
+ mObservers.RemoveObjectAt(i);
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::GetByteProgress(uint64_t* _result) {
+ NS_ENSURE_ARG(_result);
+
+ *_result = mByteProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::Schedule() {
+ LOG(("nsOfflineCacheUpdate::Schedule [%p]", this));
+
+ nsOfflineCacheUpdateService* service =
+ nsOfflineCacheUpdateService::EnsureService();
+
+ if (!service) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return service->ScheduleUpdate(this);
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::UpdateStateChanged(nsIOfflineCacheUpdate* aUpdate,
+ uint32_t aState) {
+ if (aState == nsIOfflineCacheUpdateObserver::STATE_FINISHED) {
+ // Take the mSucceeded flag from the underlying update, we will be
+ // queried for it soon. mSucceeded of this update is false (manifest
+ // check failed) but the subsequent re-fetch update might succeed
+ bool succeeded;
+ aUpdate->GetSucceeded(&succeeded);
+ mSucceeded = succeeded;
+ }
+
+ NotifyState(aState);
+ if (aState == nsIOfflineCacheUpdateObserver::STATE_FINISHED)
+ aUpdate->RemoveObserver(this);
+
+ return NS_OK;
+}
+
+void nsOfflineCacheUpdate::SetCookieJarSettings(
+ nsICookieJarSettings* aCookieJarSettings) {
+ mCookieJarSettings = aCookieJarSettings;
+}
+
+void nsOfflineCacheUpdate::SetCookieJarSettingsArgs(
+ const CookieJarSettingsArgs& aCookieJarSettingsArgs) {
+ MOZ_ASSERT(!mCookieJarSettings);
+
+ CookieJarSettings::Deserialize(aCookieJarSettingsArgs,
+ getter_AddRefs(mCookieJarSettings));
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::ApplicationCacheAvailable(
+ nsIApplicationCache* applicationCache) {
+ AssociateDocuments(applicationCache);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdate::nsIRunable
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdate::Run() {
+ ProcessNextURI();
+ return NS_OK;
+}
diff --git a/uriloader/prefetch/nsOfflineCacheUpdate.h b/uriloader/prefetch/nsOfflineCacheUpdate.h
new file mode 100644
index 0000000000..bb7699c046
--- /dev/null
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.h
@@ -0,0 +1,369 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsOfflineCacheUpdate_h__
+#define nsOfflineCacheUpdate_h__
+
+#include "nsIOfflineCacheUpdate.h"
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIMutableArray.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIApplicationCache.h"
+#include "nsIRunnable.h"
+#include "nsIStreamListener.h"
+#include "nsIURI.h"
+#include "nsClassHashtable.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+#include "nsICryptoHash.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/WeakPtr.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+
+namespace mozilla {
+
+namespace net {
+class CookieJarSettingsArgs;
+}
+
+} // namespace mozilla
+
+class nsOfflineCacheUpdate;
+
+class nsOfflineCacheUpdateItem : public nsIStreamListener,
+ public nsIRunnable,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+
+ nsOfflineCacheUpdateItem(nsIURI* aURI, nsIURI* aReferrerURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIApplicationCache* aApplicationCache,
+ nsIApplicationCache* aPreviousApplicationCache,
+ uint32_t aType, uint32_t aLoadFlags);
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mReferrerURI;
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+ nsCOMPtr<nsIApplicationCache> mApplicationCache;
+ nsCOMPtr<nsIApplicationCache> mPreviousApplicationCache;
+ nsCString mCacheKey;
+ uint32_t mItemType;
+ uint32_t mLoadFlags;
+
+ nsresult OpenChannel(nsOfflineCacheUpdate* aUpdate);
+ nsresult Cancel();
+ nsresult GetRequestSucceeded(bool* succeeded);
+
+ bool IsInProgress();
+ bool IsScheduled();
+ bool IsCompleted();
+
+ nsresult GetStatus(uint16_t* aStatus);
+
+ private:
+ enum LoadStatus : uint16_t {
+ UNINITIALIZED = 0U,
+ REQUESTED = 1U,
+ RECEIVING = 2U,
+ LOADED = 3U
+ };
+
+ RefPtr<nsOfflineCacheUpdate> mUpdate;
+ nsCOMPtr<nsIChannel> mChannel;
+ uint16_t mState;
+
+ protected:
+ virtual ~nsOfflineCacheUpdateItem();
+
+ int64_t mBytesRead;
+};
+
+class nsOfflineManifestItem : public nsOfflineCacheUpdateItem {
+ public:
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ nsOfflineManifestItem(nsIURI* aURI, nsIURI* aReferrerURI,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIApplicationCache* aApplicationCache,
+ nsIApplicationCache* aPreviousApplicationCache);
+ virtual ~nsOfflineManifestItem();
+
+ nsCOMArray<nsIURI>& GetExplicitURIs() { return mExplicitURIs; }
+ nsCOMArray<nsIURI>& GetAnonymousURIs() { return mAnonymousURIs; }
+ nsCOMArray<nsIURI>& GetFallbackURIs() { return mFallbackURIs; }
+
+ nsTArray<nsCString>& GetOpportunisticNamespaces() {
+ return mOpportunisticNamespaces;
+ }
+ nsIArray* GetNamespaces() { return mNamespaces.get(); }
+
+ bool ParseSucceeded() {
+ return (mParserState != PARSE_INIT && mParserState != PARSE_ERROR);
+ }
+ bool NeedsUpdate() { return mParserState != PARSE_INIT && mNeedsUpdate; }
+
+ void GetManifestHash(nsCString& aManifestHash) {
+ aManifestHash = mManifestHashValue;
+ }
+
+ private:
+ static nsresult ReadManifest(nsIInputStream* aInputStream, void* aClosure,
+ const char* aFromSegment, uint32_t aOffset,
+ uint32_t aCount, uint32_t* aBytesConsumed);
+
+ nsresult AddNamespace(uint32_t namespaceType, const nsCString& namespaceSpec,
+ const nsCString& data);
+
+ nsresult HandleManifestLine(const nsCString::const_iterator& aBegin,
+ const nsCString::const_iterator& aEnd);
+
+ /**
+ * Saves "offline-manifest-hash" meta data from the old offline cache
+ * token to mOldManifestHashValue member to be compared on
+ * successfull load.
+ */
+ nsresult GetOldManifestContentHash(nsIRequest* aRequest);
+ /**
+ * This method setups the mNeedsUpdate to false when hash value
+ * of the just downloaded manifest file is the same as stored in cache's
+ * "offline-manifest-hash" meta data. Otherwise stores the new value
+ * to this meta data.
+ */
+ nsresult CheckNewManifestContentHash(nsIRequest* aRequest);
+
+ void ReadStrictFileOriginPolicyPref();
+
+ enum {
+ PARSE_INIT,
+ PARSE_CACHE_ENTRIES,
+ PARSE_FALLBACK_ENTRIES,
+ PARSE_BYPASS_ENTRIES,
+ PARSE_UNKNOWN_SECTION,
+ PARSE_ERROR
+ } mParserState;
+
+ nsCString mReadBuf;
+
+ nsCOMArray<nsIURI> mExplicitURIs;
+ nsCOMArray<nsIURI> mAnonymousURIs;
+ nsCOMArray<nsIURI> mFallbackURIs;
+
+ // All opportunistic caching namespaces. Used to decide whether
+ // to include previously-opportunistically-cached entries.
+ nsTArray<nsCString> mOpportunisticNamespaces;
+
+ // Array of nsIApplicationCacheNamespace objects specified by the
+ // manifest.
+ nsCOMPtr<nsIMutableArray> mNamespaces;
+
+ bool mNeedsUpdate;
+ bool mStrictFileOriginPolicy;
+
+ // manifest hash data
+ nsCOMPtr<nsICryptoHash> mManifestHash;
+ bool mManifestHashInitialized;
+ nsCString mManifestHashValue;
+ nsCString mOldManifestHashValue;
+};
+
+class nsOfflineCacheUpdateOwner : public mozilla::SupportsWeakPtr {
+ public:
+ virtual ~nsOfflineCacheUpdateOwner() {}
+ virtual nsresult UpdateFinished(nsOfflineCacheUpdate* aUpdate) = 0;
+};
+
+class nsOfflineCacheUpdate final : public nsIOfflineCacheUpdate,
+ public nsIOfflineCacheUpdateObserver,
+ public nsIRunnable,
+ public nsOfflineCacheUpdateOwner {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOFFLINECACHEUPDATE
+ NS_DECL_NSIOFFLINECACHEUPDATEOBSERVER
+ NS_DECL_NSIRUNNABLE
+
+ nsOfflineCacheUpdate();
+
+ static nsresult GetCacheKey(nsIURI* aURI, nsACString& aKey);
+
+ nsresult Init();
+
+ nsresult Begin();
+
+ void LoadCompleted(nsOfflineCacheUpdateItem* aItem);
+ void ManifestCheckCompleted(nsresult aStatus, const nsCString& aManifestHash);
+ void StickDocument(nsIURI* aDocumentURI);
+
+ void SetOwner(nsOfflineCacheUpdateOwner* aOwner);
+
+ bool IsForGroupID(const nsACString& groupID);
+ bool IsForProfile(nsIFile* aCustomProfileDir);
+
+ virtual nsresult UpdateFinished(nsOfflineCacheUpdate* aUpdate) override;
+
+ nsICookieJarSettings* CookieJarSettings() const { return mCookieJarSettings; }
+ void SetCookieJarSettings(nsICookieJarSettings* aCookieJarSettings);
+ void SetCookieJarSettingsArgs(
+ const mozilla::net::CookieJarSettingsArgs& aCookieJarSettingsArgs);
+
+ protected:
+ ~nsOfflineCacheUpdate();
+
+ friend class nsOfflineCacheUpdateItem;
+ void OnByteProgress(uint64_t byteIncrement);
+
+ private:
+ nsresult InitInternal(nsIURI* aManifestURI, nsIPrincipal* aPrincipal);
+ nsresult HandleManifest(bool* aDoUpdate);
+ nsresult AddURI(nsIURI* aURI, uint32_t aItemType, uint32_t aLoadFlags = 0);
+
+ nsresult ProcessNextURI();
+
+ // Adds items from the previous cache witha type matching aType.
+ // If namespaceFilter is non-null, only items matching the
+ // specified namespaces will be added.
+ nsresult AddExistingItems(uint32_t aType,
+ nsTArray<nsCString>* namespaceFilter = nullptr);
+ nsresult ScheduleImplicit();
+ void AssociateDocuments(nsIApplicationCache* cache);
+ bool CheckUpdateAvailability();
+ void NotifyUpdateAvailability(bool updateAvailable);
+
+ void GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver>& aObservers);
+ void NotifyState(uint32_t state);
+ nsresult Finish();
+ nsresult FinishNoNotify();
+
+ void AsyncFinishWithError();
+
+ // Find one non-pinned cache group and evict it.
+ nsresult EvictOneNonPinned();
+
+ enum {
+ STATE_UNINITIALIZED,
+ STATE_INITIALIZED,
+ STATE_CHECKING,
+ STATE_DOWNLOADING,
+ STATE_CANCELLED,
+ STATE_FINISHED
+ } mState;
+
+ mozilla::WeakPtr<nsOfflineCacheUpdateOwner> mOwner;
+
+ bool mAddedItems;
+ bool mPartialUpdate;
+ bool mOnlyCheckUpdate;
+ bool mSucceeded;
+ bool mObsolete;
+
+ nsCString mUpdateDomain;
+ nsCString mGroupID;
+ nsCOMPtr<nsIURI> mManifestURI;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+ nsCOMPtr<nsIFile> mCustomProfileDir;
+ nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
+
+ nsCOMPtr<nsIObserver> mUpdateAvailableObserver;
+
+ nsCOMPtr<nsIApplicationCache> mApplicationCache;
+ nsCOMPtr<nsIApplicationCache> mPreviousApplicationCache;
+
+ nsCOMPtr<nsIObserverService> mObserverService;
+
+ RefPtr<nsOfflineManifestItem> mManifestItem;
+
+ /* Items being updated */
+ uint32_t mItemsInProgress;
+ nsTArray<RefPtr<nsOfflineCacheUpdateItem> > mItems;
+
+ /* Clients watching this update for changes */
+ nsCOMArray<nsIWeakReference> mWeakObservers;
+ nsCOMArray<nsIOfflineCacheUpdateObserver> mObservers;
+
+ /* Documents that requested this update */
+ nsCOMArray<nsIURI> mDocumentURIs;
+
+ /* Reschedule count. When an update is rescheduled due to
+ * mismatched manifests, the reschedule count will be increased. */
+ uint32_t mRescheduleCount;
+
+ /* Whena an entry for a pinned app is retried, retries count is
+ * increaded. */
+ uint32_t mPinnedEntryRetriesCount;
+
+ RefPtr<nsOfflineCacheUpdate> mImplicitUpdate;
+
+ bool mPinned;
+
+ uint64_t mByteProgress;
+};
+
+class nsOfflineCacheUpdateService final : public nsIOfflineCacheUpdateService,
+ public nsIObserver,
+ public nsOfflineCacheUpdateOwner,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOFFLINECACHEUPDATESERVICE
+ NS_DECL_NSIOBSERVER
+
+ nsOfflineCacheUpdateService();
+
+ nsresult Init();
+
+ nsresult ScheduleUpdate(nsOfflineCacheUpdate* aUpdate);
+ nsresult FindUpdate(nsIURI* aManifestURI, nsACString const& aOriginSuffix,
+ nsIFile* aCustomProfileDir,
+ nsOfflineCacheUpdate** aUpdate);
+
+ nsresult Schedule(nsIURI* aManifestURI, nsIURI* aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ mozilla::dom::Document* aDocument,
+ nsPIDOMWindowInner* aWindow, nsIFile* aCustomProfileDir,
+ nsIOfflineCacheUpdate** aUpdate);
+
+ virtual nsresult UpdateFinished(nsOfflineCacheUpdate* aUpdate) override;
+
+ /**
+ * Returns the singleton nsOfflineCacheUpdateService without an addref, or
+ * nullptr if the service couldn't be created.
+ */
+ static nsOfflineCacheUpdateService* EnsureService();
+
+ static already_AddRefed<nsOfflineCacheUpdateService> GetInstance();
+
+ static nsresult OfflineAppPinnedForURI(nsIURI* aDocumentURI, bool* aPinned);
+
+ static nsTHashtable<nsCStringHashKey>* AllowedDomains();
+
+ private:
+ ~nsOfflineCacheUpdateService();
+
+ nsresult ProcessNextUpdate();
+
+ nsTArray<RefPtr<nsOfflineCacheUpdate> > mUpdates;
+ static nsTHashtable<nsCStringHashKey>* mAllowedDomains;
+
+ bool mDisabled;
+ bool mUpdateRunning;
+};
+
+#endif
diff --git a/uriloader/prefetch/nsOfflineCacheUpdateService.cpp b/uriloader/prefetch/nsOfflineCacheUpdateService.cpp
new file mode 100644
index 0000000000..3f43193e39
--- /dev/null
+++ b/uriloader/prefetch/nsOfflineCacheUpdateService.cpp
@@ -0,0 +1,646 @@
+/* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "OfflineCacheUpdateChild.h"
+#include "OfflineCacheUpdateParent.h"
+#include "nsXULAppAPI.h"
+#include "OfflineCacheUpdateGlue.h"
+#include "nsOfflineCacheUpdate.h"
+
+#include "nsCURILoader.h"
+#include "nsIApplicationCacheService.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Document.h"
+#include "nsIObserverService.h"
+#include "nsIWebProgress.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrincipal.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Components.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "nsContentUtils.h"
+#include "mozilla/Unused.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static nsOfflineCacheUpdateService* gOfflineCacheUpdateService = nullptr;
+
+nsTHashtable<nsCStringHashKey>* nsOfflineCacheUpdateService::mAllowedDomains =
+ nullptr;
+
+nsTHashtable<nsCStringHashKey>* nsOfflineCacheUpdateService::AllowedDomains() {
+ if (!mAllowedDomains) mAllowedDomains = new nsTHashtable<nsCStringHashKey>();
+
+ return mAllowedDomains;
+}
+
+typedef mozilla::docshell::OfflineCacheUpdateParent OfflineCacheUpdateParent;
+typedef mozilla::docshell::OfflineCacheUpdateChild OfflineCacheUpdateChild;
+typedef mozilla::docshell::OfflineCacheUpdateGlue OfflineCacheUpdateGlue;
+
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=nsOfflineCacheUpdate:5
+// set MOZ_LOG_FILE=offlineupdate.log
+//
+// this enables LogLevel::Debug level information and places all output in
+// the file offlineupdate.log
+//
+LazyLogModule gOfflineCacheUpdateLog("nsOfflineCacheUpdate");
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug, args)
+
+#undef LOG_ENABLED
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(gOfflineCacheUpdateLog, mozilla::LogLevel::Debug)
+
+//-----------------------------------------------------------------------------
+// nsOfflineCachePendingUpdate
+//-----------------------------------------------------------------------------
+
+class nsOfflineCachePendingUpdate final : public nsIWebProgressListener,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ nsOfflineCachePendingUpdate(nsOfflineCacheUpdateService* aService,
+ nsIURI* aManifestURI, nsIURI* aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ Document* aDocument)
+ : mService(aService),
+ mManifestURI(aManifestURI),
+ mDocumentURI(aDocumentURI),
+ mLoadingPrincipal(aLoadingPrincipal),
+ mDidReleaseThis(false) {
+ mDocument = do_GetWeakReference(aDocument);
+ }
+
+ private:
+ ~nsOfflineCachePendingUpdate() {}
+
+ RefPtr<nsOfflineCacheUpdateService> mService;
+ nsCOMPtr<nsIURI> mManifestURI;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+ nsWeakPtr mDocument;
+ bool mDidReleaseThis;
+};
+
+NS_IMPL_ISUPPORTS(nsOfflineCachePendingUpdate, nsIWebProgressListener,
+ nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService::nsIWebProgressListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCachePendingUpdate::OnProgressChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest,
+ int32_t curSelfProgress,
+ int32_t maxSelfProgress,
+ int32_t curTotalProgress,
+ int32_t maxTotalProgress) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCachePendingUpdate::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t progressStateFlags,
+ nsresult aStatus) {
+ if (mDidReleaseThis) {
+ return NS_OK;
+ }
+ nsCOMPtr<Document> updateDoc = do_QueryReferent(mDocument);
+ if (!updateDoc) {
+ // The document that scheduled this update has gone away,
+ // we don't need to listen anymore.
+ aWebProgress->RemoveProgressListener(this);
+ MOZ_ASSERT(!mDidReleaseThis);
+ mDidReleaseThis = true;
+ NS_RELEASE_THIS();
+ return NS_OK;
+ }
+
+ if (!(progressStateFlags & STATE_STOP)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> windowProxy;
+ aWebProgress->GetDOMWindow(getter_AddRefs(windowProxy));
+ if (!windowProxy) return NS_OK;
+
+ auto* outerWindow = nsPIDOMWindowOuter::From(windowProxy);
+ nsPIDOMWindowInner* innerWindow = outerWindow->GetCurrentInnerWindow();
+
+ nsCOMPtr<Document> progressDoc = outerWindow->GetDoc();
+ if (!progressDoc || progressDoc != updateDoc) {
+ return NS_OK;
+ }
+
+ LOG(("nsOfflineCachePendingUpdate::OnStateChange [%p, doc=%p]", this,
+ progressDoc.get()));
+
+ // Only schedule the update if the document loaded successfully
+ if (NS_SUCCEEDED(aStatus)) {
+ nsCOMPtr<nsIOfflineCacheUpdate> update;
+ mService->Schedule(mManifestURI, mDocumentURI, mLoadingPrincipal, updateDoc,
+ innerWindow, nullptr, getter_AddRefs(update));
+ if (mDidReleaseThis) {
+ return NS_OK;
+ }
+ }
+
+ aWebProgress->RemoveProgressListener(this);
+ MOZ_ASSERT(!mDidReleaseThis);
+ mDidReleaseThis = true;
+ NS_RELEASE_THIS();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCachePendingUpdate::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI* location,
+ uint32_t aFlags) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCachePendingUpdate::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCachePendingUpdate::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aState) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCachePendingUpdate::OnContentBlockingEvent(
+ nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t aEvent) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheUpdateService, nsIOfflineCacheUpdateService,
+ nsIObserver, nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService <public>
+//-----------------------------------------------------------------------------
+
+nsOfflineCacheUpdateService::nsOfflineCacheUpdateService()
+ : mDisabled(false), mUpdateRunning(false) {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+nsOfflineCacheUpdateService::~nsOfflineCacheUpdateService() {
+ MOZ_ASSERT(gOfflineCacheUpdateService == this);
+ gOfflineCacheUpdateService = nullptr;
+
+ delete mAllowedDomains;
+ mAllowedDomains = nullptr;
+}
+
+nsresult nsOfflineCacheUpdateService::Init() {
+ // Observe xpcom-shutdown event
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) return NS_ERROR_FAILURE;
+
+ nsresult rv =
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ gOfflineCacheUpdateService = this;
+
+ return NS_OK;
+}
+
+/* static */
+already_AddRefed<nsOfflineCacheUpdateService>
+nsOfflineCacheUpdateService::GetInstance() {
+ if (!gOfflineCacheUpdateService) {
+ auto serv = MakeRefPtr<nsOfflineCacheUpdateService>();
+ if (NS_FAILED(serv->Init())) serv = nullptr;
+ MOZ_ASSERT(gOfflineCacheUpdateService == serv.get());
+ return serv.forget();
+ }
+
+ return do_AddRef(gOfflineCacheUpdateService);
+}
+
+/* static */
+nsOfflineCacheUpdateService* nsOfflineCacheUpdateService::EnsureService() {
+ if (!gOfflineCacheUpdateService) {
+ // Make the service manager hold a long-lived reference to the service
+ nsCOMPtr<nsIOfflineCacheUpdateService> service =
+ components::OfflineCacheUpdate::Service();
+ Unused << service;
+ }
+
+ return gOfflineCacheUpdateService;
+}
+
+nsresult nsOfflineCacheUpdateService::ScheduleUpdate(
+ nsOfflineCacheUpdate* aUpdate) {
+ LOG(("nsOfflineCacheUpdateService::Schedule [%p, update=%p]", this, aUpdate));
+
+ aUpdate->SetOwner(this);
+
+ mUpdates.AppendElement(aUpdate);
+ ProcessNextUpdate();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::ScheduleOnDocumentStop(
+ nsIURI* aManifestURI, nsIURI* aDocumentURI, nsIPrincipal* aLoadingPrincipal,
+ Document* aDocument) {
+ LOG(
+ ("nsOfflineCacheUpdateService::ScheduleOnDocumentStop [%p, "
+ "manifestURI=%p, documentURI=%p doc=%p]",
+ this, aManifestURI, aDocumentURI, aDocument));
+
+ nsCOMPtr<nsIWebProgress> progress =
+ do_QueryInterface(aDocument->GetContainer());
+ NS_ENSURE_TRUE(progress, NS_ERROR_INVALID_ARG);
+
+ // Proceed with cache update
+ RefPtr<nsOfflineCachePendingUpdate> update = new nsOfflineCachePendingUpdate(
+ this, aManifestURI, aDocumentURI, aLoadingPrincipal, aDocument);
+ NS_ENSURE_TRUE(update, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = progress->AddProgressListener(
+ update, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The update will release when it has scheduled itself.
+ Unused << update.forget();
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheUpdateService::UpdateFinished(
+ nsOfflineCacheUpdate* aUpdate) {
+ LOG(("nsOfflineCacheUpdateService::UpdateFinished [%p, update=%p]", this,
+ aUpdate));
+
+ NS_ASSERTION(mUpdates.Length() > 0 && mUpdates[0] == aUpdate,
+ "Unknown update completed");
+
+ // keep this item alive until we're done notifying observers
+ RefPtr<nsOfflineCacheUpdate> update = mUpdates[0];
+ Unused << update;
+ mUpdates.RemoveElementAt(0);
+ mUpdateRunning = false;
+
+ ProcessNextUpdate();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService <private>
+//-----------------------------------------------------------------------------
+
+nsresult nsOfflineCacheUpdateService::ProcessNextUpdate() {
+ LOG(("nsOfflineCacheUpdateService::ProcessNextUpdate [%p, num=%zu]", this,
+ mUpdates.Length()));
+
+ if (mDisabled) return NS_ERROR_ABORT;
+
+ if (mUpdateRunning) return NS_OK;
+
+ if (mUpdates.Length() > 0) {
+ mUpdateRunning = true;
+
+ return mUpdates[0]->Begin();
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService::nsIOfflineCacheUpdateService
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::GetNumUpdates(uint32_t* aNumUpdates) {
+ LOG(("nsOfflineCacheUpdateService::GetNumUpdates [%p]", this));
+
+ *aNumUpdates = mUpdates.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::GetUpdate(uint32_t aIndex,
+ nsIOfflineCacheUpdate** aUpdate) {
+ LOG(("nsOfflineCacheUpdateService::GetUpdate [%p, %d]", this, aIndex));
+
+ if (aIndex < mUpdates.Length()) {
+ NS_ADDREF(*aUpdate = mUpdates[aIndex]);
+ } else {
+ *aUpdate = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheUpdateService::FindUpdate(
+ nsIURI* aManifestURI, nsACString const& aOriginSuffix,
+ nsIFile* aCustomProfileDir, nsOfflineCacheUpdate** aUpdate) {
+ nsresult rv;
+
+ nsCOMPtr<nsIApplicationCacheService> cacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString groupID;
+ rv =
+ cacheService->BuildGroupIDForSuffix(aManifestURI, aOriginSuffix, groupID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsOfflineCacheUpdate> update;
+ for (uint32_t i = 0; i < mUpdates.Length(); i++) {
+ update = mUpdates[i];
+
+ bool partial;
+ rv = update->GetPartial(&partial);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (partial) {
+ // Partial updates aren't considered
+ continue;
+ }
+
+ if (update->IsForGroupID(groupID) &&
+ update->IsForProfile(aCustomProfileDir)) {
+ update.swap(*aUpdate);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsOfflineCacheUpdateService::Schedule(
+ nsIURI* aManifestURI, nsIURI* aDocumentURI, nsIPrincipal* aLoadingPrincipal,
+ Document* aDocument, nsPIDOMWindowInner* aWindow,
+ nsIFile* aCustomProfileDir, nsIOfflineCacheUpdate** aUpdate) {
+ nsCOMPtr<nsIOfflineCacheUpdate> update;
+ if (GeckoProcessType_Default != XRE_GetProcessType()) {
+ update = new OfflineCacheUpdateChild(aWindow);
+ } else {
+ update = new OfflineCacheUpdateGlue();
+ }
+
+ nsresult rv;
+
+ if (aWindow) {
+ // Ensure there is window.applicationCache object that is
+ // responsible for association of the new applicationCache
+ // with the corresponding document. Just ignore the result.
+ aWindow->GetApplicationCache();
+ }
+
+ rv = update->Init(aManifestURI, aDocumentURI, aLoadingPrincipal, aDocument,
+ aCustomProfileDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = update->Schedule();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aUpdate = update);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::ScheduleUpdate(nsIURI* aManifestURI,
+ nsIURI* aDocumentURI,
+ nsIPrincipal* aLoadingPrincipal,
+ mozIDOMWindow* aWindow,
+ nsIOfflineCacheUpdate** aUpdate) {
+ return Schedule(aManifestURI, aDocumentURI, aLoadingPrincipal, nullptr,
+ nsPIDOMWindowInner::From(aWindow), nullptr, aUpdate);
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::ScheduleAppUpdate(
+ nsIURI* aManifestURI, nsIURI* aDocumentURI, nsIPrincipal* aLoadingPrincipal,
+ nsIFile* aProfileDir, nsIOfflineCacheUpdate** aUpdate) {
+ return Schedule(aManifestURI, aDocumentURI, aLoadingPrincipal, nullptr,
+ nullptr, aProfileDir, aUpdate);
+}
+
+NS_IMETHODIMP nsOfflineCacheUpdateService::CheckForUpdate(
+ nsIURI* aManifestURI, nsIPrincipal* aLoadingPrincipal,
+ nsIObserver* aObserver) {
+ if (GeckoProcessType_Default != XRE_GetProcessType()) {
+ // Not intended to support this on child processes
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsCOMPtr<nsIOfflineCacheUpdate> update = new OfflineCacheUpdateGlue();
+
+ nsresult rv;
+
+ rv = update->InitForUpdateCheck(aManifestURI, aLoadingPrincipal, aObserver);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = update->Schedule();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ if (mUpdates.Length() > 0) mUpdates[0]->Cancel();
+ mDisabled = true;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService::nsIOfflineCacheUpdateService
+//-----------------------------------------------------------------------------
+
+static nsresult OfflineAppPermForPrincipal(nsIPrincipal* aPrincipal,
+ bool pinned, bool* aAllowed) {
+ *aAllowed = false;
+
+ if (!StaticPrefs::browser_cache_offline_enable()) {
+ return NS_OK;
+ }
+
+ if (!StaticPrefs::browser_cache_offline_storage_enable()) {
+ return NS_OK;
+ }
+
+ if (!aPrincipal) return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIURI> uri;
+ // Casting to BasePrincipal, as we can't get InnerMost URI otherwise
+ auto* basePrincipal = BasePrincipal::Cast(aPrincipal);
+ basePrincipal->GetURI(getter_AddRefs(uri));
+
+ if (!uri) return NS_OK;
+
+ nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri);
+ if (!innerURI) return NS_OK;
+
+ // only https applications can use offline APIs.
+ if (!innerURI->SchemeIs("https")) {
+ return NS_OK;
+ }
+
+ nsAutoCString domain;
+ nsresult rv = innerURI->GetAsciiHost(domain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (nsOfflineCacheUpdateService::AllowedDomains()->Contains(domain)) {
+ *aAllowed = true;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPermissionManager> permissionManager =
+ services::GetPermissionManager();
+ if (!permissionManager) {
+ return NS_OK;
+ }
+
+ uint32_t perm;
+ const nsLiteralCString permName = pinned ? "pin-app"_ns : "offline-app"_ns;
+ permissionManager->TestExactPermissionFromPrincipal(aPrincipal, permName,
+ &perm);
+
+ if (perm == nsIPermissionManager::ALLOW_ACTION ||
+ perm == nsIOfflineCacheUpdateService::ALLOW_NO_WARN) {
+ *aAllowed = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::OfflineAppAllowed(nsIPrincipal* aPrincipal,
+ bool* aAllowed) {
+ return OfflineAppPermForPrincipal(aPrincipal, false, aAllowed);
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::OfflineAppAllowedForURI(nsIURI* aURI,
+ bool* aAllowed) {
+ OriginAttributes attrs;
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(aURI, attrs);
+ return OfflineAppPermForPrincipal(principal, false, aAllowed);
+}
+
+nsresult nsOfflineCacheUpdateService::OfflineAppPinnedForURI(
+ nsIURI* aDocumentURI, bool* aPinned) {
+ OriginAttributes attrs;
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(aDocumentURI, attrs);
+ return OfflineAppPermForPrincipal(principal, true, aPinned);
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::AllowOfflineApp(nsIPrincipal* aPrincipal) {
+ nsresult rv;
+
+ if (!StaticPrefs::browser_cache_offline_enable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!StaticPrefs::browser_cache_offline_storage_enable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ // Casting to BasePrincipal, as we can't get InnerMost URI otherwise
+ auto* basePrincipal = BasePrincipal::Cast(aPrincipal);
+ basePrincipal->GetURI(getter_AddRefs(uri));
+
+ if (!uri) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri);
+ if (!innerURI) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // if http then we should prevent this cache
+ if (innerURI->SchemeIs("http")) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (GeckoProcessType_Default != XRE_GetProcessType()) {
+ ContentChild* child = ContentChild::GetSingleton();
+
+ if (!child->SendSetOfflinePermission(IPC::Principal(aPrincipal))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString domain;
+ rv = aPrincipal->GetBaseDomain(domain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsOfflineCacheUpdateService::AllowedDomains()->PutEntry(domain);
+ } else {
+ nsCOMPtr<nsIPermissionManager> permissionManager =
+ services::GetPermissionManager();
+ if (!permissionManager) return NS_ERROR_NOT_AVAILABLE;
+
+ rv = permissionManager->AddFromPrincipal(
+ aPrincipal, "offline-app"_ns, nsIPermissionManager::ALLOW_ACTION,
+ nsIPermissionManager::EXPIRE_NEVER, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
diff --git a/uriloader/prefetch/nsPrefetchService.cpp b/uriloader/prefetch/nsPrefetchService.cpp
new file mode 100644
index 0000000000..94762702e6
--- /dev/null
+++ b/uriloader/prefetch/nsPrefetchService.cpp
@@ -0,0 +1,889 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsPrefetchService.h"
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/Components.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/HTMLLinkElement.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/Preferences.h"
+#include "ReferrerInfo.h"
+
+#include "nsIObserverService.h"
+#include "nsIWebProgress.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIURL.h"
+#include "nsISupportsPriority.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsStreamUtils.h"
+#include "prtime.h"
+#include "mozilla/Logging.h"
+#include "plstr.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsINode.h"
+#include "mozilla/dom/Document.h"
+#include "nsContentUtils.h"
+#include "mozilla/AsyncEventDispatcher.h"
+
+using namespace mozilla;
+
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=nsPrefetch:5
+// set MOZ_LOG_FILE=prefetch.log
+//
+// this enables LogLevel::Debug level information and places all output in
+// the file prefetch.log
+//
+static LazyLogModule gPrefetchLog("nsPrefetch");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gPrefetchLog, mozilla::LogLevel::Debug, args)
+
+#undef LOG_ENABLED
+#define LOG_ENABLED() MOZ_LOG_TEST(gPrefetchLog, mozilla::LogLevel::Debug)
+
+#define PREFETCH_PREF "network.prefetch-next"
+#define PARALLELISM_PREF "network.prefetch-next.parallelism"
+#define AGGRESSIVE_PREF "network.prefetch-next.aggressive"
+
+//-----------------------------------------------------------------------------
+// nsPrefetchNode <public>
+//-----------------------------------------------------------------------------
+
+nsPrefetchNode::nsPrefetchNode(nsPrefetchService* aService, nsIURI* aURI,
+ nsIReferrerInfo* aReferrerInfo, nsINode* aSource,
+ nsContentPolicyType aPolicyType, bool aPreload)
+ : mURI(aURI),
+ mReferrerInfo(aReferrerInfo),
+ mPolicyType(aPolicyType),
+ mPreload(aPreload),
+ mService(aService),
+ mChannel(nullptr),
+ mBytesRead(0),
+ mShouldFireLoadEvent(false) {
+ nsWeakPtr source = do_GetWeakReference(aSource);
+ mSources.AppendElement(source);
+}
+
+nsresult nsPrefetchNode::OpenChannel() {
+ if (mSources.IsEmpty()) {
+ // Don't attempt to prefetch if we don't have a source node
+ // (which should never happen).
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsINode> source;
+ while (!mSources.IsEmpty() &&
+ !(source = do_QueryReferent(mSources.ElementAt(0)))) {
+ // If source is null remove it.
+ // (which should never happen).
+ mSources.RemoveElementAt(0);
+ }
+
+ if (!source) {
+ // Don't attempt to prefetch if we don't have a source node
+ // (which should never happen).
+
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsILoadGroup> loadGroup = source->OwnerDoc()->GetDocumentLoadGroup();
+ CORSMode corsMode = CORS_NONE;
+ if (auto* link = dom::HTMLLinkElement::FromNode(source)) {
+ corsMode = link->GetCORSMode();
+ }
+
+ uint32_t securityFlags;
+ if (corsMode == CORS_NONE) {
+ securityFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT;
+ } else {
+ securityFlags = nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
+ if (corsMode == CORS_USE_CREDENTIALS) {
+ securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
+ }
+ }
+ nsresult rv = NS_NewChannelInternal(
+ getter_AddRefs(mChannel), mURI, source, source->NodePrincipal(),
+ nullptr, // aTriggeringPrincipal
+ Maybe<ClientInfo>(), Maybe<ServiceWorkerDescriptor>(), securityFlags,
+ mPolicyType, source->OwnerDoc()->CookieJarSettings(),
+ nullptr, // aPerformanceStorage
+ loadGroup, // aLoadGroup
+ this, // aCallbacks
+ nsIRequest::LOAD_BACKGROUND | nsICachingChannel::LOAD_ONLY_IF_MODIFIED);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // configure HTTP specific stuff
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (httpChannel) {
+ DebugOnly<nsresult> success = httpChannel->SetReferrerInfo(mReferrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(success));
+ success = httpChannel->SetRequestHeader("X-Moz"_ns, "prefetch"_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(success));
+ }
+
+ // Reduce the priority of prefetch network requests.
+ nsCOMPtr<nsISupportsPriority> priorityChannel = do_QueryInterface(mChannel);
+ if (priorityChannel) {
+ priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_LOWEST);
+ }
+
+ rv = mChannel->AsyncOpen(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // Drop the ref to the channel, because we don't want to end up with
+ // cycles through it.
+ mChannel = nullptr;
+ }
+ return rv;
+}
+
+nsresult nsPrefetchNode::CancelChannel(nsresult error) {
+ mChannel->Cancel(error);
+ mChannel = nullptr;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchNode::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsPrefetchNode, nsIRequestObserver, nsIStreamListener,
+ nsIInterfaceRequestor, nsIChannelEventSink,
+ nsIRedirectResultListener)
+
+//-----------------------------------------------------------------------------
+// nsPrefetchNode::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsPrefetchNode::OnStartRequest(nsIRequest* aRequest) {
+ nsresult rv;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // if the load is cross origin without CORS, or the CORS access is rejected,
+ // always fire load event to avoid leaking site information.
+ nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->LoadInfo();
+ mShouldFireLoadEvent =
+ loadInfo->GetTainting() == LoadTainting::Opaque ||
+ (loadInfo->GetTainting() == LoadTainting::CORS &&
+ (NS_FAILED(httpChannel->GetStatus(&rv)) || NS_FAILED(rv)));
+
+ // no need to prefetch http error page
+ bool requestSucceeded;
+ if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
+ !requestSucceeded) {
+ return NS_BINDING_ABORTED;
+ }
+
+ nsCOMPtr<nsICacheInfoChannel> cacheInfoChannel =
+ do_QueryInterface(aRequest, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // no need to prefetch a document that is already in the cache
+ bool fromCache;
+ if (NS_SUCCEEDED(cacheInfoChannel->IsFromCache(&fromCache)) && fromCache) {
+ LOG(("document is already in the cache; canceling prefetch\n"));
+ // although it's canceled we still want to fire load event
+ mShouldFireLoadEvent = true;
+ return NS_BINDING_ABORTED;
+ }
+
+ //
+ // no need to prefetch a document that must be requested fresh each
+ // and every time.
+ //
+ uint32_t expTime;
+ if (NS_SUCCEEDED(cacheInfoChannel->GetCacheTokenExpirationTime(&expTime))) {
+ if (NowInSeconds() >= expTime) {
+ LOG(
+ ("document cannot be reused from cache; "
+ "canceling prefetch\n"));
+ return NS_BINDING_ABORTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchNode::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aCount) {
+ uint32_t bytesRead = 0;
+ aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &bytesRead);
+ mBytesRead += bytesRead;
+ LOG(("prefetched %u bytes [offset=%" PRIu64 "]\n", bytesRead, aOffset));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchNode::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ LOG(("done prefetching [status=%" PRIx32 "]\n",
+ static_cast<uint32_t>(aStatus)));
+
+ if (mBytesRead == 0 && aStatus == NS_OK && mChannel) {
+ // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
+ // specified), but the object should report loadedSize as if it
+ // did.
+ mChannel->GetContentLength(&mBytesRead);
+ }
+
+ mService->NotifyLoadCompleted(this);
+ mService->DispatchEvent(this, mShouldFireLoadEvent || NS_SUCCEEDED(aStatus));
+ mService->RemoveNodeAndMaybeStartNextPrefetchURI(this);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchNode::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsPrefetchNode::GetInterface(const nsIID& aIID, void** aResult) {
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *aResult = static_cast<nsIChannelEventSink*>(this);
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) {
+ NS_ADDREF_THIS();
+ *aResult = static_cast<nsIRedirectResultListener*>(this);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchNode::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsPrefetchNode::AsyncOnChannelRedirect(
+ nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI));
+ if (NS_FAILED(rv)) return rv;
+
+ if (!newURI->SchemeIs("http") && !newURI->SchemeIs("https")) {
+ LOG(("rejected: URL is not of type http/https\n"));
+ return NS_ERROR_ABORT;
+ }
+
+ // HTTP request headers are not automatically forwarded to the new channel.
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
+ NS_ENSURE_STATE(httpChannel);
+
+ rv = httpChannel->SetRequestHeader("X-Moz"_ns, "prefetch"_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Assign to mChannel after we get notification about success of the
+ // redirect in OnRedirectResult.
+ mRedirectChannel = aNewChannel;
+
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchNode::nsIRedirectResultListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsPrefetchNode::OnRedirectResult(bool proceeding) {
+ if (proceeding && mRedirectChannel) mChannel = mRedirectChannel;
+
+ mRedirectChannel = nullptr;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService <public>
+//-----------------------------------------------------------------------------
+
+nsPrefetchService::nsPrefetchService()
+ : mMaxParallelism(6),
+ mStopCount(0),
+ mHaveProcessed(false),
+ mPrefetchDisabled(true),
+ mAggressive(false) {}
+
+nsPrefetchService::~nsPrefetchService() {
+ Preferences::RemoveObserver(this, PREFETCH_PREF);
+ Preferences::RemoveObserver(this, PARALLELISM_PREF);
+ Preferences::RemoveObserver(this, AGGRESSIVE_PREF);
+ // cannot reach destructor if prefetch in progress (listener owns reference
+ // to this service)
+ EmptyPrefetchQueue();
+}
+
+nsresult nsPrefetchService::Init() {
+ nsresult rv;
+
+ // read prefs and hook up pref observer
+ mPrefetchDisabled = !Preferences::GetBool(PREFETCH_PREF, !mPrefetchDisabled);
+ Preferences::AddWeakObserver(this, PREFETCH_PREF);
+
+ mMaxParallelism = Preferences::GetInt(PARALLELISM_PREF, mMaxParallelism);
+ if (mMaxParallelism < 1) {
+ mMaxParallelism = 1;
+ }
+ Preferences::AddWeakObserver(this, PARALLELISM_PREF);
+
+ mAggressive = Preferences::GetBool(AGGRESSIVE_PREF, false);
+ Preferences::AddWeakObserver(this, AGGRESSIVE_PREF);
+
+ // Observe xpcom-shutdown event
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) return NS_ERROR_FAILURE;
+
+ rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mPrefetchDisabled) {
+ AddProgressListener();
+ }
+
+ return NS_OK;
+}
+
+void nsPrefetchService::RemoveNodeAndMaybeStartNextPrefetchURI(
+ nsPrefetchNode* aFinished) {
+ if (aFinished) {
+ mCurrentNodes.RemoveElement(aFinished);
+ }
+
+ if ((!mStopCount && mHaveProcessed) || mAggressive) {
+ ProcessNextPrefetchURI();
+ }
+}
+
+void nsPrefetchService::ProcessNextPrefetchURI() {
+ if (mCurrentNodes.Length() >= static_cast<uint32_t>(mMaxParallelism)) {
+ // We already have enough prefetches going on, so hold off
+ // for now.
+ return;
+ }
+
+ nsresult rv;
+
+ do {
+ if (mPrefetchQueue.empty()) {
+ break;
+ }
+ RefPtr<nsPrefetchNode> node = std::move(mPrefetchQueue.front());
+ mPrefetchQueue.pop_front();
+
+ if (LOG_ENABLED()) {
+ LOG(("ProcessNextPrefetchURI [%s]\n",
+ node->mURI->GetSpecOrDefault().get()));
+ }
+
+ //
+ // if opening the channel fails (e.g. security check returns an error),
+ // send an error event and then just skip to the next uri
+ //
+ rv = node->OpenChannel();
+ if (NS_SUCCEEDED(rv)) {
+ mCurrentNodes.AppendElement(node);
+ } else {
+ DispatchEvent(node, false);
+ }
+ } while (NS_FAILED(rv));
+}
+
+void nsPrefetchService::NotifyLoadRequested(nsPrefetchNode* node) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) return;
+
+ observerService->NotifyObservers(
+ static_cast<nsIStreamListener*>(node),
+ (node->mPreload) ? "preload-load-requested" : "prefetch-load-requested",
+ nullptr);
+}
+
+void nsPrefetchService::NotifyLoadCompleted(nsPrefetchNode* node) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) return;
+
+ observerService->NotifyObservers(
+ static_cast<nsIStreamListener*>(node),
+ (node->mPreload) ? "preload-load-completed" : "prefetch-load-completed",
+ nullptr);
+}
+
+void nsPrefetchService::DispatchEvent(nsPrefetchNode* node, bool aSuccess) {
+ for (uint32_t i = 0; i < node->mSources.Length(); i++) {
+ nsCOMPtr<nsINode> domNode = do_QueryReferent(node->mSources.ElementAt(i));
+ if (domNode && domNode->IsInComposedDoc()) {
+ // We don't dispatch synchronously since |node| might be in a DocGroup
+ // that we're not allowed to touch. (Our network request happens in the
+ // DocGroup of one of the mSources nodes--not necessarily this one).
+ RefPtr<AsyncEventDispatcher> dispatcher = new AsyncEventDispatcher(
+ domNode, aSuccess ? u"load"_ns : u"error"_ns, CanBubble::eNo);
+ dispatcher->RequireNodeInDocument();
+ dispatcher->PostDOMEvent();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService <private>
+//-----------------------------------------------------------------------------
+
+void nsPrefetchService::AddProgressListener() {
+ // Register as an observer for the document loader
+ nsCOMPtr<nsIWebProgress> progress = components::DocLoader::Service();
+ if (progress)
+ progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+}
+
+void nsPrefetchService::RemoveProgressListener() {
+ // Register as an observer for the document loader
+ nsCOMPtr<nsIWebProgress> progress = components::DocLoader::Service();
+ if (progress) progress->RemoveProgressListener(this);
+}
+
+nsresult nsPrefetchService::EnqueueURI(nsIURI* aURI,
+ nsIReferrerInfo* aReferrerInfo,
+ nsINode* aSource,
+ nsPrefetchNode** aNode) {
+ RefPtr<nsPrefetchNode> node = new nsPrefetchNode(
+ this, aURI, aReferrerInfo, aSource, nsIContentPolicy::TYPE_OTHER, false);
+ mPrefetchQueue.push_back(node);
+ node.forget(aNode);
+ return NS_OK;
+}
+
+void nsPrefetchService::EmptyPrefetchQueue() {
+ while (!mPrefetchQueue.empty()) {
+ mPrefetchQueue.pop_back();
+ }
+}
+
+void nsPrefetchService::StartPrefetching() {
+ //
+ // at initialization time we might miss the first DOCUMENT START
+ // notification, so we have to be careful to avoid letting our
+ // stop count go negative.
+ //
+ if (mStopCount > 0) mStopCount--;
+
+ LOG(("StartPrefetching [stopcount=%d]\n", mStopCount));
+
+ // only start prefetching after we've received enough DOCUMENT
+ // STOP notifications. we do this inorder to defer prefetching
+ // until after all sub-frames have finished loading.
+ if (!mStopCount) {
+ mHaveProcessed = true;
+ while (!mPrefetchQueue.empty() &&
+ mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
+ ProcessNextPrefetchURI();
+ }
+ }
+}
+
+void nsPrefetchService::StopPrefetching() {
+ mStopCount++;
+
+ LOG(("StopPrefetching [stopcount=%d]\n", mStopCount));
+
+ // When we start a load, we need to stop all prefetches that has been
+ // added by the old load, therefore call StopAll only at the moment we
+ // switch to a new page load (i.e. mStopCount == 1).
+ // TODO: do not stop prefetches that are relevant for the new load.
+ if (mStopCount == 1) {
+ StopAll();
+ }
+}
+
+void nsPrefetchService::StopCurrentPrefetchsPreloads(bool aPreload) {
+ for (int32_t i = mCurrentNodes.Length() - 1; i >= 0; --i) {
+ if (mCurrentNodes[i]->mPreload == aPreload) {
+ mCurrentNodes[i]->CancelChannel(NS_BINDING_ABORTED);
+ mCurrentNodes.RemoveElementAt(i);
+ }
+ }
+
+ if (!aPreload) {
+ EmptyPrefetchQueue();
+ }
+}
+
+void nsPrefetchService::StopAll() {
+ for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
+ mCurrentNodes[i]->CancelChannel(NS_BINDING_ABORTED);
+ }
+ mCurrentNodes.Clear();
+ EmptyPrefetchQueue();
+}
+
+nsresult nsPrefetchService::CheckURIScheme(nsIURI* aURI,
+ nsIReferrerInfo* aReferrerInfo) {
+ //
+ // XXX we should really be asking the protocol handler if it supports
+ // caching, so we can determine if there is any value to prefetching.
+ // for now, we'll only prefetch http and https links since we know that's
+ // the most common case.
+ //
+ if (!aURI->SchemeIs("http") && !aURI->SchemeIs("https")) {
+ LOG(("rejected: URL is not of type http/https\n"));
+ return NS_ERROR_ABORT;
+ }
+
+ //
+ // the referrer URI must be http:
+ //
+ nsCOMPtr<nsIURI> referrer = aReferrerInfo->GetOriginalReferrer();
+ if (!referrer) {
+ return NS_ERROR_ABORT;
+ }
+
+ if (!referrer->SchemeIs("http") && !referrer->SchemeIs("https")) {
+ LOG(("rejected: referrer URL is neither http nor https\n"));
+ return NS_ERROR_ABORT;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsPrefetchService, nsIPrefetchService, nsIWebProgressListener,
+ nsIObserver, nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService::nsIPrefetchService
+//-----------------------------------------------------------------------------
+
+nsresult nsPrefetchService::Preload(nsIURI* aURI,
+ nsIReferrerInfo* aReferrerInfo,
+ nsINode* aSource,
+ nsContentPolicyType aPolicyType) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(aReferrerInfo);
+ if (LOG_ENABLED()) {
+ LOG(("PreloadURI [%s]\n", aURI->GetSpecOrDefault().get()));
+ }
+
+ LOG(("rejected: preload service is deprecated\n"));
+ return NS_ERROR_ABORT;
+}
+
+nsresult nsPrefetchService::Prefetch(nsIURI* aURI,
+ nsIReferrerInfo* aReferrerInfo,
+ nsINode* aSource, bool aExplicit) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(aReferrerInfo);
+
+ if (LOG_ENABLED()) {
+ LOG(("PrefetchURI [%s]\n", aURI->GetSpecOrDefault().get()));
+ }
+
+ if (mPrefetchDisabled) {
+ LOG(("rejected: prefetch service is disabled\n"));
+ return NS_ERROR_ABORT;
+ }
+
+ nsresult rv = CheckURIScheme(aURI, aReferrerInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // XXX we might want to either leverage nsIProtocolHandler::protocolFlags
+ // or possibly nsIRequest::loadFlags to determine if this URI should be
+ // prefetched.
+ //
+
+ // skip URLs that contain query strings, except URLs for which prefetching
+ // has been explicitly requested.
+ if (!aExplicit) {
+ nsCOMPtr<nsIURL> url(do_QueryInterface(aURI, &rv));
+ if (NS_FAILED(rv)) return rv;
+ nsAutoCString query;
+ rv = url->GetQuery(query);
+ if (NS_FAILED(rv) || !query.IsEmpty()) {
+ LOG(("rejected: URL has a query string\n"));
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ //
+ // Check whether it is being prefetched.
+ //
+ for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
+ bool equals;
+ if (NS_SUCCEEDED(mCurrentNodes[i]->mURI->Equals(aURI, &equals)) && equals) {
+ nsWeakPtr source = do_GetWeakReference(aSource);
+ if (mCurrentNodes[i]->mSources.IndexOf(source) ==
+ mCurrentNodes[i]->mSources.NoIndex) {
+ LOG(
+ ("URL is already being prefetched, add a new reference "
+ "document\n"));
+ mCurrentNodes[i]->mSources.AppendElement(source);
+ return NS_OK;
+ } else {
+ LOG(("URL is already being prefetched by this document"));
+ return NS_ERROR_ABORT;
+ }
+ }
+ }
+
+ //
+ // Check whether it is on the prefetch queue.
+ //
+ for (std::deque<RefPtr<nsPrefetchNode>>::iterator nodeIt =
+ mPrefetchQueue.begin();
+ nodeIt != mPrefetchQueue.end(); nodeIt++) {
+ bool equals;
+ RefPtr<nsPrefetchNode> node = nodeIt->get();
+ if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) {
+ nsWeakPtr source = do_GetWeakReference(aSource);
+ if (node->mSources.IndexOf(source) == node->mSources.NoIndex) {
+ LOG(
+ ("URL is already being prefetched, add a new reference "
+ "document\n"));
+ node->mSources.AppendElement(do_GetWeakReference(aSource));
+ return NS_OK;
+ } else {
+ LOG(("URL is already being prefetched by this document"));
+ return NS_ERROR_ABORT;
+ }
+ }
+ }
+
+ RefPtr<nsPrefetchNode> enqueuedNode;
+ rv = EnqueueURI(aURI, aReferrerInfo, aSource, getter_AddRefs(enqueuedNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NotifyLoadRequested(enqueuedNode);
+
+ // if there are no pages loading, kick off the request immediately
+ if ((!mStopCount && mHaveProcessed) || mAggressive) {
+ ProcessNextPrefetchURI();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::CancelPrefetchPreloadURI(nsIURI* aURI, nsINode* aSource) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ if (LOG_ENABLED()) {
+ LOG(("CancelPrefetchURI [%s]\n", aURI->GetSpecOrDefault().get()));
+ }
+
+ //
+ // look in current prefetches
+ //
+ for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
+ bool equals;
+ if (NS_SUCCEEDED(mCurrentNodes[i]->mURI->Equals(aURI, &equals)) && equals) {
+ nsWeakPtr source = do_GetWeakReference(aSource);
+ if (mCurrentNodes[i]->mSources.IndexOf(source) !=
+ mCurrentNodes[i]->mSources.NoIndex) {
+ mCurrentNodes[i]->mSources.RemoveElement(source);
+ if (mCurrentNodes[i]->mSources.IsEmpty()) {
+ mCurrentNodes[i]->CancelChannel(NS_BINDING_ABORTED);
+ mCurrentNodes.RemoveElementAt(i);
+ }
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ //
+ // look into the prefetch queue
+ //
+ for (std::deque<RefPtr<nsPrefetchNode>>::iterator nodeIt =
+ mPrefetchQueue.begin();
+ nodeIt != mPrefetchQueue.end(); nodeIt++) {
+ bool equals;
+ RefPtr<nsPrefetchNode> node = nodeIt->get();
+ if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) {
+ nsWeakPtr source = do_GetWeakReference(aSource);
+ if (node->mSources.IndexOf(source) != node->mSources.NoIndex) {
+#ifdef DEBUG
+ int32_t inx = node->mSources.IndexOf(source);
+ nsCOMPtr<nsINode> domNode =
+ do_QueryReferent(node->mSources.ElementAt(inx));
+ MOZ_ASSERT(domNode);
+#endif
+
+ node->mSources.RemoveElement(source);
+ if (node->mSources.IsEmpty()) {
+ mPrefetchQueue.erase(nodeIt);
+ }
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // not found!
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::PreloadURI(nsIURI* aURI, nsIReferrerInfo* aReferrerInfo,
+ nsINode* aSource,
+ nsContentPolicyType aPolicyType) {
+ return Preload(aURI, aReferrerInfo, aSource, aPolicyType);
+}
+
+NS_IMETHODIMP
+nsPrefetchService::PrefetchURI(nsIURI* aURI, nsIReferrerInfo* aReferrerInfo,
+ nsINode* aSource, bool aExplicit) {
+ return Prefetch(aURI, aReferrerInfo, aSource, aExplicit);
+}
+
+NS_IMETHODIMP
+nsPrefetchService::HasMoreElements(bool* aHasMore) {
+ *aHasMore = (mCurrentNodes.Length() || !mPrefetchQueue.empty());
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService::nsIWebProgressListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsPrefetchService::OnProgressChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest,
+ int32_t curSelfProgress,
+ int32_t maxSelfProgress,
+ int32_t curTotalProgress,
+ int32_t maxTotalProgress) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t progressStateFlags,
+ nsresult aStatus) {
+ if (progressStateFlags & STATE_IS_DOCUMENT) {
+ if (progressStateFlags & STATE_STOP)
+ StartPrefetching();
+ else if (progressStateFlags & STATE_START)
+ StopPrefetching();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsIURI* location,
+ uint32_t aFlags) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsresult aStatus,
+ const char16_t* aMessage) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aState) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aEvent) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsPrefetchService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ LOG(("nsPrefetchService::Observe [topic=%s]\n", aTopic));
+
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ StopAll();
+ mPrefetchDisabled = true;
+ } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ const nsCString converted = NS_ConvertUTF16toUTF8(aData);
+ const char* pref = converted.get();
+ if (!strcmp(pref, PREFETCH_PREF)) {
+ if (Preferences::GetBool(PREFETCH_PREF, false)) {
+ if (mPrefetchDisabled) {
+ LOG(("enabling prefetching\n"));
+ mPrefetchDisabled = false;
+ AddProgressListener();
+ }
+ } else {
+ if (!mPrefetchDisabled) {
+ LOG(("disabling prefetching\n"));
+ StopCurrentPrefetchsPreloads(false);
+ mPrefetchDisabled = true;
+ RemoveProgressListener();
+ }
+ }
+ } else if (!strcmp(pref, PARALLELISM_PREF)) {
+ mMaxParallelism = Preferences::GetInt(PARALLELISM_PREF, mMaxParallelism);
+ if (mMaxParallelism < 1) {
+ mMaxParallelism = 1;
+ }
+ // If our parallelism has increased, go ahead and kick off enough
+ // prefetches to fill up our allowance. If we're now over our
+ // allowance, we'll just silently let some of them finish to get
+ // back below our limit.
+ while (((!mStopCount && mHaveProcessed) || mAggressive) &&
+ !mPrefetchQueue.empty() &&
+ mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
+ ProcessNextPrefetchURI();
+ }
+ } else if (!strcmp(pref, AGGRESSIVE_PREF)) {
+ mAggressive = Preferences::GetBool(AGGRESSIVE_PREF, false);
+ // in aggressive mode, start prefetching immediately
+ if (mAggressive) {
+ while (mStopCount && !mPrefetchQueue.empty() &&
+ mCurrentNodes.Length() <
+ static_cast<uint32_t>(mMaxParallelism)) {
+ ProcessNextPrefetchURI();
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+// vim: ts=4 sw=2 expandtab
diff --git a/uriloader/prefetch/nsPrefetchService.h b/uriloader/prefetch/nsPrefetchService.h
new file mode 100644
index 0000000000..7b931ba52c
--- /dev/null
+++ b/uriloader/prefetch/nsPrefetchService.h
@@ -0,0 +1,130 @@
+/* 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 nsPrefetchService_h__
+#define nsPrefetchService_h__
+
+#include "nsIObserver.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIChannelEventSink.h"
+#include "nsIPrefetchService.h"
+#include "nsIRedirectResultListener.h"
+#include "nsIWebProgressListener.h"
+#include "nsIStreamListener.h"
+#include "nsIChannel.h"
+#include "nsIURI.h"
+#include "nsWeakReference.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include <deque>
+
+class nsPrefetchService;
+class nsPrefetchNode;
+class nsIReferrerInfo;
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService
+//-----------------------------------------------------------------------------
+
+class nsPrefetchService final : public nsIPrefetchService,
+ public nsIWebProgressListener,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPREFETCHSERVICE
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIOBSERVER
+
+ nsPrefetchService();
+
+ nsresult Init();
+ void RemoveNodeAndMaybeStartNextPrefetchURI(nsPrefetchNode* aFinished);
+ void ProcessNextPrefetchURI();
+
+ void NotifyLoadRequested(nsPrefetchNode* node);
+ void NotifyLoadCompleted(nsPrefetchNode* node);
+ void DispatchEvent(nsPrefetchNode* node, bool aSuccess);
+
+ private:
+ ~nsPrefetchService();
+
+ nsresult Prefetch(nsIURI* aURI, nsIReferrerInfo* aReferrerInfo,
+ nsINode* aSource, bool aExplicit);
+
+ nsresult Preload(nsIURI* aURI, nsIReferrerInfo* aReferrerInfo,
+ nsINode* aSource, nsContentPolicyType aPolicyType);
+
+ void AddProgressListener();
+ void RemoveProgressListener();
+ nsresult EnqueueURI(nsIURI* aURI, nsIReferrerInfo* aReferrerInfo,
+ nsINode* aSource, nsPrefetchNode** node);
+ void EmptyPrefetchQueue();
+
+ void StartPrefetching();
+ void StopPrefetching();
+ void StopCurrentPrefetchsPreloads(bool aPreload);
+ void StopAll();
+ nsresult CheckURIScheme(nsIURI* aURI, nsIReferrerInfo* aReferrerInfo);
+
+ std::deque<RefPtr<nsPrefetchNode>> mPrefetchQueue;
+ nsTArray<RefPtr<nsPrefetchNode>> mCurrentNodes;
+ int32_t mMaxParallelism;
+ int32_t mStopCount;
+ bool mHaveProcessed;
+ bool mPrefetchDisabled;
+
+ // In usual case prefetch does not start until all normal loads are done.
+ // Aggressive mode ignores normal loads and just start prefetch ASAP.
+ // It's mainly for testing purpose and discoraged for normal use;
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=1281415 for details.
+ bool mAggressive;
+};
+
+//-----------------------------------------------------------------------------
+// nsPreFetchingNode
+//-----------------------------------------------------------------------------
+
+class nsPrefetchNode final : public nsIStreamListener,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink,
+ public nsIRedirectResultListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIREDIRECTRESULTLISTENER
+
+ nsPrefetchNode(nsPrefetchService* aPrefetchService, nsIURI* aURI,
+ nsIReferrerInfo* aReferrerInfo, nsINode* aSource,
+ nsContentPolicyType aPolicyType, bool aPreload);
+
+ nsresult OpenChannel();
+ nsresult CancelChannel(nsresult error);
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+ nsTArray<nsWeakPtr> mSources;
+
+ // The policy type to be used for fetching the resource.
+ nsContentPolicyType mPolicyType;
+ // nsPrefetchNode is used for prefetching and preloading resource.
+ // mPreload is true if a resource is preloaded. Preloads and
+ // prefetches are fetched in different phases (during load and
+ // after a page load), therefore we need to distinguish them.
+ bool mPreload;
+
+ private:
+ ~nsPrefetchNode() {}
+
+ RefPtr<nsPrefetchService> mService;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ int64_t mBytesRead;
+ bool mShouldFireLoadEvent;
+};
+
+#endif // !nsPrefetchService_h__