summaryrefslogtreecommitdiffstats
path: root/uriloader/prefetch
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /uriloader/prefetch
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'uriloader/prefetch')
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateChild.cpp528
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateChild.h94
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateGlue.cpp228
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateGlue.h80
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateParent.cpp294
-rw-r--r--uriloader/prefetch/OfflineCacheUpdateParent.h65
-rw-r--r--uriloader/prefetch/POfflineCacheUpdate.ipdl28
-rw-r--r--uriloader/prefetch/moz.build45
-rw-r--r--uriloader/prefetch/nsCPrefetchService.h52
-rw-r--r--uriloader/prefetch/nsIOfflineCacheUpdate.idl292
-rw-r--r--uriloader/prefetch/nsIPrefetchService.idl37
-rw-r--r--uriloader/prefetch/nsOfflineCacheUpdate.cpp2471
-rw-r--r--uriloader/prefetch/nsOfflineCacheUpdate.h381
-rw-r--r--uriloader/prefetch/nsOfflineCacheUpdateService.cpp736
-rw-r--r--uriloader/prefetch/nsPrefetchService.cpp931
-rw-r--r--uriloader/prefetch/nsPrefetchService.h121
16 files changed, 6383 insertions, 0 deletions
diff --git a/uriloader/prefetch/OfflineCacheUpdateChild.cpp b/uriloader/prefetch/OfflineCacheUpdateChild.cpp
new file mode 100644
index 000000000..555508c37
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateChild.cpp
@@ -0,0 +1,528 @@
+/* -*- 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 "BackgroundUtils.h"
+#include "OfflineCacheUpdateChild.h"
+#include "nsOfflineCacheUpdate.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoCommon.h"
+
+#include "nsIApplicationCacheContainer.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIApplicationCacheService.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMOfflineResourceList.h"
+#include "nsIDocument.h"
+#include "nsIObserverService.h"
+#include "nsIURL.h"
+#include "nsITabChild.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Logging.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+
+using namespace mozilla::ipc;
+using namespace mozilla::net;
+using mozilla::dom::TabChild;
+using mozilla::dom::ContentChild;
+
+//
+// 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(nsIDOMDocument *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).
+ nsCOMPtr<nsIDocument> document = do_QueryInterface(aDocument);
+ if (!document)
+ return;
+
+ nsIChannel* channel = document->GetChannel();
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(channel);
+ if (!appCacheChannel)
+ return;
+
+ bool loadedFromAppCache;
+ appCacheChannel->GetLoadedFromApplicationCache(&loadedFromAppCache);
+ if (loadedFromAppCache)
+ return;
+
+ mDocument = aDocument;
+}
+
+nsresult
+OfflineCacheUpdateChild::AssociateDocument(nsIDOMDocument *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<nsIApplicationCacheContainer> container =
+ do_QueryInterface(aDocument);
+ if (!container)
+ return NS_OK;
+
+ nsCOMPtr<nsIApplicationCache> existingCache;
+ nsresult rv = container->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 = container->SetApplicationCache(aApplicationCache);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// OfflineCacheUpdateChild::nsIOfflineCacheUpdate
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::Init(nsIURI *aManifestURI,
+ nsIURI *aDocumentURI,
+ nsIPrincipal *aLoadingPrincipal,
+ nsIDOMDocument *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.
+ bool match;
+ rv = aManifestURI->SchemeIs("http", &match);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!match) {
+ rv = aManifestURI->SchemeIs("https", &match);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!match)
+ 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)
+{
+ NS_NOTREACHED("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)
+{
+ NS_NOTREACHED("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 = nsIDOMOfflineResourceList::CHECKING;
+ return NS_OK;
+ case STATE_DOWNLOADING :
+ *aStatus = nsIDOMOfflineResourceList::DOWNLOADING;
+ return NS_OK;
+ default :
+ *aStatus = nsIDOMOfflineResourceList::IDLE;
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateChild::GetPartial(bool *aPartial)
+{
+ *aPartial = false;
+ 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) {
+ nsCOMPtr<nsIWeakReference> 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 = mWindow.forget();
+ nsCOMPtr<nsIDocShell >docshell = window->GetDocShell();
+ if (!docshell) {
+ NS_WARNING("doc shell tree item is null");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsITabChild> tabchild = docshell->GetTabChild();
+ // because owner implements nsITabChild, we can assume that it is
+ // the one and only TabChild.
+ TabChild* child = tabchild ? static_cast<TabChild*>(tabchild.get()) : nullptr;
+
+ if (MissingRequiredTabChild(child, "offlinecacheupdate")) {
+ return NS_ERROR_FAILURE;
+ }
+
+ URIParams manifestURI, documentURI;
+ SerializeURI(mManifestURI, manifestURI);
+ SerializeURI(mDocumentURI, documentURI);
+
+ 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;
+
+ // Need to addref ourself here, because the IPC stack doesn't hold
+ // a reference to us. Will be released in RecvFinish() that identifies
+ // the work has been done.
+ ContentChild::GetSingleton()->SendPOfflineCacheUpdateConstructor(
+ this, manifestURI, documentURI, loadingPrincipalInfo,
+ stickDocument);
+
+ // ContentChild::DeallocPOfflineCacheUpdate will release this.
+ NS_ADDREF_THIS();
+
+ return NS_OK;
+}
+
+bool
+OfflineCacheUpdateChild::RecvAssociateDocuments(const nsCString &cacheGroupId,
+ const nsCString &cacheClientId)
+{
+ LOG(("OfflineCacheUpdateChild::RecvAssociateDocuments [%p, cache=%s]", this, cacheClientId.get()));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIApplicationCache> cache =
+ do_CreateInstance(NS_APPLICATIONCACHE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return true;
+
+ 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 true;
+}
+
+bool
+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 true;
+}
+
+bool
+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().
+ // TabChild::DeallocPOfflineCacheUpdate will call Release.
+ OfflineCacheUpdateChild::Send__delete__(this);
+
+ return true;
+}
+
+} // namespace docshell
+} // namespace mozilla
diff --git a/uriloader/prefetch/OfflineCacheUpdateChild.h b/uriloader/prefetch/OfflineCacheUpdateChild.h
new file mode 100644
index 000000000..89d1e6f1f
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateChild.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOfflineCacheUpdateChild_h
+#define nsOfflineCacheUpdateChild_h
+
+#include "mozilla/docshell/POfflineCacheUpdateChild.h"
+#include "nsIOfflineCacheUpdate.h"
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMDocument.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace docshell {
+
+class OfflineCacheUpdateChild : public nsIOfflineCacheUpdate
+ , public POfflineCacheUpdateChild
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOFFLINECACHEUPDATE
+
+ virtual bool
+ RecvNotifyStateEvent(const uint32_t& stateEvent,
+ const uint64_t& byteProgress) override;
+
+ virtual bool
+ RecvAssociateDocuments(
+ const nsCString& cacheGroupId,
+ const nsCString& cacheClientId) override;
+
+ virtual bool
+ RecvFinish(const bool& succeeded,
+ const bool& isUpgrade) override;
+
+ explicit OfflineCacheUpdateChild(nsPIDOMWindowInner* aWindow);
+
+ void SetDocument(nsIDOMDocument *aDocument);
+
+private:
+ ~OfflineCacheUpdateChild();
+
+ nsresult AssociateDocument(nsIDOMDocument *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<nsIObserverService> mObserverService;
+
+ /* Clients watching this update for changes */
+ nsCOMArray<nsIWeakReference> mWeakObservers;
+ nsCOMArray<nsIOfflineCacheUpdateObserver> mObservers;
+
+ /* Document that requested this update */
+ nsCOMPtr<nsIDOMDocument> 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 000000000..71ca986ff
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateGlue.cpp
@@ -0,0 +1,228 @@
+/* -*- 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 "OfflineCacheUpdateGlue.h"
+#include "nsOfflineCacheUpdate.h"
+#include "mozilla/Services.h"
+
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIApplicationCacheContainer.h"
+#include "nsIChannel.h"
+#include "nsIDocument.h"
+#include "mozilla/Logging.h"
+
+//
+// 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()));
+ }
+
+ 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,
+ nsIDOMDocument *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;
+ }
+
+ return mUpdate->Init(aManifestURI, aDocumentURI, aLoadingPrincipal, nullptr,
+ aCustomProfileDir);
+}
+
+void
+OfflineCacheUpdateGlue::SetDocument(nsIDOMDocument *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).
+ nsCOMPtr<nsIDocument> document = do_QueryInterface(aDocument);
+ if (!document)
+ return;
+
+ nsIChannel* channel = document->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.
+ nsCOMPtr<nsIApplicationCacheContainer> container =
+ do_QueryInterface(mDocument);
+ if (!container)
+ return NS_OK;
+
+ nsCOMPtr<nsIApplicationCache> existingCache;
+ nsresult rv = container->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 = container->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 000000000..92201ec82
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateGlue.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOfflineCacheUpdateGlue_h
+#define nsOfflineCacheUpdateGlue_h
+
+#include "nsIOfflineCacheUpdate.h"
+
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+
+class nsOfflineCacheUpdate;
+
+namespace mozilla {
+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 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) override { return !_to ? NS_ERROR_NULL_POINTER : _to->InitPartial(aManifestURI, aClientID, aDocumentURI, aLoadingPrincipal); } \
+ 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,
+ nsIDOMDocument *aDocument,
+ nsIFile *aCustomProfileDir) override;
+
+ NS_DECL_NSIOFFLINECACHEUPDATEOBSERVER
+
+ OfflineCacheUpdateGlue();
+
+ void SetDocument(nsIDOMDocument *aDocument);
+
+private:
+ ~OfflineCacheUpdateGlue();
+
+ RefPtr<nsOfflineCacheUpdate> mUpdate;
+ bool mCoalesced;
+
+ /* Document that requested this update */
+ nsCOMPtr<nsIDOMDocument> mDocument;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+};
+
+} // namespace docshell
+} // namespace mozilla
+
+#endif
diff --git a/uriloader/prefetch/OfflineCacheUpdateParent.cpp b/uriloader/prefetch/OfflineCacheUpdateParent.cpp
new file mode 100644
index 000000000..0381ec3f6
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateParent.cpp
@@ -0,0 +1,294 @@
+/* -*- 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 "OfflineCacheUpdateParent.h"
+
+#include "BackgroundUtils.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/Unused.h"
+#include "nsContentUtils.h"
+#include "nsOfflineCacheUpdate.h"
+#include "nsIApplicationCache.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla::ipc;
+using mozilla::BasePrincipal;
+using mozilla::DocShellOriginAttributes;
+using mozilla::PrincipalOriginAttributes;
+using mozilla::dom::TabParent;
+
+//
+// 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(const URIParams& aManifestURI,
+ const URIParams& aDocumentURI,
+ const PrincipalInfo& aLoadingPrincipalInfo,
+ const bool& stickDocument)
+{
+ LOG(("OfflineCacheUpdateParent::RecvSchedule [%p]", this));
+
+ nsresult rv;
+
+ RefPtr<nsOfflineCacheUpdate> update;
+ nsCOMPtr<nsIURI> manifestURI = DeserializeURI(aManifestURI);
+ if (!manifestURI)
+ return NS_ERROR_FAILURE;
+
+ mLoadingPrincipal = PrincipalInfoToPrincipal(aLoadingPrincipalInfo, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsOfflineCacheUpdateService* service =
+ nsOfflineCacheUpdateService::EnsureService();
+ if (!service)
+ return NS_ERROR_FAILURE;
+
+ bool offlinePermissionAllowed = false;
+
+ rv = service->OfflineAppAllowed(
+ mLoadingPrincipal, nullptr, &offlinePermissionAllowed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!offlinePermissionAllowed)
+ return NS_ERROR_DOM_SECURITY_ERR;
+
+ nsCOMPtr<nsIURI> documentURI = DeserializeURI(aDocumentURI);
+ if (!documentURI)
+ return NS_ERROR_FAILURE;
+
+ if (!NS_SecurityCompareURIs(manifestURI, documentURI, false))
+ return NS_ERROR_DOM_SECURITY_ERR;
+
+ nsAutoCString originSuffix;
+ rv = mLoadingPrincipal->GetOriginSuffix(originSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ service->FindUpdate(manifestURI,
+ originSuffix,
+ nullptr,
+ getter_AddRefs(update));
+ if (!update) {
+ update = new nsOfflineCacheUpdate();
+
+ // Leave aDocument argument null. Only glues and children keep
+ // document instances.
+ rv = update->Init(manifestURI, documentURI, mLoadingPrincipal, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 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) {
+ nsCOMPtr<nsIURI> stickURI;
+ documentURI->Clone(getter_AddRefs(stickURI));
+ update->StickDocument(stickURI);
+ }
+
+ 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(nsIDOMElement** aElement)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetNestedFrameId(uint64_t* aId)
+{
+ 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::GetIsInIsolatedMozBrowserElement(bool *aIsInIsolatedMozBrowserElement)
+{
+ NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_UNEXPECTED);
+ return mLoadingPrincipal->GetIsInIsolatedMozBrowserElement(aIsInIsolatedMozBrowserElement);
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetAppId(uint32_t *aAppId)
+{
+ NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_UNEXPECTED);
+ return mLoadingPrincipal->GetAppId(aAppId);
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::GetOriginAttributes(JS::MutableHandleValue aAttrs)
+{
+ NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_UNEXPECTED);
+
+ JSContext* cx = nsContentUtils::GetCurrentJSContext();
+ MOZ_ASSERT(cx);
+
+ nsresult rv = mLoadingPrincipal->GetOriginAttributes(cx, aAttrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OfflineCacheUpdateParent::IsTrackingProtectionOn(bool* aIsTrackingProtectionOn)
+{
+ 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 000000000..f6dbc1cb2
--- /dev/null
+++ b/uriloader/prefetch/OfflineCacheUpdateParent.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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 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(const URIParams& manifestURI,
+ const URIParams& documentURI,
+ const PrincipalInfo& loadingPrincipalInfo,
+ const bool& stickDocument);
+
+ 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 000000000..e62475252
--- /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 {
+
+//-------------------------------------------------------------------
+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 000000000..348d57a92
--- /dev/null
+++ b/uriloader/prefetch/moz.build
@@ -0,0 +1,45 @@
+# -*- 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/.
+
+XPIDL_SOURCES += [
+ 'nsIOfflineCacheUpdate.idl',
+ 'nsIPrefetchService.idl',
+]
+
+XPIDL_MODULE = 'prefetch'
+
+EXPORTS += [
+ 'nsCPrefetchService.h',
+]
+
+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['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/uriloader/prefetch/nsCPrefetchService.h b/uriloader/prefetch/nsCPrefetchService.h
new file mode 100644
index 000000000..d74d89fe7
--- /dev/null
+++ b/uriloader/prefetch/nsCPrefetchService.h
@@ -0,0 +1,52 @@
+/* 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 nsCPrefetchService_h__
+#define nsCPrefetchService_h__
+
+#include "nsIPrefetchService.h"
+
+/**
+ * nsPrefetchService : nsIPrefetchService
+ */
+#define NS_PREFETCHSERVICE_CONTRACTID \
+ "@mozilla.org/prefetch-service;1"
+#define NS_PREFETCHSERVICE_CID \
+{ /* 6b8bdffc-3394-417d-be83-a81b7c0f63bf */ \
+ 0x6b8bdffc, \
+ 0x3394, \
+ 0x417d, \
+ {0xbe, 0x83, 0xa8, 0x1b, 0x7c, 0x0f, 0x63, 0xbf} \
+}
+
+/**
+ * nsOfflineCacheUpdateService : nsIOfflineCacheUpdateService
+ */
+
+#define NS_OFFLINECACHEUPDATESERVICE_CONTRACTID \
+ "@mozilla.org/offlinecacheupdate-service;1"
+#define NS_OFFLINECACHEUPDATESERVICE_CID \
+{ /* ec06f3fc-70db-4ecd-94e0-a6e91ca44d8a */ \
+ 0xec06f3fc, \
+ 0x70db, \
+ 0x4ecd , \
+ {0x94, 0xe0, 0xa6, 0xe9, 0x1c, 0xa4, 0x4d, 0x8a} \
+}
+
+/**
+ * nsOfflineCacheUpdate : nsIOfflineCacheUpdate
+ */
+
+#define NS_OFFLINECACHEUPDATE_CONTRACTID \
+ "@mozilla.org/offlinecacheupdate;1"
+#define NS_OFFLINECACHEUPDATE_CID \
+{ /* e56f5e01-c7cc-4675-a9d7-b8f1e4127295 */ \
+ 0xe56f5e01, \
+ 0xc7cc, \
+ 0x4675, \
+ {0xa9, 0xd7, 0xb8, 0xf1, 0xe4, 0x12, 0x72, 0x95} \
+}
+
+
+#endif // !nsCPrefetchService_h__
diff --git a/uriloader/prefetch/nsIOfflineCacheUpdate.idl b/uriloader/prefetch/nsIOfflineCacheUpdate.idl
new file mode 100644
index 000000000..1308a8de2
--- /dev/null
+++ b/uriloader/prefetch/nsIOfflineCacheUpdate.idl
@@ -0,0 +1,292 @@
+/* -*- 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 nsIDOMNode;
+interface nsIDOMDocument;
+interface nsIOfflineCacheUpdate;
+interface nsIPrincipal;
+interface nsIPrefBranch;
+interface nsIApplicationCache;
+interface nsIFile;
+interface nsIObserver;
+
+[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 nsIDOMOfflineResourceList.
+ */
+ 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;
+
+ /**
+ * 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 nsIDOMDocument 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.
+ */
+ void initPartial(in nsIURI aManifestURI, in ACString aClientID,
+ in nsIURI aDocumentURI, in nsIPrincipal aPrincipal);
+
+ /**
+ * 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,
+ 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+inIsolatedMozBrowser 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 nsIDOMDocument 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.
+ * @param aPrefBranch
+ * The pref branch to use to check the
+ * offline-apps.allow_by_default pref. If not specified,
+ * the pref service will be used.
+ */
+ boolean offlineAppAllowed(in nsIPrincipal aPrincipal,
+ in nsIPrefBranch aPrefBranch);
+
+ /**
+ * Checks whether a document at the given URI should have access
+ * to the offline cache.
+ * @param aURI
+ * The URI to check
+ * @param aPrefBranch
+ * The pref branch to use to check the
+ * offline-apps.allow_by_default pref. If not specified,
+ * the pref service will be used.
+ */
+ boolean offlineAppAllowedForURI(in nsIURI aURI,
+ in nsIPrefBranch aPrefBranch);
+
+ /**
+ * 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 000000000..198320dd2
--- /dev/null
+++ b/uriloader/prefetch/nsIPrefetchService.idl
@@ -0,0 +1,37 @@
+/* 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 nsIURI;
+interface nsIDOMNode;
+interface nsISimpleEnumerator;
+
+[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 aReferrerURI the URI of the referring page
+ * @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 nsIURI aReferrerURI,
+ in nsIDOMNode aSource,
+ in boolean aExplicit);
+
+ /**
+ * Find out if there are any prefetches running or queued
+ */
+ boolean hasMoreElements();
+
+ /**
+ * Cancel prefetch
+ */
+ void cancelPrefetchURI(in nsIURI aURI, in nsIDOMNode aSource);
+};
diff --git a/uriloader/prefetch/nsOfflineCacheUpdate.cpp b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
new file mode 100644
index 000000000..4b6cd4d0c
--- /dev/null
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
@@ -0,0 +1,2471 @@
+/* -*- 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 "nsOfflineCacheUpdate.h"
+
+#include "nsCPrefetchService.h"
+#include "nsCURILoader.h"
+#include "nsIApplicationCacheContainer.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIApplicationCacheService.h"
+#include "nsICachingChannel.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Element.h"
+#include "nsIDocumentLoader.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMOfflineResourceList.h"
+#include "nsIDocument.h"
+#include "nsIObserverService.h"
+#include "nsIURL.h"
+#include "nsIWebProgress.h"
+#include "nsICryptoHash.h"
+#include "nsICacheEntry.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 "nsIConsoleService.h"
+#include "mozilla/Logging.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Attributes.h"
+#include "nsContentUtils.h"
+#include "nsIPrincipal.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)
+
+class AutoFreeArray {
+public:
+ AutoFreeArray(uint32_t count, char **values)
+ : mCount(count), mValues(values) {};
+ ~AutoFreeArray() { NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mCount, mValues); }
+private:
+ uint32_t mCount;
+ char **mValues;
+};
+
+namespace {
+
+nsresult
+DropReferenceFromURL(nsIURI * aURI)
+{
+ // XXXdholbert If this SetRef fails, callers of this method probably
+ // want to call aURI->CloneIgnoringRef() and use the result of that.
+ return aURI->SetRef(EmptyCString());
+}
+
+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,
+ 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) {
+ httpChannel->SetReferrer(mReferrerURI);
+ httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
+ NS_LITERAL_CSTRING("offline-resource"),
+ false);
+ }
+
+ return mChannel->AsyncOpen2(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,
+ nsISupports *aContext)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsManifestCheck::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ 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,
+ nsISupports *aContext,
+ 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_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ 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) {
+ httpChannel->SetReferrer(mReferrerURI);
+ httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
+ NS_LITERAL_CSTRING("offline-resource"),
+ false);
+ }
+
+ rv = mChannel->AsyncOpen2(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,
+ nsISupports *aContext)
+{
+ mState = LoadStatus::RECEIVING;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ 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=%llu]\n",
+ bytesRead, aOffset));
+
+ mUpdate->OnByteProgress(bytesRead);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateItem::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ if (LOG_ENABLED()) {
+ LOG(("%p: Done fetching offline item %s [status=%x]\n",
+ this, mURI->GetSpecOrDefault().get(), 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);
+
+ bool match;
+ if (NS_FAILED(newURI->SchemeIs(oldScheme.get(), &match)) || !match) {
+ 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);
+
+ httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
+ NS_LITERAL_CSTRING("offline-resource"),
+ false);
+
+ 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%08x", 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=%08x", 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=%08x", 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%08x", 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 =
+ do_CreateInstance(NS_APPLICATIONCACHENAMESPACE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ns->Init(namespaceType, namespaceSpec, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mNamespaces->AppendElement(ns, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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 nsCSubstring &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 nsCSubstring &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.
+ bool match;
+ if (NS_FAILED(mURI->SchemeIs(scheme.get(), &match)) || !match)
+ 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;
+
+ // 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,
+ EmptyCString(), EmptyCString());
+ break;
+ }
+
+ nsCOMPtr<nsIURI> bypassURI;
+ rv = NS_NewURI(getter_AddRefs(bypassURI), line, nullptr, mURI);
+ if (NS_FAILED(rv))
+ break;
+
+ nsAutoCString scheme;
+ bypassURI->GetScheme(scheme);
+ bool equals;
+ if (NS_FAILED(mURI->SchemeIs(scheme.get(), &equals)) || !equals)
+ break;
+ if (NS_FAILED(DropReferenceFromURL(bypassURI)))
+ break;
+ nsCString spec;
+ if (NS_FAILED(bypassURI->GetAsciiSpec(spec)))
+ break;
+
+ AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_BYPASS,
+ spec, EmptyCString());
+ 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=%08x", 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,
+ nsISupports *aContext)
+{
+ 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, aContext);
+}
+
+NS_IMETHODIMP
+nsOfflineManifestItem::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ 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=%u]\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,
+ nsISupports *aContext,
+ 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, aContext, 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 = aURI->CloneIgnoringRef(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.
+ bool match;
+ rv = aManifestURI->SchemeIs("http", &match);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!match) {
+ rv = aManifestURI->SchemeIs("https", &match);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!match)
+ 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,
+ nsIDOMDocument *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 (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,
+ nullptr,
+ &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,
+ nullptr,
+ &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)
+{
+ 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,
+ nullptr,
+ &mPinned);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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 (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 algoritm 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);
+
+ // 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(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;
+ }
+
+ uint32_t count = 0;
+ char **keys = nullptr;
+ nsresult rv = mPreviousApplicationCache->GatherEntries(aType,
+ &count, &keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AutoFreeArray autoFree(count, keys);
+
+ for (uint32_t i = 0; i < count; i++) {
+ if (namespaceFilter) {
+ bool found = false;
+ for (uint32_t j = 0; j < namespaceFilter->Length() && !found; j++) {
+ found = StringBeginsWith(nsDependentCString(keys[i]),
+ namespaceFilter->ElementAt(j));
+ }
+
+ if (!found)
+ continue;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), keys[i]))) {
+ 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=%d]",
+ 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 nsCSubstring &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);
+ 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,
+ uint32_t count, const char * const *groups)
+{
+ nsresult rv;
+ unsigned int i;
+
+ for (i = 0; i < count; i++) {
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), groups[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsDependentCString group_name(groups[i]);
+ nsCOMPtr<nsIApplicationCache> cache;
+ rv = cacheService->GetActiveCache(group_name, 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,
+ nullptr,
+ &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);
+
+ uint32_t count;
+ char **groups;
+ rv = cacheService->GetGroupsTimeOrdered(&count, &groups);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = EvictOneOfCacheGroups(cacheService, count, groups);
+
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, groups);
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// 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 = nsIDOMOfflineResourceList::CHECKING;
+ return NS_OK;
+ case STATE_DOWNLOADING :
+ *aStatus = nsIDOMOfflineResourceList::DOWNLOADING;
+ return NS_OK;
+ default :
+ *aStatus = nsIDOMOfflineResourceList::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::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);
+
+ bool match;
+ if (NS_FAILED(mManifestURI->SchemeIs(scheme.get(), &match)) || !match)
+ 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) {
+ nsCOMPtr<nsIWeakReference> 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;
+}
+
+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 000000000..4ccba4135
--- /dev/null
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.h
@@ -0,0 +1,381 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOfflineCacheUpdate_h__
+#define nsOfflineCacheUpdate_h__
+
+#include "nsIOfflineCacheUpdate.h"
+
+#include "nsAutoPtr.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIChannelEventSink.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMNode.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIMutableArray.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIApplicationCache.h"
+#include "nsIRequestObserver.h"
+#include "nsIRunnable.h"
+#include "nsIStreamListener.h"
+#include "nsIURI.h"
+#include "nsIWebProgressListener.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"
+
+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<nsOfflineCacheUpdateOwner>
+{
+public:
+ MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsOfflineCacheUpdateOwner)
+ 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 nsCSubstring &groupID);
+ bool IsForProfile(nsIFile* aCustomProfileDir);
+
+ virtual nsresult UpdateFinished(nsOfflineCacheUpdate *aUpdate) override;
+
+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<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,
+ nsIDOMDocument *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();
+
+ /** Addrefs and returns the singleton nsOfflineCacheUpdateService. */
+ static nsOfflineCacheUpdateService *GetInstance();
+
+ static nsresult OfflineAppPinnedForURI(nsIURI *aDocumentURI,
+ nsIPrefBranch *aPrefBranch,
+ bool *aPinned);
+
+ static nsTHashtable<nsCStringHashKey>* AllowedDomains();
+
+private:
+ ~nsOfflineCacheUpdateService();
+
+ nsresult ProcessNextUpdate();
+
+ nsTArray<RefPtr<nsOfflineCacheUpdate> > mUpdates;
+ static nsTHashtable<nsCStringHashKey>* mAllowedDomains;
+
+ bool mDisabled;
+ bool mUpdateRunning;
+ bool mLowFreeSpace;
+};
+
+#endif
diff --git a/uriloader/prefetch/nsOfflineCacheUpdateService.cpp b/uriloader/prefetch/nsOfflineCacheUpdateService.cpp
new file mode 100644
index 000000000..adb3fd516
--- /dev/null
+++ b/uriloader/prefetch/nsOfflineCacheUpdateService.cpp
@@ -0,0 +1,736 @@
+/* -*- 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 "OfflineCacheUpdateChild.h"
+#include "OfflineCacheUpdateParent.h"
+#include "nsXULAppAPI.h"
+#include "OfflineCacheUpdateGlue.h"
+#include "nsOfflineCacheUpdate.h"
+
+#include "nsCPrefetchService.h"
+#include "nsCURILoader.h"
+#include "nsIApplicationCacheContainer.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIApplicationCacheService.h"
+#include "nsICachingChannel.h"
+#include "nsIContent.h"
+#include "nsIDocShell.h"
+#include "nsIDocumentLoader.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMOfflineResourceList.h"
+#include "nsIDocument.h"
+#include "nsIObserverService.h"
+#include "nsIURL.h"
+#include "nsIWebProgress.h"
+#include "nsIWebNavigation.h"
+#include "nsICryptoHash.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrincipal.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Logging.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Unused.h"
+#include "nsIDiskSpaceWatcher.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.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;
+static bool sAllowOfflineCache = true;
+
+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,
+ nsIDOMDocument *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;
+ nsCOMPtr<nsIWeakReference> 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)
+{
+ NS_NOTREACHED("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<nsIDOMDocument> 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<nsIDocument> progressDoc = outerWindow->GetDoc();
+ if (!progressDoc) return NS_OK;
+
+ if (!SameCOMIdentity(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)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCachePendingUpdate::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCachePendingUpdate::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t state)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheUpdateService,
+ nsIOfflineCacheUpdateService,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService <public>
+//-----------------------------------------------------------------------------
+
+nsOfflineCacheUpdateService::nsOfflineCacheUpdateService()
+ : mDisabled(false)
+ , mUpdateRunning(false)
+ , mLowFreeSpace(false)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ Preferences::AddBoolVarCache(&sAllowOfflineCache,
+ "browser.cache.offline.enable",
+ true);
+}
+
+nsOfflineCacheUpdateService::~nsOfflineCacheUpdateService()
+{
+ gOfflineCacheUpdateService = 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);
+
+ // Get the current status of the disk in terms of free space and observe
+ // low device storage notifications.
+ nsCOMPtr<nsIDiskSpaceWatcher> diskSpaceWatcherService =
+ do_GetService("@mozilla.org/toolkit/disk-space-watcher;1");
+ if (diskSpaceWatcherService) {
+ diskSpaceWatcherService->GetIsDiskFull(&mLowFreeSpace);
+ } else {
+ NS_WARNING("Could not get disk status from nsIDiskSpaceWatcher");
+ }
+
+ rv = observerService->AddObserver(this, "disk-space-watcher", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ gOfflineCacheUpdateService = this;
+
+ return NS_OK;
+}
+
+/* static */
+nsOfflineCacheUpdateService *
+nsOfflineCacheUpdateService::GetInstance()
+{
+ if (!gOfflineCacheUpdateService) {
+ gOfflineCacheUpdateService = new nsOfflineCacheUpdateService();
+ if (!gOfflineCacheUpdateService)
+ return nullptr;
+ NS_ADDREF(gOfflineCacheUpdateService);
+ nsresult rv = gOfflineCacheUpdateService->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(gOfflineCacheUpdateService);
+ return nullptr;
+ }
+ return gOfflineCacheUpdateService;
+ }
+
+ NS_ADDREF(gOfflineCacheUpdateService);
+
+ return gOfflineCacheUpdateService;
+}
+
+/* static */
+nsOfflineCacheUpdateService *
+nsOfflineCacheUpdateService::EnsureService()
+{
+ if (!gOfflineCacheUpdateService) {
+ // Make the service manager hold a long-lived reference to the service
+ nsCOMPtr<nsIOfflineCacheUpdateService> service =
+ do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID);
+ }
+
+ 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,
+ nsIDOMDocument *aDocument)
+{
+ LOG(("nsOfflineCacheUpdateService::ScheduleOnDocumentStop [%p, manifestURI=%p, documentURI=%p doc=%p]",
+ this, aManifestURI, aDocumentURI, aDocument));
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDocument);
+ nsCOMPtr<nsIWebProgress> progress = do_QueryInterface(doc->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=%d]",
+ this, mUpdates.Length()));
+
+ if (mDisabled)
+ return NS_ERROR_ABORT;
+
+ if (mUpdateRunning)
+ return NS_OK;
+
+ if (mUpdates.Length() > 0) {
+ mUpdateRunning = true;
+ // Canceling the update before Begin() call will make the update
+ // asynchronously finish with an error.
+ if (mLowFreeSpace) {
+ mUpdates[0]->Cancel();
+ }
+ 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,
+ nsIDOMDocument *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.
+ nsCOMPtr<nsIDOMOfflineResourceList> appCacheWindowObject =
+ 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;
+ }
+
+ if (!strcmp(aTopic, "disk-space-watcher")) {
+ if (NS_LITERAL_STRING("full").Equals(aData)) {
+ mLowFreeSpace = true;
+ for (uint32_t i = 0; i < mUpdates.Length(); i++) {
+ mUpdates[i]->Cancel();
+ }
+ } else if (NS_LITERAL_STRING("free").Equals(aData)) {
+ mLowFreeSpace = false;
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsOfflineCacheUpdateService::nsIOfflineCacheUpdateService
+//-----------------------------------------------------------------------------
+
+static nsresult
+OfflineAppPermForPrincipal(nsIPrincipal *aPrincipal,
+ nsIPrefBranch *aPrefBranch,
+ bool pinned,
+ bool *aAllowed)
+{
+ *aAllowed = false;
+
+ if (!sAllowOfflineCache) {
+ return NS_OK;
+ }
+
+ if (!aPrincipal)
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIURI> uri;
+ aPrincipal->GetURI(getter_AddRefs(uri));
+
+ if (!uri)
+ return NS_OK;
+
+ nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri);
+ if (!innerURI)
+ return NS_OK;
+
+ // only http and https applications can use offline APIs.
+ bool match;
+ nsresult rv = innerURI->SchemeIs("http", &match);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!match) {
+ rv = innerURI->SchemeIs("https", &match);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!match) {
+ return NS_OK;
+ }
+ }
+
+ nsAutoCString domain;
+ 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 char *permName = pinned ? "pin-app" : "offline-app";
+ permissionManager->TestExactPermissionFromPrincipal(aPrincipal, permName, &perm);
+
+ if (perm == nsIPermissionManager::ALLOW_ACTION ||
+ perm == nsIOfflineCacheUpdateService::ALLOW_NO_WARN) {
+ *aAllowed = true;
+ }
+
+ // offline-apps.allow_by_default is now effective at the cache selection
+ // algorithm code (nsContentSink).
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::OfflineAppAllowed(nsIPrincipal *aPrincipal,
+ nsIPrefBranch *aPrefBranch,
+ bool *aAllowed)
+{
+ return OfflineAppPermForPrincipal(aPrincipal, aPrefBranch, false, aAllowed);
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::OfflineAppAllowedForURI(nsIURI *aURI,
+ nsIPrefBranch *aPrefBranch,
+ bool *aAllowed)
+{
+ PrincipalOriginAttributes attrs;
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
+ return OfflineAppPermForPrincipal(principal, aPrefBranch, false, aAllowed);
+}
+
+nsresult
+nsOfflineCacheUpdateService::OfflineAppPinnedForURI(nsIURI *aDocumentURI,
+ nsIPrefBranch *aPrefBranch,
+ bool *aPinned)
+{
+ PrincipalOriginAttributes attrs;
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateCodebasePrincipal(aDocumentURI, attrs);
+ return OfflineAppPermForPrincipal(principal, aPrefBranch, true, aPinned);
+}
+
+NS_IMETHODIMP
+nsOfflineCacheUpdateService::AllowOfflineApp(nsIPrincipal *aPrincipal)
+{
+ nsresult rv;
+
+ if (!sAllowOfflineCache) {
+ 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", 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 000000000..bd2b10d30
--- /dev/null
+++ b/uriloader/prefetch/nsPrefetchService.cpp
@@ -0,0 +1,931 @@
+/* -*- 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 "nsICacheEntry.h"
+#include "nsIServiceManager.h"
+#include "nsICategoryManager.h"
+#include "nsIObserverService.h"
+#include "nsIWebProgress.h"
+#include "nsCURILoader.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIURL.h"
+#include "nsISimpleEnumerator.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsStreamUtils.h"
+#include "nsAutoPtr.h"
+#include "prtime.h"
+#include "mozilla/Logging.h"
+#include "plstr.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/dom/HTMLLinkElement.h"
+#include "nsIDOMNode.h"
+#include "nsINode.h"
+#include "nsIDocument.h"
+#include "nsContentUtils.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"
+
+//-----------------------------------------------------------------------------
+// helpers
+//-----------------------------------------------------------------------------
+
+static inline uint32_t
+PRTimeToSeconds(PRTime t_usec)
+{
+ PRTime usec_per_sec = PR_USEC_PER_SEC;
+ return uint32_t(t_usec /= usec_per_sec);
+}
+
+#define NowInSeconds() PRTimeToSeconds(PR_Now())
+
+//-----------------------------------------------------------------------------
+// nsPrefetchNode <public>
+//-----------------------------------------------------------------------------
+
+nsPrefetchNode::nsPrefetchNode(nsPrefetchService *aService,
+ nsIURI *aURI,
+ nsIURI *aReferrerURI,
+ nsIDOMNode *aSource)
+ : mURI(aURI)
+ , mReferrerURI(aReferrerURI)
+ , mService(aService)
+ , mChannel(nullptr)
+ , mBytesRead(0)
+ , mShouldFireLoadEvent(false)
+{
+ nsCOMPtr<nsIWeakReference> 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;
+ net::ReferrerPolicy referrerPolicy = net::RP_Unset;
+ if (source->IsHTMLElement(nsGkAtoms::link)) {
+ dom::HTMLLinkElement* link = static_cast<dom::HTMLLinkElement*>(source.get());
+ corsMode = link->GetCORSMode();
+ referrerPolicy = link->GetLinkReferrerPolicy();
+ }
+
+ if (referrerPolicy == net::RP_Unset) {
+ referrerPolicy = source->OwnerDoc()->GetReferrerPolicy();
+ }
+
+ uint32_t securityFlags;
+ if (corsMode == CORS_NONE) {
+ securityFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
+ } else {
+ securityFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
+ if (corsMode == CORS_USE_CREDENTIALS) {
+ securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
+ }
+ }
+ nsresult rv = NS_NewChannelInternal(getter_AddRefs(mChannel),
+ mURI,
+ source,
+ source->NodePrincipal(),
+ nullptr, //aTriggeringPrincipal
+ securityFlags,
+ nsIContentPolicy::TYPE_OTHER,
+ 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) {
+ httpChannel->SetReferrerWithPolicy(mReferrerURI, referrerPolicy);
+ httpChannel->SetRequestHeader(
+ NS_LITERAL_CSTRING("X-Moz"),
+ NS_LITERAL_CSTRING("prefetch"),
+ false);
+ }
+
+ rv = mChannel->AsyncOpen2(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,
+ nsISupports *aContext)
+{
+ 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->GetLoadInfo();
+ 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,
+ nsISupports *aContext,
+ 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=%llu]\n", bytesRead, aOffset));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsPrefetchNode::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ LOG(("done prefetching [status=%x]\n", 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->ProcessNextURI(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;
+
+ bool match;
+ rv = newURI->SchemeIs("http", &match);
+ if (NS_FAILED(rv) || !match) {
+ rv = newURI->SchemeIs("https", &match);
+ if (NS_FAILED(rv) || !match) {
+ 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);
+
+ httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
+ NS_LITERAL_CSTRING("prefetch"),
+ false);
+
+ // 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)
+ , mDisabled(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)
+ EmptyQueue();
+}
+
+nsresult
+nsPrefetchService::Init()
+{
+ nsresult rv;
+
+ // read prefs and hook up pref observer
+ mDisabled = !Preferences::GetBool(PREFETCH_PREF, !mDisabled);
+ 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 (!mDisabled)
+ AddProgressListener();
+
+ return NS_OK;
+}
+
+void
+nsPrefetchService::ProcessNextURI(nsPrefetchNode *aFinished)
+{
+ nsresult rv;
+
+ if (aFinished) {
+ mCurrentNodes.RemoveElement(aFinished);
+ }
+
+ if (mCurrentNodes.Length() >= static_cast<uint32_t>(mMaxParallelism)) {
+ // We already have enough prefetches going on, so hold off
+ // for now.
+ return;
+ }
+
+ do {
+ if (mQueue.empty()) {
+ break;
+ }
+ RefPtr<nsPrefetchNode> node = mQueue.front().forget();
+ mQueue.pop_front();
+
+ if (LOG_ENABLED()) {
+ LOG(("ProcessNextURI [%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),
+ "prefetch-load-requested", nullptr);
+}
+
+void
+nsPrefetchService::NotifyLoadCompleted(nsPrefetchNode *node)
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService)
+ return;
+
+ observerService->NotifyObservers(static_cast<nsIStreamListener*>(node),
+ "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()) {
+ nsContentUtils::DispatchTrustedEvent(domNode->OwnerDoc(),
+ domNode,
+ aSuccess ?
+ NS_LITERAL_STRING("load") :
+ NS_LITERAL_STRING("error"),
+ /* aCanBubble = */ false,
+ /* aCancelable = */ false);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService <private>
+//-----------------------------------------------------------------------------
+
+void
+nsPrefetchService::AddProgressListener()
+{
+ // Register as an observer for the document loader
+ nsCOMPtr<nsIWebProgress> progress =
+ do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
+ if (progress)
+ progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+}
+
+void
+nsPrefetchService::RemoveProgressListener()
+{
+ // Register as an observer for the document loader
+ nsCOMPtr<nsIWebProgress> progress =
+ do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
+ if (progress)
+ progress->RemoveProgressListener(this);
+}
+
+nsresult
+nsPrefetchService::EnqueueURI(nsIURI *aURI,
+ nsIURI *aReferrerURI,
+ nsIDOMNode *aSource,
+ nsPrefetchNode **aNode)
+{
+ RefPtr<nsPrefetchNode> node = new nsPrefetchNode(this, aURI, aReferrerURI,
+ aSource);
+ mQueue.push_back(node);
+ node.forget(aNode);
+ return NS_OK;
+}
+
+void
+nsPrefetchService::EmptyQueue()
+{
+ while (!mQueue.empty()) {
+ mQueue.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 (!mQueue.empty() && mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
+ ProcessNextURI(nullptr);
+ }
+ }
+}
+
+void
+nsPrefetchService::StopPrefetching()
+{
+ mStopCount++;
+
+ LOG(("StopPrefetching [stopcount=%d]\n", mStopCount));
+
+ // only kill the prefetch queue if we are actively prefetching right now
+ if (mCurrentNodes.IsEmpty()) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
+ mCurrentNodes[i]->CancelChannel(NS_BINDING_ABORTED);
+ }
+ mCurrentNodes.Clear();
+ EmptyQueue();
+}
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsPrefetchService,
+ nsIPrefetchService,
+ nsIWebProgressListener,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// nsPrefetchService::nsIPrefetchService
+//-----------------------------------------------------------------------------
+
+nsresult
+nsPrefetchService::Prefetch(nsIURI *aURI,
+ nsIURI *aReferrerURI,
+ nsIDOMNode *aSource,
+ bool aExplicit)
+{
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(aReferrerURI);
+
+ if (LOG_ENABLED()) {
+ LOG(("PrefetchURI [%s]\n", aURI->GetSpecOrDefault().get()));
+ }
+
+ if (mDisabled) {
+ LOG(("rejected: prefetch service is disabled\n"));
+ return NS_ERROR_ABORT;
+ }
+
+ //
+ // 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 links since we know that's the
+ // most common case. ignore https links since https content only goes
+ // into the memory cache.
+ //
+ // XXX we might want to either leverage nsIProtocolHandler::protocolFlags
+ // or possibly nsIRequest::loadFlags to determine if this URI should be
+ // prefetched.
+ //
+ bool match;
+ rv = aURI->SchemeIs("http", &match);
+ if (NS_FAILED(rv) || !match) {
+ rv = aURI->SchemeIs("https", &match);
+ if (NS_FAILED(rv) || !match) {
+ LOG(("rejected: URL is not of type http/https\n"));
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ //
+ // the referrer URI must be http:
+ //
+ rv = aReferrerURI->SchemeIs("http", &match);
+ if (NS_FAILED(rv) || !match) {
+ rv = aReferrerURI->SchemeIs("https", &match);
+ if (NS_FAILED(rv) || !match) {
+ LOG(("rejected: referrer URL is neither http nor https\n"));
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ // 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) {
+ nsCOMPtr<nsIWeakReference> 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 = mQueue.begin();
+ nodeIt != mQueue.end(); nodeIt++) {
+ bool equals;
+ RefPtr<nsPrefetchNode> node = nodeIt->get();
+ if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) {
+ nsCOMPtr<nsIWeakReference> 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, aReferrerURI, aSource,
+ getter_AddRefs(enqueuedNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NotifyLoadRequested(enqueuedNode);
+
+ // if there are no pages loading, kick off the request immediately
+ if (mStopCount == 0 && mHaveProcessed) {
+ ProcessNextURI(nullptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::CancelPrefetchURI(nsIURI* aURI,
+ nsIDOMNode* 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) {
+ nsCOMPtr<nsIWeakReference> 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 = mQueue.begin();
+ nodeIt != mQueue.end(); nodeIt++) {
+ bool equals;
+ RefPtr<nsPrefetchNode> node = nodeIt->get();
+ if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) {
+ nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
+ if (node->mSources.IndexOf(source) !=
+ node->mSources.NoIndex) {
+
+#ifdef DEBUG
+ int32_t inx = node->mSources.IndexOf(source);
+ nsCOMPtr<nsIDOMNode> domNode =
+ do_QueryReferent(node->mSources.ElementAt(inx));
+ MOZ_ASSERT(domNode);
+#endif
+
+ node->mSources.RemoveElement(source);
+ if (node->mSources.IsEmpty()) {
+ mQueue.erase(nodeIt);
+ }
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // not found!
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::PrefetchURI(nsIURI *aURI,
+ nsIURI *aReferrerURI,
+ nsIDOMNode *aSource,
+ bool aExplicit)
+{
+ return Prefetch(aURI, aReferrerURI, aSource, aExplicit);
+}
+
+NS_IMETHODIMP
+nsPrefetchService::HasMoreElements(bool *aHasMore)
+{
+ *aHasMore = (mCurrentNodes.Length() || !mQueue.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)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t progressStateFlags,
+ nsresult aStatus)
+{
+ if (mAggressive) {
+ LOG(("Document load state is ignored in aggressive mode"));
+ return NS_OK;
+ }
+
+ 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)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefetchService::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t state)
+{
+ NS_NOTREACHED("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)) {
+ StopPrefetching();
+ EmptyQueue();
+ mDisabled = 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 (mDisabled) {
+ LOG(("enabling prefetching\n"));
+ mDisabled = false;
+ AddProgressListener();
+ }
+ } else {
+ if (!mDisabled) {
+ LOG(("disabling prefetching\n"));
+ StopPrefetching();
+ EmptyQueue();
+ mDisabled = 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 (!mQueue.empty() && mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
+ ProcessNextURI(nullptr);
+ }
+ } else if (!strcmp(pref, AGGRESSIVE_PREF)) {
+ mAggressive = Preferences::GetBool(AGGRESSIVE_PREF, false);
+ // in aggressive mode, clear stop count and start prefetching immediately
+ if (mAggressive) {
+ mStopCount = 0;
+ StartPrefetching();
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+// vim: ts=4 sw=4 expandtab
diff --git a/uriloader/prefetch/nsPrefetchService.h b/uriloader/prefetch/nsPrefetchService.h
new file mode 100644
index 000000000..883449e68
--- /dev/null
+++ b/uriloader/prefetch/nsPrefetchService.h
@@ -0,0 +1,121 @@
+/* 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 "nsCPrefetchService.h"
+#include "nsIObserver.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIChannelEventSink.h"
+#include "nsIRedirectResultListener.h"
+#include "nsIWebProgressListener.h"
+#include "nsIStreamListener.h"
+#include "nsIChannel.h"
+#include "nsIURI.h"
+#include "nsWeakReference.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Attributes.h"
+#include <deque>
+
+class nsPrefetchService;
+class nsPrefetchNode;
+
+//-----------------------------------------------------------------------------
+// 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 ProcessNextURI(nsPrefetchNode *aFinished);
+
+ void NotifyLoadRequested(nsPrefetchNode *node);
+ void NotifyLoadCompleted(nsPrefetchNode *node);
+ void DispatchEvent(nsPrefetchNode *node, bool aSuccess);
+
+private:
+ ~nsPrefetchService();
+
+ nsresult Prefetch(nsIURI *aURI,
+ nsIURI *aReferrerURI,
+ nsIDOMNode *aSource,
+ bool aExplicit);
+
+ void AddProgressListener();
+ void RemoveProgressListener();
+ nsresult EnqueueURI(nsIURI *aURI, nsIURI *aReferrerURI,
+ nsIDOMNode *aSource, nsPrefetchNode **node);
+ void EmptyQueue();
+
+ void StartPrefetching();
+ void StopPrefetching();
+
+ std::deque<RefPtr<nsPrefetchNode>> mQueue;
+ nsTArray<RefPtr<nsPrefetchNode>> mCurrentNodes;
+ int32_t mMaxParallelism;
+ int32_t mStopCount;
+ // true if pending document loads have ever reached zero.
+ int32_t mHaveProcessed;
+ bool mDisabled;
+
+ // 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;
+};
+
+//-----------------------------------------------------------------------------
+// nsPrefetchNode
+//-----------------------------------------------------------------------------
+
+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,
+ nsIURI *aReferrerURI,
+ nsIDOMNode *aSource);
+
+ nsresult OpenChannel();
+ nsresult CancelChannel(nsresult error);
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mReferrerURI;
+ nsTArray<nsCOMPtr<nsIWeakReference>> mSources;
+
+private:
+ ~nsPrefetchNode() {}
+
+ RefPtr<nsPrefetchService> mService;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ int64_t mBytesRead;
+ bool mShouldFireLoadEvent;
+};
+
+#endif // !nsPrefetchService_h__