summaryrefslogtreecommitdiffstats
path: root/dom/webbrowserpersist
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webbrowserpersist')
-rw-r--r--dom/webbrowserpersist/PWebBrowserPersistDocument.ipdl100
-rw-r--r--dom/webbrowserpersist/PWebBrowserPersistResources.ipdl32
-rw-r--r--dom/webbrowserpersist/PWebBrowserPersistSerialize.ipdl30
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistDocumentChild.cpp157
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistDocumentChild.h53
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistDocumentParent.cpp115
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistDocumentParent.h72
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp1319
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistLocalDocument.h52
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp207
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistRemoteDocument.h59
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistResourcesChild.cpp76
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistResourcesChild.h30
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistResourcesParent.cpp95
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistResourcesParent.h57
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistSerializeChild.cpp136
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistSerializeChild.h38
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistSerializeParent.cpp78
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistSerializeParent.h45
-rw-r--r--dom/webbrowserpersist/moz.build49
-rw-r--r--dom/webbrowserpersist/nsIWebBrowserPersist.idl265
-rw-r--r--dom/webbrowserpersist/nsIWebBrowserPersistDocument.idl227
-rw-r--r--dom/webbrowserpersist/nsWebBrowserPersist.cpp2717
-rw-r--r--dom/webbrowserpersist/nsWebBrowserPersist.h191
24 files changed, 6200 insertions, 0 deletions
diff --git a/dom/webbrowserpersist/PWebBrowserPersistDocument.ipdl b/dom/webbrowserpersist/PWebBrowserPersistDocument.ipdl
new file mode 100644
index 0000000000..5e90569e22
--- /dev/null
+++ b/dom/webbrowserpersist/PWebBrowserPersistDocument.ipdl
@@ -0,0 +1,100 @@
+/* -*- Mode: IDL; tab-width: 8; 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 protocol PContent;
+include protocol PWebBrowserPersistResources;
+include protocol PWebBrowserPersistSerialize;
+
+include PBackgroundSharedTypes;
+include IPCStream;
+include NeckoChannelParams;
+
+include "mozilla/dom/ReferrerInfoUtils.h";
+
+[RefCounted] using class nsIReferrerInfo from "nsIReferrerInfo.h";
+
+namespace mozilla {
+
+// nsIWebBrowserPersistDocument has attributes which can be read
+// synchronously. To avoid using sync IPC for them, the actor sends
+// this structure from the child to the parent before the parent actor
+// is exposed to XPCOM.
+struct WebBrowserPersistDocumentAttrs {
+ bool isPrivate;
+ nsCString documentURI;
+ nsCString baseURI;
+ nsCString contentType;
+ nsCString characterSet;
+ nsString title;
+ nullable nsIReferrerInfo referrerInfo;
+ CookieJarSettingsArgs cookieJarSettings;
+ nsString contentDisposition;
+ uint32_t sessionHistoryCacheKey;
+ uint32_t persistFlags;
+ PrincipalInfo principal;
+};
+
+// IPDL doesn't have tuples, so this gives the pair of strings from
+// nsIWebBrowserPersistURIMap::getURIMapping a name.
+struct WebBrowserPersistURIMapEntry {
+ nsCString mapFrom;
+ nsCString mapTo;
+};
+
+// nsIWebBrowserPersistURIMap is just copied over IPC as one of these,
+// not proxied, to simplify the protocol.
+struct WebBrowserPersistURIMap {
+ WebBrowserPersistURIMapEntry[] mapURIs;
+ nsCString targetBaseURI;
+};
+
+// This remotes nsIWebBrowserPersistDocument and its visitors. The
+// lifecycle is a little complicated: the initial document is
+// constructed parent->child, but subdocuments are constructed
+// child->parent and then passed back. Subdocuments aren't subactors,
+// because that would impose a lifetime relationship that doesn't
+// exist in the XPIDL; instead they're all managed by the enclosing
+// PContent.
+[ManualDealloc]
+protocol PWebBrowserPersistDocument {
+ manager PContent;
+ manages PWebBrowserPersistResources;
+ manages PWebBrowserPersistSerialize;
+
+parent:
+ // The actor isn't exposed to XPCOM until after it gets one of these
+ // two messages; see also the state transition rules. The message
+ // is either a response to the constructor (if it was parent->child)
+ // or sent after it (if it was child->parent).
+ async Attributes(WebBrowserPersistDocumentAttrs aAttrs,
+ IPCStream? stream);
+ async InitFailure(nsresult aStatus);
+
+child:
+ async SetPersistFlags(uint32_t aNewFlags);
+ async PWebBrowserPersistResources();
+ async PWebBrowserPersistSerialize(WebBrowserPersistURIMap aMap,
+ nsCString aRequestedContentType,
+ uint32_t aEncoderFlags,
+ uint32_t aWrapColumn);
+ async __delete__();
+
+/*
+state START:
+ recv Attributes goto MAIN;
+ recv InitFailure goto FAILED;
+
+state MAIN:
+ send SetPersistFlags goto MAIN;
+ send PWebBrowserPersistResources goto MAIN;
+ send PWebBrowserPersistSerialize goto MAIN;
+ send __delete__;
+
+state FAILED:
+ send __delete__;
+*/
+};
+
+} // namespace mozilla
diff --git a/dom/webbrowserpersist/PWebBrowserPersistResources.ipdl b/dom/webbrowserpersist/PWebBrowserPersistResources.ipdl
new file mode 100644
index 0000000000..87b880c296
--- /dev/null
+++ b/dom/webbrowserpersist/PWebBrowserPersistResources.ipdl
@@ -0,0 +1,32 @@
+/* -*- Mode: IDL; tab-width: 8; 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 protocol PWebBrowserPersistDocument;
+
+using mozilla::dom::MaybeDiscardedBrowsingContext from "mozilla/dom/BrowsingContext.h";
+using nsContentPolicyType from "nsIContentPolicy.h";
+
+namespace mozilla {
+
+// == nsIWebBrowserPersistResourceVisitor
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+protocol PWebBrowserPersistResources {
+ manager PWebBrowserPersistDocument;
+
+parent:
+ async VisitResource(nsCString aURI, nsContentPolicyType aContentPolicyType);
+
+ // The actor sent here is in the START state; the parent-side
+ // receiver will have to wait for it to enter the MAIN state
+ // before exposing it with a visitDocument call.
+ async VisitDocument(PWebBrowserPersistDocument aSubDocument);
+
+ async VisitBrowsingContext(MaybeDiscardedBrowsingContext aContext);
+
+ // This reflects the endVisit method.
+ async __delete__(nsresult aStatus);
+};
+
+} // namespace mozilla
diff --git a/dom/webbrowserpersist/PWebBrowserPersistSerialize.ipdl b/dom/webbrowserpersist/PWebBrowserPersistSerialize.ipdl
new file mode 100644
index 0000000000..11fbe530c0
--- /dev/null
+++ b/dom/webbrowserpersist/PWebBrowserPersistSerialize.ipdl
@@ -0,0 +1,30 @@
+/* -*- Mode: IDL; tab-width: 8; 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 protocol PWebBrowserPersistDocument;
+
+namespace mozilla {
+
+// This actor represents both an nsIWebBrowserPersistWriteCompletion
+// and the nsIOutputStream passed with it to the writeContent method.
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+protocol PWebBrowserPersistSerialize {
+ manager PWebBrowserPersistDocument;
+
+parent:
+ // This sends the data with no flow control, so the parent could
+ // wind up buffering an arbitrarily large amount of data... but
+ // it's a serialized DOM that's already in memory as DOM nodes, so
+ // this is at worst just a constant-factor increase in memory usage.
+ // Also, Chromium does the same thing; see
+ // content::RenderViewImpl::didSerializeDataForFrame.
+ async WriteData(uint8_t[] aData);
+
+ // This is the onFinish method.
+ async __delete__(nsCString aContentType,
+ nsresult aStatus);
+};
+
+} // namespace mozilla
diff --git a/dom/webbrowserpersist/WebBrowserPersistDocumentChild.cpp b/dom/webbrowserpersist/WebBrowserPersistDocumentChild.cpp
new file mode 100644
index 0000000000..00dbbef08f
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistDocumentChild.cpp
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistDocumentChild.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/dom/Document.h"
+#include "nsIInputStream.h"
+#include "WebBrowserPersistLocalDocument.h"
+#include "WebBrowserPersistResourcesChild.h"
+#include "WebBrowserPersistSerializeChild.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/net/CookieJarSettings.h"
+
+namespace mozilla {
+
+WebBrowserPersistDocumentChild::WebBrowserPersistDocumentChild() = default;
+
+WebBrowserPersistDocumentChild::~WebBrowserPersistDocumentChild() = default;
+
+void WebBrowserPersistDocumentChild::Start(dom::Document* aDocument) {
+ RefPtr<WebBrowserPersistLocalDocument> doc;
+ if (aDocument) {
+ doc = new WebBrowserPersistLocalDocument(aDocument);
+ }
+ Start(doc);
+}
+
+void WebBrowserPersistDocumentChild::Start(
+ nsIWebBrowserPersistDocument* aDocument) {
+ MOZ_ASSERT(!mDocument);
+ if (!aDocument) {
+ SendInitFailure(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ WebBrowserPersistDocumentAttrs attrs;
+ nsCOMPtr<nsIInputStream> postDataStream;
+#define ENSURE(e) \
+ do { \
+ nsresult rv = (e); \
+ if (NS_FAILED(rv)) { \
+ SendInitFailure(rv); \
+ return; \
+ } \
+ } while (0)
+ ENSURE(aDocument->GetIsPrivate(&(attrs.isPrivate())));
+ ENSURE(aDocument->GetDocumentURI(attrs.documentURI()));
+ ENSURE(aDocument->GetBaseURI(attrs.baseURI()));
+ ENSURE(aDocument->GetContentType(attrs.contentType()));
+ ENSURE(aDocument->GetCharacterSet(attrs.characterSet()));
+ ENSURE(aDocument->GetTitle(attrs.title()));
+ ENSURE(aDocument->GetContentDisposition(attrs.contentDisposition()));
+
+ attrs.sessionHistoryCacheKey() = aDocument->GetCacheKey();
+
+ ENSURE(aDocument->GetPersistFlags(&(attrs.persistFlags())));
+
+ ENSURE(aDocument->GetPrincipal(getter_AddRefs(principal)));
+ ENSURE(ipc::PrincipalToPrincipalInfo(principal, &(attrs.principal())));
+
+ ENSURE(aDocument->GetReferrerInfo(getter_AddRefs(referrerInfo)));
+ attrs.referrerInfo() = referrerInfo;
+
+ ENSURE(aDocument->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)));
+ net::CookieJarSettings::Cast(cookieJarSettings)
+ ->Serialize(attrs.cookieJarSettings());
+
+ ENSURE(aDocument->GetPostData(getter_AddRefs(postDataStream)));
+#undef ENSURE
+
+ Maybe<mozilla::ipc::IPCStream> stream;
+ mozilla::ipc::SerializeIPCStream(postDataStream.forget(), stream,
+ /* aAllowLazy */ false);
+
+ mDocument = aDocument;
+ SendAttributes(attrs, stream);
+}
+
+mozilla::ipc::IPCResult WebBrowserPersistDocumentChild::RecvSetPersistFlags(
+ const uint32_t& aNewFlags) {
+ mDocument->SetPersistFlags(aNewFlags);
+ return IPC_OK();
+}
+
+PWebBrowserPersistResourcesChild*
+WebBrowserPersistDocumentChild::AllocPWebBrowserPersistResourcesChild() {
+ auto* actor = new WebBrowserPersistResourcesChild();
+ NS_ADDREF(actor);
+ return actor;
+}
+
+mozilla::ipc::IPCResult
+WebBrowserPersistDocumentChild::RecvPWebBrowserPersistResourcesConstructor(
+ PWebBrowserPersistResourcesChild* aActor) {
+ RefPtr<WebBrowserPersistResourcesChild> visitor =
+ static_cast<WebBrowserPersistResourcesChild*>(aActor);
+ nsresult rv = mDocument->ReadResources(visitor);
+ if (NS_FAILED(rv)) {
+ // This is a sync failure on the child side but an async
+ // failure on the parent side -- it already got NS_OK from
+ // ReadResources, so the error has to be reported via the
+ // visitor instead.
+ visitor->EndVisit(mDocument, rv);
+ }
+ return IPC_OK();
+}
+
+bool WebBrowserPersistDocumentChild::DeallocPWebBrowserPersistResourcesChild(
+ PWebBrowserPersistResourcesChild* aActor) {
+ auto* castActor = static_cast<WebBrowserPersistResourcesChild*>(aActor);
+ NS_RELEASE(castActor);
+ return true;
+}
+
+PWebBrowserPersistSerializeChild*
+WebBrowserPersistDocumentChild::AllocPWebBrowserPersistSerializeChild(
+ const WebBrowserPersistURIMap& aMap,
+ const nsACString& aRequestedContentType, const uint32_t& aEncoderFlags,
+ const uint32_t& aWrapColumn) {
+ auto* actor = new WebBrowserPersistSerializeChild(aMap);
+ NS_ADDREF(actor);
+ return actor;
+}
+
+mozilla::ipc::IPCResult
+WebBrowserPersistDocumentChild::RecvPWebBrowserPersistSerializeConstructor(
+ PWebBrowserPersistSerializeChild* aActor,
+ const WebBrowserPersistURIMap& aMap,
+ const nsACString& aRequestedContentType, const uint32_t& aEncoderFlags,
+ const uint32_t& aWrapColumn) {
+ auto* castActor = static_cast<WebBrowserPersistSerializeChild*>(aActor);
+ // This actor performs the roles of: completion, URI map, and output stream.
+ nsresult rv =
+ mDocument->WriteContent(castActor, castActor, aRequestedContentType,
+ aEncoderFlags, aWrapColumn, castActor);
+ if (NS_FAILED(rv)) {
+ castActor->OnFinish(mDocument, castActor, aRequestedContentType, rv);
+ }
+ return IPC_OK();
+}
+
+bool WebBrowserPersistDocumentChild::DeallocPWebBrowserPersistSerializeChild(
+ PWebBrowserPersistSerializeChild* aActor) {
+ auto* castActor = static_cast<WebBrowserPersistSerializeChild*>(aActor);
+ NS_RELEASE(castActor);
+ return true;
+}
+
+} // namespace mozilla
diff --git a/dom/webbrowserpersist/WebBrowserPersistDocumentChild.h b/dom/webbrowserpersist/WebBrowserPersistDocumentChild.h
new file mode 100644
index 0000000000..220811fb6f
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistDocumentChild.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistDocumentChild_h__
+#define WebBrowserPersistDocumentChild_h__
+
+#include "mozilla/PWebBrowserPersistDocumentChild.h"
+#include "nsCOMPtr.h"
+#include "nsIWebBrowserPersistDocument.h"
+
+namespace mozilla {
+
+class WebBrowserPersistDocumentChild final
+ : public PWebBrowserPersistDocumentChild {
+ public:
+ WebBrowserPersistDocumentChild();
+ ~WebBrowserPersistDocumentChild();
+
+ // This sends either Attributes or InitFailure and thereby causes
+ // the actor to leave the START state.
+ void Start(nsIWebBrowserPersistDocument* aDocument);
+ void Start(dom::Document* aDocument);
+
+ mozilla::ipc::IPCResult RecvSetPersistFlags(const uint32_t& aNewFlags);
+
+ PWebBrowserPersistResourcesChild* AllocPWebBrowserPersistResourcesChild();
+ virtual mozilla::ipc::IPCResult RecvPWebBrowserPersistResourcesConstructor(
+ PWebBrowserPersistResourcesChild* aActor) override;
+ bool DeallocPWebBrowserPersistResourcesChild(
+ PWebBrowserPersistResourcesChild* aActor);
+
+ PWebBrowserPersistSerializeChild* AllocPWebBrowserPersistSerializeChild(
+ const WebBrowserPersistURIMap& aMap,
+ const nsACString& aRequestedContentType, const uint32_t& aEncoderFlags,
+ const uint32_t& aWrapColumn);
+ virtual mozilla::ipc::IPCResult RecvPWebBrowserPersistSerializeConstructor(
+ PWebBrowserPersistSerializeChild* aActor,
+ const WebBrowserPersistURIMap& aMap,
+ const nsACString& aRequestedContentType, const uint32_t& aEncoderFlags,
+ const uint32_t& aWrapColumn) override;
+ bool DeallocPWebBrowserPersistSerializeChild(
+ PWebBrowserPersistSerializeChild* aActor);
+
+ private:
+ nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistDocumentChild_h__
diff --git a/dom/webbrowserpersist/WebBrowserPersistDocumentParent.cpp b/dom/webbrowserpersist/WebBrowserPersistDocumentParent.cpp
new file mode 100644
index 0000000000..8d08ae5fc5
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistDocumentParent.cpp
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistDocumentParent.h"
+
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/dom/PContentParent.h"
+#include "nsIInputStream.h"
+#include "nsThreadUtils.h"
+#include "WebBrowserPersistResourcesParent.h"
+#include "WebBrowserPersistSerializeParent.h"
+#include "WebBrowserPersistRemoteDocument.h"
+
+namespace mozilla {
+
+WebBrowserPersistDocumentParent::WebBrowserPersistDocumentParent()
+ : mReflection(nullptr) {}
+
+void WebBrowserPersistDocumentParent::SetOnReady(
+ nsIWebBrowserPersistDocumentReceiver* aOnReady) {
+ MOZ_ASSERT(aOnReady);
+ MOZ_ASSERT(!mOnReady);
+ MOZ_ASSERT(!mReflection);
+ mOnReady = aOnReady;
+}
+
+void WebBrowserPersistDocumentParent::ActorDestroy(ActorDestroyReason aWhy) {
+ if (mReflection) {
+ mReflection->ActorDestroy();
+ mReflection = nullptr;
+ }
+ if (mOnReady) {
+ // Bug 1202887: If this is part of a subtree destruction, then
+ // anything which could cause another actor in that subtree to
+ // be Send__delete__()ed will cause use-after-free -- such as
+ // dropping the last reference to another document's
+ // WebBrowserPersistRemoteDocument. To avoid that, defer the
+ // callback until after the entire subtree is destroyed.
+ nsCOMPtr<nsIRunnable> errorLater = NewRunnableMethod<nsresult>(
+ "nsIWebBrowserPersistDocumentReceiver::OnError", mOnReady,
+ &nsIWebBrowserPersistDocumentReceiver::OnError, NS_ERROR_FAILURE);
+ NS_DispatchToCurrentThread(errorLater);
+ mOnReady = nullptr;
+ }
+}
+
+WebBrowserPersistDocumentParent::~WebBrowserPersistDocumentParent() {
+ MOZ_RELEASE_ASSERT(!mReflection);
+ MOZ_ASSERT(!mOnReady);
+}
+
+mozilla::ipc::IPCResult WebBrowserPersistDocumentParent::RecvAttributes(
+ const Attrs& aAttrs, const Maybe<IPCStream>& aPostStream) {
+ // Deserialize the postData unconditionally so that fds aren't leaked.
+ nsCOMPtr<nsIInputStream> postData =
+ mozilla::ipc::DeserializeIPCStream(aPostStream);
+ if (!mOnReady || mReflection) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ mReflection = new WebBrowserPersistRemoteDocument(this, aAttrs, postData);
+ RefPtr<WebBrowserPersistRemoteDocument> reflection = mReflection;
+ mOnReady->OnDocumentReady(reflection);
+ mOnReady = nullptr;
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebBrowserPersistDocumentParent::RecvInitFailure(
+ const nsresult& aFailure) {
+ if (!mOnReady || mReflection) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ mOnReady->OnError(aFailure);
+ mOnReady = nullptr;
+ // Warning: Send__delete__ deallocates this object.
+ IProtocol* mgr = Manager();
+ if (!Send__delete__(this)) {
+ return IPC_FAIL_NO_REASON(mgr);
+ }
+ return IPC_OK();
+}
+
+PWebBrowserPersistResourcesParent*
+WebBrowserPersistDocumentParent::AllocPWebBrowserPersistResourcesParent() {
+ MOZ_CRASH("Don't use this; construct the actor directly and AddRef.");
+ return nullptr;
+}
+
+bool WebBrowserPersistDocumentParent::DeallocPWebBrowserPersistResourcesParent(
+ PWebBrowserPersistResourcesParent* aActor) {
+ // Turn the ref held by IPC back into an nsRefPtr.
+ RefPtr<WebBrowserPersistResourcesParent> actor =
+ already_AddRefed<WebBrowserPersistResourcesParent>(
+ static_cast<WebBrowserPersistResourcesParent*>(aActor));
+ return true;
+}
+
+PWebBrowserPersistSerializeParent*
+WebBrowserPersistDocumentParent::AllocPWebBrowserPersistSerializeParent(
+ const WebBrowserPersistURIMap& aMap,
+ const nsACString& aRequestedContentType, const uint32_t& aEncoderFlags,
+ const uint32_t& aWrapColumn) {
+ MOZ_CRASH("Don't use this; construct the actor directly.");
+ return nullptr;
+}
+
+bool WebBrowserPersistDocumentParent::DeallocPWebBrowserPersistSerializeParent(
+ PWebBrowserPersistSerializeParent* aActor) {
+ delete aActor;
+ return true;
+}
+
+} // namespace mozilla
diff --git a/dom/webbrowserpersist/WebBrowserPersistDocumentParent.h b/dom/webbrowserpersist/WebBrowserPersistDocumentParent.h
new file mode 100644
index 0000000000..5f656abbbb
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistDocumentParent.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistDocumentParent_h__
+#define WebBrowserPersistDocumentParent_h__
+
+#include "mozilla/Maybe.h"
+#include "mozilla/PWebBrowserPersistDocumentParent.h"
+#include "nsCOMPtr.h"
+#include "nsIWebBrowserPersistDocument.h"
+
+// This class is the IPC half of the glue between the
+// nsIWebBrowserPersistDocument interface and a remote document. When
+// (and if) it receives the Attributes message it constructs an
+// WebBrowserPersistRemoteDocument and releases it into the XPCOM
+// universe; otherwise, it invokes the document receiver's error
+// callback.
+//
+// This object's lifetime is the normal IPC lifetime; on destruction,
+// it calls its XPCOM reflection (if it exists yet) to remove that
+// reference. Normal deletion occurs when the XPCOM object is being
+// destroyed or after an InitFailure is received and handled.
+//
+// See also: BrowserParent::StartPersistence.
+
+namespace mozilla {
+
+class WebBrowserPersistRemoteDocument;
+
+class WebBrowserPersistDocumentParent final
+ : public PWebBrowserPersistDocumentParent {
+ public:
+ WebBrowserPersistDocumentParent();
+ virtual ~WebBrowserPersistDocumentParent();
+
+ // Set a callback to be invoked when the actor leaves the START
+ // state. This method must be called exactly once while the actor
+ // is still in the START state (or is unconstructed).
+ void SetOnReady(nsIWebBrowserPersistDocumentReceiver* aOnReady);
+
+ using Attrs = WebBrowserPersistDocumentAttrs;
+
+ // IPDL methods:
+ mozilla::ipc::IPCResult RecvAttributes(const Attrs& aAttrs,
+ const Maybe<IPCStream>& aPostStream);
+ mozilla::ipc::IPCResult RecvInitFailure(const nsresult& aFailure);
+
+ PWebBrowserPersistResourcesParent* AllocPWebBrowserPersistResourcesParent();
+ bool DeallocPWebBrowserPersistResourcesParent(
+ PWebBrowserPersistResourcesParent* aActor);
+
+ PWebBrowserPersistSerializeParent* AllocPWebBrowserPersistSerializeParent(
+ const WebBrowserPersistURIMap& aMap,
+ const nsACString& aRequestedContentType, const uint32_t& aEncoderFlags,
+ const uint32_t& aWrapColumn);
+ bool DeallocPWebBrowserPersistSerializeParent(
+ PWebBrowserPersistSerializeParent* aActor);
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ private:
+ // This is reset to nullptr when the callback is invoked.
+ nsCOMPtr<nsIWebBrowserPersistDocumentReceiver> mOnReady;
+ WebBrowserPersistRemoteDocument* mReflection;
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistDocumentParent_h__
diff --git a/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp b/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp
new file mode 100644
index 0000000000..524f496d35
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp
@@ -0,0 +1,1319 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistLocalDocument.h"
+#include "WebBrowserPersistDocumentParent.h"
+
+#include "mozilla/dom/Attr.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/Comment.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLAnchorElement.h"
+#include "mozilla/dom/HTMLAreaElement.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/HTMLLinkElement.h"
+#include "mozilla/dom/HTMLObjectElement.h"
+#include "mozilla/dom/HTMLOptionElement.h"
+#include "mozilla/dom/HTMLSharedElement.h"
+#include "mozilla/dom/HTMLTextAreaElement.h"
+#include "mozilla/dom/NodeFilterBinding.h"
+#include "mozilla/dom/ProcessingInstruction.h"
+#include "mozilla/dom/ResponsiveImageSelector.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/TreeWalker.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/Unused.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsContentCID.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMAttributeMap.h"
+#include "nsFrameLoader.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsIContent.h"
+#include "nsICookieJarSettings.h"
+#include "nsIDOMWindowUtils.h"
+#include "mozilla/dom/Document.h"
+#include "nsIDocumentEncoder.h"
+#include "nsILoadContext.h"
+#include "nsIProtocolHandler.h"
+#include "nsISHEntry.h"
+#include "nsIURIMutator.h"
+#include "nsIWebBrowserPersist.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebPageDescriptor.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+
+namespace mozilla {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebBrowserPersistLocalDocument)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebBrowserPersistLocalDocument)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebBrowserPersistLocalDocument)
+ NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersistDocument)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(WebBrowserPersistLocalDocument, mDocument)
+
+WebBrowserPersistLocalDocument::WebBrowserPersistLocalDocument(
+ dom::Document* aDocument)
+ : mDocument(aDocument), mPersistFlags(0) {
+ MOZ_ASSERT(mDocument);
+}
+
+WebBrowserPersistLocalDocument::~WebBrowserPersistLocalDocument() = default;
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::SetPersistFlags(uint32_t aFlags) {
+ mPersistFlags = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetPersistFlags(uint32_t* aFlags) {
+ *aFlags = mPersistFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetIsClosed(bool* aIsClosed) {
+ *aIsClosed = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetIsPrivate(bool* aIsPrivate) {
+ nsCOMPtr<nsILoadContext> privacyContext = mDocument->GetLoadContext();
+ *aIsPrivate = privacyContext && privacyContext->UsePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetDocumentURI(nsACString& aURISpec) {
+ nsCOMPtr<nsIURI> uri = mDocument->GetDocumentURI();
+ if (!uri) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return uri->GetSpec(aURISpec);
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetBaseURI(nsACString& aURISpec) {
+ nsCOMPtr<nsIURI> uri = GetBaseURI();
+ if (!uri) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return uri->GetSpec(aURISpec);
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetContentType(nsACString& aContentType) {
+ nsAutoString utf16Type;
+ mDocument->GetContentType(utf16Type);
+ CopyUTF16toUTF8(utf16Type, aContentType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetCharacterSet(nsACString& aCharSet) {
+ GetCharacterSet()->Name(aCharSet);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetTitle(nsAString& aTitle) {
+ nsAutoString titleBuffer;
+ mDocument->GetTitle(titleBuffer);
+ aTitle = titleBuffer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetReferrerInfo(
+ nsIReferrerInfo** aReferrerInfo) {
+ *aReferrerInfo = mDocument->GetReferrerInfo();
+ NS_IF_ADDREF(*aReferrerInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetCookieJarSettings(
+ nsICookieJarSettings** aCookieJarSettings) {
+ *aCookieJarSettings = mDocument->CookieJarSettings();
+ NS_ADDREF(*aCookieJarSettings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetContentDisposition(nsAString& aCD) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
+ if (NS_WARN_IF(!window)) {
+ aCD.SetIsVoid(true);
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDOMWindowUtils> utils =
+ nsGlobalWindowOuter::Cast(window)->WindowUtils();
+ nsresult rv = utils->GetDocumentMetadata(u"content-disposition"_ns, aCD);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aCD.SetIsVoid(true);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetCacheKey(uint32_t* aKey) {
+ Maybe<uint32_t> cacheKey;
+
+ if (nsDocShell* docShell = nsDocShell::Cast(mDocument->GetDocShell())) {
+ cacheKey = docShell->GetCacheKeyFromCurrentEntry();
+ }
+ *aKey = cacheKey.valueOr(0);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetPostData(nsIInputStream** aStream) {
+ nsCOMPtr<nsIInputStream> postData;
+ if (nsDocShell* docShell = nsDocShell::Cast(mDocument->GetDocShell())) {
+ postData = docShell->GetPostDataFromCurrentEntry();
+ }
+
+ postData.forget(aStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetPrincipal(nsIPrincipal** aPrincipal) {
+ nsCOMPtr<nsIPrincipal> nodePrincipal = mDocument->NodePrincipal();
+ nodePrincipal.forget(aPrincipal);
+ return NS_OK;
+}
+
+already_AddRefed<nsISHEntry> WebBrowserPersistLocalDocument::GetHistory() {
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
+ if (NS_WARN_IF(!window)) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(window);
+ if (NS_WARN_IF(!webNav)) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIWebPageDescriptor> desc = do_QueryInterface(webNav);
+ if (NS_WARN_IF(!desc)) {
+ return nullptr;
+ }
+ nsCOMPtr<nsISupports> curDesc;
+ nsresult rv = desc->GetCurrentDescriptor(getter_AddRefs(curDesc));
+ // This can fail if, e.g., the document is a Print Preview.
+ if (NS_FAILED(rv) || NS_WARN_IF(!curDesc)) {
+ return nullptr;
+ }
+ nsCOMPtr<nsISHEntry> history = do_QueryInterface(curDesc);
+ return history.forget();
+}
+
+NotNull<const Encoding*> WebBrowserPersistLocalDocument::GetCharacterSet()
+ const {
+ return mDocument->GetDocumentCharacterSet();
+}
+
+uint32_t WebBrowserPersistLocalDocument::GetPersistFlags() const {
+ return mPersistFlags;
+}
+
+nsIURI* WebBrowserPersistLocalDocument::GetBaseURI() const {
+ return mDocument->GetBaseURI();
+}
+
+namespace {
+
+// Helper class for ReadResources().
+class ResourceReader final : public nsIWebBrowserPersistDocumentReceiver {
+ public:
+ ResourceReader(WebBrowserPersistLocalDocument* aParent,
+ nsIWebBrowserPersistResourceVisitor* aVisitor);
+ nsresult OnWalkDOMNode(nsINode* aNode);
+
+ // This is called both to indicate the end of the document walk
+ // and when a subdocument is (maybe asynchronously) sent to the
+ // visitor. The call to EndVisit needs to happen after both of
+ // those have finished.
+ void DocumentDone(nsresult aStatus);
+
+ NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER
+ NS_DECL_ISUPPORTS
+
+ private:
+ RefPtr<WebBrowserPersistLocalDocument> mParent;
+ nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor;
+ nsCOMPtr<nsIURI> mCurrentBaseURI;
+ uint32_t mPersistFlags;
+
+ // The number of DocumentDone calls after which EndVisit will be
+ // called on the visitor. Counts the main document if it's still
+ // being walked and any outstanding asynchronous subdocument
+ // StartPersistence calls.
+ size_t mOutstandingDocuments;
+ // Collects the status parameters to DocumentDone calls.
+ nsresult mEndStatus;
+
+ nsresult OnWalkURI(const nsACString& aURISpec,
+ nsContentPolicyType aContentPolicyType);
+ nsresult OnWalkURI(nsIURI* aURI, nsContentPolicyType aContentPolicyType);
+ nsresult OnWalkAttribute(dom::Element* aElement,
+ nsContentPolicyType aContentPolicyType,
+ const char* aAttribute,
+ const char* aNamespaceURI = "");
+ nsresult OnWalkSubframe(nsINode* aNode);
+ nsresult OnWalkSrcSet(dom::Element* aElement);
+
+ ~ResourceReader();
+
+ using IWBP = nsIWebBrowserPersist;
+};
+
+NS_IMPL_ISUPPORTS(ResourceReader, nsIWebBrowserPersistDocumentReceiver)
+
+ResourceReader::ResourceReader(WebBrowserPersistLocalDocument* aParent,
+ nsIWebBrowserPersistResourceVisitor* aVisitor)
+ : mParent(aParent),
+ mVisitor(aVisitor),
+ mCurrentBaseURI(aParent->GetBaseURI()),
+ mPersistFlags(aParent->GetPersistFlags()),
+ mOutstandingDocuments(1),
+ mEndStatus(NS_OK) {
+ MOZ_ASSERT(mCurrentBaseURI);
+}
+
+ResourceReader::~ResourceReader() { MOZ_ASSERT(mOutstandingDocuments == 0); }
+
+void ResourceReader::DocumentDone(nsresult aStatus) {
+ MOZ_ASSERT(mOutstandingDocuments > 0);
+ if (NS_SUCCEEDED(mEndStatus)) {
+ mEndStatus = aStatus;
+ }
+ if (--mOutstandingDocuments == 0) {
+ mVisitor->EndVisit(mParent, mEndStatus);
+ }
+}
+
+nsresult ResourceReader::OnWalkSubframe(nsINode* aNode) {
+ RefPtr<nsFrameLoaderOwner> loaderOwner = do_QueryObject(aNode);
+ NS_ENSURE_STATE(loaderOwner);
+ RefPtr<nsFrameLoader> loader = loaderOwner->GetFrameLoader();
+ NS_ENSURE_STATE(loader);
+
+ RefPtr<dom::BrowsingContext> context = loader->GetBrowsingContext();
+ NS_ENSURE_STATE(context);
+
+ if (loader->IsRemoteFrame()) {
+ mVisitor->VisitBrowsingContext(mParent, context);
+ return NS_OK;
+ }
+
+ ++mOutstandingDocuments;
+ ErrorResult err;
+ loader->StartPersistence(context, this, err);
+ nsresult rv = err.StealNSResult();
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_NO_CONTENT) {
+ // Just ignore frames with no content document.
+ rv = NS_OK;
+ }
+ // StartPersistence won't eventually call this if it failed,
+ // so this does so (to keep mOutstandingDocuments correct).
+ DocumentDone(rv);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+ResourceReader::OnDocumentReady(nsIWebBrowserPersistDocument* aDocument) {
+ mVisitor->VisitDocument(mParent, aDocument);
+ DocumentDone(NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ResourceReader::OnError(nsresult aFailure) {
+ DocumentDone(aFailure);
+ return NS_OK;
+}
+
+nsresult ResourceReader::OnWalkURI(nsIURI* aURI,
+ nsContentPolicyType aContentPolicyType) {
+ // Test if this URI should be persisted. By default
+ // we should assume the URI is persistable.
+ bool doNotPersistURI;
+ nsresult rv = NS_URIChainHasFlags(
+ aURI, nsIProtocolHandler::URI_NON_PERSISTABLE, &doNotPersistURI);
+ if (NS_SUCCEEDED(rv) && doNotPersistURI) {
+ return NS_OK;
+ }
+
+ nsAutoCString stringURI;
+ rv = aURI->GetSpec(stringURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mVisitor->VisitResource(mParent, stringURI, aContentPolicyType);
+}
+
+nsresult ResourceReader::OnWalkURI(const nsACString& aURISpec,
+ nsContentPolicyType aContentPolicyType) {
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+
+ rv = NS_NewURI(getter_AddRefs(uri), aURISpec, mParent->GetCharacterSet(),
+ mCurrentBaseURI);
+ if (NS_FAILED(rv)) {
+ // We don't want to break saving a page in case of a malformed URI.
+ return NS_OK;
+ }
+ return OnWalkURI(uri, aContentPolicyType);
+}
+
+static void ExtractAttribute(dom::Element* aElement, const char* aAttribute,
+ const char* aNamespaceURI, nsCString& aValue) {
+ // Find the named URI attribute on the (element) node and store
+ // a reference to the URI that maps onto a local file name
+
+ RefPtr<nsDOMAttributeMap> attrMap = aElement->Attributes();
+
+ NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI);
+ NS_ConvertASCIItoUTF16 attribute(aAttribute);
+ RefPtr<dom::Attr> attr = attrMap->GetNamedItemNS(namespaceURI, attribute);
+ if (attr) {
+ nsAutoString value;
+ attr->GetValue(value);
+ CopyUTF16toUTF8(value, aValue);
+ } else {
+ aValue.Truncate();
+ }
+}
+
+nsresult ResourceReader::OnWalkAttribute(dom::Element* aElement,
+ nsContentPolicyType aContentPolicyType,
+ const char* aAttribute,
+ const char* aNamespaceURI) {
+ nsAutoCString uriSpec;
+ ExtractAttribute(aElement, aAttribute, aNamespaceURI, uriSpec);
+ if (uriSpec.IsEmpty()) {
+ return NS_OK;
+ }
+ return OnWalkURI(uriSpec, aContentPolicyType);
+}
+
+nsresult ResourceReader::OnWalkSrcSet(dom::Element* aElement) {
+ nsAutoString srcSet;
+ if (!aElement->GetAttr(nsGkAtoms::srcset, srcSet)) {
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+ auto eachCandidate = [&](dom::ResponsiveImageCandidate&& aCandidate) {
+ if (!aCandidate.IsValid() || NS_FAILED(rv)) {
+ return;
+ }
+ rv = OnWalkURI(NS_ConvertUTF16toUTF8(aCandidate.URLString()),
+ nsIContentPolicy::TYPE_IMAGE);
+ };
+ dom::ResponsiveImageSelector::ParseSourceSet(srcSet, eachCandidate);
+ return rv;
+}
+
+static nsresult GetXMLStyleSheetLink(dom::ProcessingInstruction* aPI,
+ nsAString& aHref) {
+ nsAutoString data;
+ aPI->GetData(data);
+
+ nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::href, aHref);
+ return NS_OK;
+}
+
+nsresult ResourceReader::OnWalkDOMNode(nsINode* aNode) {
+ // Fixup xml-stylesheet processing instructions
+ if (auto nodeAsPI = dom::ProcessingInstruction::FromNode(aNode)) {
+ nsAutoString target;
+ nodeAsPI->GetTarget(target);
+ if (target.EqualsLiteral("xml-stylesheet")) {
+ nsAutoString href;
+ GetXMLStyleSheetLink(nodeAsPI, href);
+ if (!href.IsEmpty()) {
+ return OnWalkURI(NS_ConvertUTF16toUTF8(href),
+ nsIContentPolicy::TYPE_STYLESHEET);
+ }
+ }
+ return NS_OK;
+ }
+
+ // Test the node to see if it's an image, frame, iframe, css, js
+ if (auto* img = dom::HTMLImageElement::FromNode(*aNode)) {
+ MOZ_TRY(OnWalkAttribute(img, nsIContentPolicy::TYPE_IMAGE, "src"));
+ MOZ_TRY(OnWalkSrcSet(img));
+ return NS_OK;
+ }
+
+ if (aNode->IsSVGElement(nsGkAtoms::image)) {
+ MOZ_TRY(OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
+ "href"));
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
+ "href", "http://www.w3.org/1999/xlink");
+ }
+
+ if (aNode->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video)) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_MEDIA,
+ "src");
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::source)) {
+ MOZ_TRY(OnWalkSrcSet(aNode->AsElement()));
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_MEDIA,
+ "src");
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::body)) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
+ "background");
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::table)) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
+ "background");
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::tr)) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
+ "background");
+ }
+
+ if (aNode->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
+ "background");
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::script)) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_SCRIPT,
+ "src");
+ }
+
+ if (aNode->IsSVGElement(nsGkAtoms::script)) {
+ MOZ_TRY(OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_SCRIPT,
+ "href"));
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_SCRIPT,
+ "href", "http://www.w3.org/1999/xlink");
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::embed)) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_OBJECT,
+ "src");
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::object)) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_OBJECT,
+ "data");
+ }
+
+ if (auto nodeAsLink = dom::HTMLLinkElement::FromNode(aNode)) {
+ // Test if the link has a rel value indicating it to be a stylesheet
+ nsAutoString linkRel;
+ nodeAsLink->GetRel(linkRel);
+ if (!linkRel.IsEmpty()) {
+ nsReadingIterator<char16_t> start;
+ nsReadingIterator<char16_t> end;
+ nsReadingIterator<char16_t> current;
+
+ linkRel.BeginReading(start);
+ linkRel.EndReading(end);
+
+ // Walk through space delimited string looking for "stylesheet"
+ for (current = start; current != end; ++current) {
+ // Ignore whitespace
+ if (nsCRT::IsAsciiSpace(*current)) {
+ continue;
+ }
+
+ // Grab the next space delimited word
+ nsReadingIterator<char16_t> startWord = current;
+ do {
+ ++current;
+ } while (current != end && !nsCRT::IsAsciiSpace(*current));
+
+ // Store the link for fix up if it says "stylesheet"
+ if (Substring(startWord, current)
+ .LowerCaseEqualsLiteral("stylesheet")) {
+ OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_STYLESHEET,
+ "href");
+ return NS_OK;
+ }
+ if (current == end) {
+ break;
+ }
+ }
+ }
+ return NS_OK;
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::frame)) {
+ return OnWalkSubframe(aNode);
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::iframe) &&
+ !(mPersistFlags & IWBP::PERSIST_FLAGS_IGNORE_IFRAMES)) {
+ return OnWalkSubframe(aNode);
+ }
+
+ auto nodeAsInput = dom::HTMLInputElement::FromNode(aNode);
+ if (nodeAsInput) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
+ "src");
+ }
+
+ return NS_OK;
+}
+
+// Helper class for node rewriting in writeContent().
+class PersistNodeFixup final : public nsIDocumentEncoderNodeFixup {
+ public:
+ PersistNodeFixup(WebBrowserPersistLocalDocument* aParent,
+ nsIWebBrowserPersistURIMap* aMap, nsIURI* aTargetURI);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOCUMENTENCODERNODEFIXUP
+ private:
+ virtual ~PersistNodeFixup() = default;
+ RefPtr<WebBrowserPersistLocalDocument> mParent;
+ nsClassHashtable<nsCStringHashKey, nsCString> mMap;
+ nsCOMPtr<nsIURI> mCurrentBaseURI;
+ nsCOMPtr<nsIURI> mTargetBaseURI;
+
+ bool IsFlagSet(uint32_t aFlag) const {
+ return mParent->GetPersistFlags() & aFlag;
+ }
+
+ nsresult GetNodeToFixup(nsINode* aNodeIn, nsINode** aNodeOut);
+ nsresult FixupURI(nsAString& aURI);
+ nsresult FixupAttribute(nsINode* aNode, const char* aAttribute,
+ const char* aNamespaceURI = "");
+ nsresult FixupAnchor(nsINode* aNode);
+ nsresult FixupXMLStyleSheetLink(dom::ProcessingInstruction* aPI,
+ const nsAString& aHref);
+
+ nsresult FixupSrcSet(nsINode*);
+
+ using IWBP = nsIWebBrowserPersist;
+};
+
+NS_IMPL_ISUPPORTS(PersistNodeFixup, nsIDocumentEncoderNodeFixup)
+
+PersistNodeFixup::PersistNodeFixup(WebBrowserPersistLocalDocument* aParent,
+ nsIWebBrowserPersistURIMap* aMap,
+ nsIURI* aTargetURI)
+ : mParent(aParent),
+ mCurrentBaseURI(aParent->GetBaseURI()),
+ mTargetBaseURI(aTargetURI) {
+ if (aMap) {
+ uint32_t mapSize;
+ nsresult rv = aMap->GetNumMappedURIs(&mapSize);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ for (uint32_t i = 0; i < mapSize; ++i) {
+ nsAutoCString urlFrom;
+ auto urlTo = MakeUnique<nsCString>();
+
+ rv = aMap->GetURIMapping(i, urlFrom, *urlTo);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_SUCCEEDED(rv)) {
+ mMap.InsertOrUpdate(urlFrom, std::move(urlTo));
+ }
+ }
+ }
+}
+
+nsresult PersistNodeFixup::GetNodeToFixup(nsINode* aNodeIn,
+ nsINode** aNodeOut) {
+ // Avoid mixups in FixupNode that could leak objects; this goes
+ // against the usual out parameter convention, but it's a private
+ // method so shouldn't be a problem.
+ MOZ_ASSERT(!*aNodeOut);
+
+ if (!IsFlagSet(IWBP::PERSIST_FLAGS_FIXUP_ORIGINAL_DOM)) {
+ ErrorResult rv;
+ *aNodeOut = aNodeIn->CloneNode(false, rv).take();
+ return rv.StealNSResult();
+ }
+
+ NS_ADDREF(*aNodeOut = aNodeIn);
+ return NS_OK;
+}
+
+nsresult PersistNodeFixup::FixupURI(nsAString& aURI) {
+ // get the current location of the file (absolutized)
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI, mParent->GetCharacterSet(),
+ mCurrentBaseURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString spec;
+ rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const nsCString* replacement = mMap.Get(spec);
+ if (!replacement) {
+ // Note that most callers ignore this "failure".
+ return NS_ERROR_FAILURE;
+ }
+ if (!replacement->IsEmpty()) {
+ CopyUTF8toUTF16(*replacement, aURI);
+ }
+ return NS_OK;
+}
+
+nsresult PersistNodeFixup::FixupSrcSet(nsINode* aNode) {
+ dom::Element* element = aNode->AsElement();
+ nsAutoString originalSrcSet;
+ if (!element->GetAttr(nsGkAtoms::srcset, originalSrcSet)) {
+ return NS_OK;
+ }
+ nsAutoString newSrcSet;
+ bool first = true;
+ auto eachCandidate = [&](dom::ResponsiveImageCandidate&& aCandidate) {
+ if (!aCandidate.IsValid()) {
+ return;
+ }
+ if (!first) {
+ newSrcSet.AppendLiteral(", ");
+ }
+ first = false;
+ nsAutoString uri(aCandidate.URLString());
+ FixupURI(uri);
+ newSrcSet.Append(uri);
+ aCandidate.AppendDescriptors(newSrcSet);
+ };
+ dom::ResponsiveImageSelector::ParseSourceSet(originalSrcSet, eachCandidate);
+ element->SetAttr(nsGkAtoms::srcset, newSrcSet, IgnoreErrors());
+ return NS_OK;
+}
+
+nsresult PersistNodeFixup::FixupAttribute(nsINode* aNode,
+ const char* aAttribute,
+ const char* aNamespaceURI) {
+ MOZ_ASSERT(aNode->IsElement());
+ dom::Element* element = aNode->AsElement();
+
+ RefPtr<nsDOMAttributeMap> attrMap = element->Attributes();
+
+ NS_ConvertASCIItoUTF16 attribute(aAttribute);
+ NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI);
+ RefPtr<dom::Attr> attr = attrMap->GetNamedItemNS(namespaceURI, attribute);
+ nsresult rv = NS_OK;
+ if (attr) {
+ nsString uri;
+ attr->GetValue(uri);
+ rv = FixupURI(uri);
+ if (NS_SUCCEEDED(rv)) {
+ attr->SetValue(uri, IgnoreErrors());
+ }
+ }
+
+ return rv;
+}
+
+nsresult PersistNodeFixup::FixupAnchor(nsINode* aNode) {
+ if (IsFlagSet(IWBP::PERSIST_FLAGS_DONT_FIXUP_LINKS)) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aNode->IsElement());
+ dom::Element* element = aNode->AsElement();
+
+ RefPtr<nsDOMAttributeMap> attrMap = element->Attributes();
+
+ // Make all anchor links absolute so they point off onto the Internet
+ nsString attribute(u"href"_ns);
+ RefPtr<dom::Attr> attr = attrMap->GetNamedItem(attribute);
+ if (attr) {
+ nsString oldValue;
+ attr->GetValue(oldValue);
+ NS_ConvertUTF16toUTF8 oldCValue(oldValue);
+
+ // Skip empty values and self-referencing bookmarks
+ if (oldCValue.IsEmpty() || oldCValue.CharAt(0) == '#') {
+ return NS_OK;
+ }
+
+ // if saving file to same location, we don't need to do any fixup
+ bool isEqual;
+ if (mTargetBaseURI &&
+ NS_SUCCEEDED(mCurrentBaseURI->Equals(mTargetBaseURI, &isEqual)) &&
+ isEqual) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> relativeURI;
+ relativeURI = IsFlagSet(IWBP::PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION)
+ ? mTargetBaseURI
+ : mCurrentBaseURI;
+ // Make a new URI to replace the current one
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(newURI), oldCValue,
+ mParent->GetCharacterSet(), relativeURI);
+ if (NS_SUCCEEDED(rv) && newURI) {
+ Unused << NS_MutateURI(newURI).SetUserPass(""_ns).Finalize(newURI);
+ nsAutoCString uriSpec;
+ rv = newURI->GetSpec(uriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ attr->SetValue(NS_ConvertUTF8toUTF16(uriSpec), IgnoreErrors());
+ }
+ }
+
+ return NS_OK;
+}
+
+static void AppendXMLAttr(const nsAString& key, const nsAString& aValue,
+ nsAString& aBuffer) {
+ if (!aBuffer.IsEmpty()) {
+ aBuffer.Append(' ');
+ }
+ aBuffer.Append(key);
+ aBuffer.AppendLiteral(R"(=")");
+ for (size_t i = 0; i < aValue.Length(); ++i) {
+ switch (aValue[i]) {
+ case '&':
+ aBuffer.AppendLiteral("&amp;");
+ break;
+ case '<':
+ aBuffer.AppendLiteral("&lt;");
+ break;
+ case '>':
+ aBuffer.AppendLiteral("&gt;");
+ break;
+ case '"':
+ aBuffer.AppendLiteral("&quot;");
+ break;
+ default:
+ aBuffer.Append(aValue[i]);
+ break;
+ }
+ }
+ aBuffer.Append('"');
+}
+
+nsresult PersistNodeFixup::FixupXMLStyleSheetLink(
+ dom::ProcessingInstruction* aPI, const nsAString& aHref) {
+ NS_ENSURE_ARG_POINTER(aPI);
+
+ nsAutoString data;
+ aPI->GetData(data);
+
+ nsAutoString href;
+ nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::href, href);
+
+ // Construct and set a new data value for the xml-stylesheet
+ if (!aHref.IsEmpty() && !href.IsEmpty()) {
+ nsAutoString alternate;
+ nsAutoString charset;
+ nsAutoString title;
+ nsAutoString type;
+ nsAutoString media;
+
+ nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::alternate,
+ alternate);
+ nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::charset, charset);
+ nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::title, title);
+ nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::type, type);
+ nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::media, media);
+
+ nsAutoString newData;
+ AppendXMLAttr(u"href"_ns, aHref, newData);
+ if (!title.IsEmpty()) {
+ AppendXMLAttr(u"title"_ns, title, newData);
+ }
+ if (!media.IsEmpty()) {
+ AppendXMLAttr(u"media"_ns, media, newData);
+ }
+ if (!type.IsEmpty()) {
+ AppendXMLAttr(u"type"_ns, type, newData);
+ }
+ if (!charset.IsEmpty()) {
+ AppendXMLAttr(u"charset"_ns, charset, newData);
+ }
+ if (!alternate.IsEmpty()) {
+ AppendXMLAttr(u"alternate"_ns, alternate, newData);
+ }
+ aPI->SetData(newData, IgnoreErrors());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PersistNodeFixup::FixupNode(nsINode* aNodeIn, bool* aSerializeCloneKids,
+ nsINode** aNodeOut) {
+ *aNodeOut = nullptr;
+ *aSerializeCloneKids = false;
+
+ uint16_t type = aNodeIn->NodeType();
+ if (type != nsINode::ELEMENT_NODE &&
+ type != nsINode::PROCESSING_INSTRUCTION_NODE) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aNodeIn->IsContent());
+
+ // Fixup xml-stylesheet processing instructions
+ if (auto nodeAsPI = dom::ProcessingInstruction::FromNode(aNodeIn)) {
+ nsAutoString target;
+ nodeAsPI->GetTarget(target);
+ if (target.EqualsLiteral("xml-stylesheet")) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ MOZ_ASSERT((*aNodeOut)->IsProcessingInstruction());
+ auto nodeAsPI = static_cast<dom::ProcessingInstruction*>(*aNodeOut);
+ nsAutoString href;
+ GetXMLStyleSheetLink(nodeAsPI, href);
+ if (!href.IsEmpty()) {
+ FixupURI(href);
+ FixupXMLStyleSheetLink(nodeAsPI, href);
+ }
+ }
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aNodeIn);
+ if (!content) {
+ return NS_OK;
+ }
+
+ // BASE elements are replaced by a comment so relative links are not hosed.
+ if (!IsFlagSet(IWBP::PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS) &&
+ content->IsHTMLElement(nsGkAtoms::base)) {
+ // Base uses HTMLSharedElement, which would be awkward to implement
+ // FromContent on, since it represents multiple elements. Since we've
+ // already checked IsHTMLElement here, just cast as we were doing.
+ auto* base = static_cast<dom::HTMLSharedElement*>(content.get());
+ dom::Document* ownerDoc = base->OwnerDoc();
+
+ nsAutoString href;
+ base->GetHref(href); // Doesn't matter if this fails
+ nsAutoString commentText;
+ commentText.AssignLiteral(" base ");
+ if (!href.IsEmpty()) {
+ commentText += u"href=\""_ns + href + u"\" "_ns;
+ }
+ *aNodeOut = ownerDoc->CreateComment(commentText).take();
+ return NS_OK;
+ }
+
+ // Fix up href and file links in the elements
+ RefPtr<dom::HTMLAnchorElement> nodeAsAnchor =
+ dom::HTMLAnchorElement::FromNode(content);
+ if (nodeAsAnchor) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAnchor(*aNodeOut);
+ }
+ return rv;
+ }
+
+ RefPtr<dom::HTMLAreaElement> nodeAsArea =
+ dom::HTMLAreaElement::FromNode(content);
+ if (nodeAsArea) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAnchor(*aNodeOut);
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::body)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "background");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::table)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "background");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::tr)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "background");
+ }
+ return rv;
+ }
+
+ if (content->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "background");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::img)) {
+ MOZ_TRY(GetNodeToFixup(aNodeIn, aNodeOut));
+ if (!*aNodeOut) {
+ return NS_OK;
+ }
+
+ // Disable image loads
+ nsCOMPtr<nsIImageLoadingContent> imgCon = do_QueryInterface(*aNodeOut);
+ if (imgCon) {
+ imgCon->SetLoadingEnabled(false);
+ }
+ // FIXME(emilio): Why fixing up <img href>? Looks bogus
+ FixupAnchor(*aNodeOut);
+ FixupAttribute(*aNodeOut, "src");
+ FixupSrcSet(*aNodeOut);
+ return NS_OK;
+ }
+
+ if (content->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::source)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ FixupSrcSet(*aNodeOut);
+ }
+ return rv;
+ }
+
+ if (content->IsSVGElement(nsGkAtoms::image)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ // Disable image loads
+ nsCOMPtr<nsIImageLoadingContent> imgCon = do_QueryInterface(*aNodeOut);
+ if (imgCon) imgCon->SetLoadingEnabled(false);
+
+ // FixupAnchor(*aNodeOut); // XXXjwatt: is this line needed?
+ FixupAttribute(*aNodeOut, "href");
+ FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::script)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ if (content->IsSVGElement(nsGkAtoms::script)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "href");
+ FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::embed)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::object)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "data");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::link)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ // First see if the link represents linked content
+ rv = FixupAttribute(*aNodeOut, "href");
+ if (NS_FAILED(rv)) {
+ // Perhaps this link is actually an anchor to related content
+ FixupAnchor(*aNodeOut);
+ }
+ // TODO if "type" attribute == "text/css"
+ // fixup stylesheet
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::frame)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::iframe)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ RefPtr<dom::HTMLInputElement> nodeAsInput =
+ dom::HTMLInputElement::FromNodeOrNull(content);
+ if (nodeAsInput) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ // Disable image loads
+ nsCOMPtr<nsIImageLoadingContent> imgCon = do_QueryInterface(*aNodeOut);
+ if (imgCon) {
+ imgCon->SetLoadingEnabled(false);
+ }
+
+ FixupAttribute(*aNodeOut, "src");
+
+ nsAutoString valueStr;
+ constexpr auto valueAttr = u"value"_ns;
+ // Update element node attributes with user-entered form state
+ RefPtr<dom::HTMLInputElement> outElt =
+ dom::HTMLInputElement::FromNode((*aNodeOut)->AsContent());
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(*aNodeOut);
+ switch (formControl->ControlType()) {
+ case FormControlType::InputEmail:
+ case FormControlType::InputSearch:
+ case FormControlType::InputText:
+ case FormControlType::InputTel:
+ case FormControlType::InputUrl:
+ case FormControlType::InputNumber:
+ case FormControlType::InputRange:
+ case FormControlType::InputDate:
+ case FormControlType::InputTime:
+ case FormControlType::InputColor:
+ nodeAsInput->GetValue(valueStr, dom::CallerType::System);
+ // Avoid superfluous value="" serialization
+ if (valueStr.IsEmpty()) {
+ outElt->RemoveAttribute(valueAttr, IgnoreErrors());
+ } else {
+ outElt->SetAttribute(valueAttr, valueStr, IgnoreErrors());
+ }
+ break;
+ case FormControlType::InputCheckbox:
+ case FormControlType::InputRadio:
+ outElt->SetDefaultChecked(nodeAsInput->Checked(), IgnoreErrors());
+ break;
+ default:
+ break;
+ }
+ }
+ return rv;
+ }
+
+ dom::HTMLTextAreaElement* nodeAsTextArea =
+ dom::HTMLTextAreaElement::FromNode(content);
+ if (nodeAsTextArea) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ // Tell the document encoder to serialize the text child we create below
+ *aSerializeCloneKids = true;
+
+ nsAutoString valueStr;
+ nodeAsTextArea->GetValue(valueStr);
+
+ (*aNodeOut)->SetTextContent(valueStr, IgnoreErrors());
+ }
+ return rv;
+ }
+
+ dom::HTMLOptionElement* nodeAsOption =
+ dom::HTMLOptionElement::FromNode(content);
+ if (nodeAsOption) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ dom::HTMLOptionElement* outElt =
+ dom::HTMLOptionElement::FromNode((*aNodeOut)->AsContent());
+ bool selected = nodeAsOption->Selected();
+ outElt->SetDefaultSelected(selected, IgnoreErrors());
+ }
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+} // unnamed namespace
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::ReadResources(
+ nsIWebBrowserPersistResourceVisitor* aVisitor) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIWebBrowserPersistResourceVisitor> visitor = aVisitor;
+
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
+
+ ErrorResult err;
+ RefPtr<dom::TreeWalker> walker = mDocument->CreateTreeWalker(
+ *mDocument,
+ dom::NodeFilter_Binding::SHOW_ELEMENT |
+ dom::NodeFilter_Binding::SHOW_DOCUMENT |
+ dom::NodeFilter_Binding::SHOW_PROCESSING_INSTRUCTION,
+ nullptr, err);
+
+ if (NS_WARN_IF(err.Failed())) {
+ return err.StealNSResult();
+ }
+ MOZ_ASSERT(walker);
+
+ RefPtr<ResourceReader> reader = new ResourceReader(this, aVisitor);
+ nsCOMPtr<nsINode> currentNode = walker->CurrentNode();
+ do {
+ rv = reader->OnWalkDOMNode(currentNode);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+
+ ErrorResult err;
+ currentNode = walker->NextNode(err);
+ if (NS_WARN_IF(err.Failed())) {
+ err.SuppressException();
+ break;
+ }
+ } while (currentNode);
+ reader->DocumentDone(rv);
+ // If NS_FAILED(rv), it was / will be reported by an EndVisit call
+ // via DocumentDone. This method must return a failure if and
+ // only if visitor won't be invoked.
+ return NS_OK;
+}
+
+static uint32_t ConvertEncoderFlags(uint32_t aEncoderFlags) {
+ uint32_t encoderFlags = 0;
+
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_SELECTION_ONLY)
+ encoderFlags |= nsIDocumentEncoder::OutputSelectionOnly;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_FORMATTED)
+ encoderFlags |= nsIDocumentEncoder::OutputFormatted;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_RAW)
+ encoderFlags |= nsIDocumentEncoder::OutputRaw;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_BODY_ONLY)
+ encoderFlags |= nsIDocumentEncoder::OutputBodyOnly;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_PREFORMATTED)
+ encoderFlags |= nsIDocumentEncoder::OutputPreformatted;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_WRAP)
+ encoderFlags |= nsIDocumentEncoder::OutputWrap;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_FORMAT_FLOWED)
+ encoderFlags |= nsIDocumentEncoder::OutputFormatFlowed;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ABSOLUTE_LINKS)
+ encoderFlags |= nsIDocumentEncoder::OutputAbsoluteLinks;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_BASIC_ENTITIES)
+ encoderFlags |= nsIDocumentEncoder::OutputEncodeBasicEntities;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_CR_LINEBREAKS)
+ encoderFlags |= nsIDocumentEncoder::OutputCRLineBreak;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_LF_LINEBREAKS)
+ encoderFlags |= nsIDocumentEncoder::OutputLFLineBreak;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_NOSCRIPT_CONTENT)
+ encoderFlags |= nsIDocumentEncoder::OutputNoScriptContent;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_NOFRAMES_CONTENT)
+ encoderFlags |= nsIDocumentEncoder::OutputNoFramesContent;
+
+ return encoderFlags;
+}
+
+static bool ContentTypeEncoderExists(const nsACString& aType) {
+ return do_getDocumentTypeSupportedForEncoding(
+ PromiseFlatCString(aType).get());
+}
+
+void WebBrowserPersistLocalDocument::DecideContentType(
+ nsACString& aContentType) {
+ if (aContentType.IsEmpty()) {
+ if (NS_WARN_IF(NS_FAILED(GetContentType(aContentType)))) {
+ aContentType.Truncate();
+ }
+ }
+ if (!aContentType.IsEmpty() && !ContentTypeEncoderExists(aContentType)) {
+ aContentType.Truncate();
+ }
+ if (aContentType.IsEmpty()) {
+ aContentType.AssignLiteral("text/html");
+ }
+}
+
+nsresult WebBrowserPersistLocalDocument::GetDocEncoder(
+ const nsACString& aContentType, uint32_t aEncoderFlags,
+ nsIDocumentEncoder** aEncoder) {
+ nsCOMPtr<nsIDocumentEncoder> encoder =
+ do_createDocumentEncoder(PromiseFlatCString(aContentType).get());
+ NS_ENSURE_TRUE(encoder, NS_ERROR_FAILURE);
+
+ nsresult rv =
+ encoder->NativeInit(mDocument, NS_ConvertASCIItoUTF16(aContentType),
+ ConvertEncoderFlags(aEncoderFlags));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ nsAutoCString charSet;
+ rv = GetCharacterSet(charSet);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ rv = encoder->SetCharset(charSet);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ encoder.forget(aEncoder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::WriteContent(
+ nsIOutputStream* aStream, nsIWebBrowserPersistURIMap* aMap,
+ const nsACString& aRequestedContentType, uint32_t aEncoderFlags,
+ uint32_t aWrapColumn, nsIWebBrowserPersistWriteCompletion* aCompletion) {
+ NS_ENSURE_ARG_POINTER(aStream);
+ NS_ENSURE_ARG_POINTER(aCompletion);
+ nsAutoCString contentType(aRequestedContentType);
+ DecideContentType(contentType);
+
+ nsCOMPtr<nsIDocumentEncoder> encoder;
+ nsresult rv =
+ GetDocEncoder(contentType, aEncoderFlags, getter_AddRefs(encoder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aWrapColumn != 0 &&
+ (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_WRAP)) {
+ encoder->SetWrapColumn(aWrapColumn);
+ }
+
+ nsCOMPtr<nsIURI> targetURI;
+ if (aMap) {
+ nsAutoCString targetURISpec;
+ rv = aMap->GetTargetBaseURI(targetURISpec);
+ if (NS_SUCCEEDED(rv) && !targetURISpec.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(targetURI), targetURISpec);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+ } else if (mPersistFlags &
+ nsIWebBrowserPersist::PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ rv = encoder->SetNodeFixup(new PersistNodeFixup(this, aMap, targetURI));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ rv = encoder->EncodeToStream(aStream);
+ aCompletion->OnFinish(this, aStream, contentType, rv);
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/dom/webbrowserpersist/WebBrowserPersistLocalDocument.h b/dom/webbrowserpersist/WebBrowserPersistLocalDocument.h
new file mode 100644
index 0000000000..c1af8acd54
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistLocalDocument.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistLocalDocument_h__
+#define WebBrowserPersistLocalDocument_h__
+
+#include "mozilla/NotNull.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIURI.h"
+#include "nsIWebBrowserPersistDocument.h"
+
+class nsIDocumentEncoder;
+class nsISHEntry;
+
+namespace mozilla {
+
+namespace dom {
+class Document;
+}
+
+class WebBrowserPersistLocalDocument final
+ : public nsIWebBrowserPersistDocument {
+ public:
+ explicit WebBrowserPersistLocalDocument(dom::Document* aDocument);
+
+ NotNull<const Encoding*> GetCharacterSet() const;
+ uint32_t GetPersistFlags() const;
+ nsIURI* GetBaseURI() const;
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIWEBBROWSERPERSISTDOCUMENT
+
+ NS_DECL_CYCLE_COLLECTION_CLASS(WebBrowserPersistLocalDocument)
+
+ private:
+ RefPtr<dom::Document> mDocument;
+ uint32_t mPersistFlags;
+
+ void DecideContentType(nsACString& aContentType);
+ nsresult GetDocEncoder(const nsACString& aContentType, uint32_t aEncoderFlags,
+ nsIDocumentEncoder** aEncoder);
+
+ virtual ~WebBrowserPersistLocalDocument();
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistLocalDocument_h__
diff --git a/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp b/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp
new file mode 100644
index 0000000000..a4e7c96c85
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistRemoteDocument.h"
+#include "WebBrowserPersistDocumentParent.h"
+#include "WebBrowserPersistResourcesParent.h"
+#include "WebBrowserPersistSerializeParent.h"
+#include "mozilla/Unused.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/net/CookieJarSettings.h"
+
+#include "nsDebug.h"
+#include "nsIPrincipal.h"
+#include "nsISHEntry.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(WebBrowserPersistRemoteDocument, nsIWebBrowserPersistDocument)
+
+WebBrowserPersistRemoteDocument ::WebBrowserPersistRemoteDocument(
+ WebBrowserPersistDocumentParent* aActor, const Attrs& aAttrs,
+ nsIInputStream* aPostData)
+ : mActor(aActor), mAttrs(aAttrs), mPostData(aPostData) {
+ auto principalOrErr = ipc::PrincipalInfoToPrincipal(mAttrs.principal());
+ if (principalOrErr.isOk()) {
+ mPrincipal = principalOrErr.unwrap();
+ } else {
+ NS_WARNING("Failed to obtain principal!");
+ }
+
+ net::CookieJarSettings::Deserialize(mAttrs.cookieJarSettings(),
+ getter_AddRefs(mCookieJarSettings));
+}
+
+WebBrowserPersistRemoteDocument::~WebBrowserPersistRemoteDocument() {
+ if (mActor) {
+ Unused << WebBrowserPersistDocumentParent::Send__delete__(mActor);
+ // That will call mActor->ActorDestroy, which calls this->ActorDestroy
+ // (whether or not the IPC send succeeds).
+ }
+ MOZ_ASSERT(!mActor);
+}
+
+void WebBrowserPersistRemoteDocument::ActorDestroy(void) { mActor = nullptr; }
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetIsClosed(bool* aIsClosed) {
+ *aIsClosed = !mActor;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetIsPrivate(bool* aIsPrivate) {
+ *aIsPrivate = mAttrs.isPrivate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetDocumentURI(nsACString& aURISpec) {
+ aURISpec = mAttrs.documentURI();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetBaseURI(nsACString& aURISpec) {
+ aURISpec = mAttrs.baseURI();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetContentType(nsACString& aContentType) {
+ aContentType = mAttrs.contentType();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetCharacterSet(nsACString& aCharSet) {
+ aCharSet = mAttrs.characterSet();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetTitle(nsAString& aTitle) {
+ aTitle = mAttrs.title();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetReferrerInfo(
+ nsIReferrerInfo** aReferrerInfo) {
+ *aReferrerInfo = mAttrs.referrerInfo();
+ NS_IF_ADDREF(*aReferrerInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetCookieJarSettings(
+ nsICookieJarSettings** aCookieJarSettings) {
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings = mCookieJarSettings;
+ cookieJarSettings.forget(aCookieJarSettings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetContentDisposition(nsAString& aDisp) {
+ aDisp = mAttrs.contentDisposition();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetCacheKey(uint32_t* aCacheKey) {
+ *aCacheKey = mAttrs.sessionHistoryCacheKey();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetPersistFlags(uint32_t* aFlags) {
+ *aFlags = mAttrs.persistFlags();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::SetPersistFlags(uint32_t aFlags) {
+ if (!mActor) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!mActor->SendSetPersistFlags(aFlags)) {
+ return NS_ERROR_FAILURE;
+ }
+ mAttrs.persistFlags() = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetPostData(nsIInputStream** aStream) {
+ nsCOMPtr<nsIInputStream> stream = mPostData;
+ stream.forget(aStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetPrincipal(nsIPrincipal** aPrincipal) {
+ nsCOMPtr<nsIPrincipal> nodePrincipal = mPrincipal;
+ nodePrincipal.forget(aPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::ReadResources(
+ nsIWebBrowserPersistResourceVisitor* aVisitor) {
+ if (!mActor) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<WebBrowserPersistResourcesParent> subActor =
+ new WebBrowserPersistResourcesParent(this, aVisitor);
+ return mActor->SendPWebBrowserPersistResourcesConstructor(
+ subActor.forget().take())
+ ? NS_OK
+ : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::WriteContent(
+ nsIOutputStream* aStream, nsIWebBrowserPersistURIMap* aMap,
+ const nsACString& aRequestedContentType, uint32_t aEncoderFlags,
+ uint32_t aWrapColumn, nsIWebBrowserPersistWriteCompletion* aCompletion) {
+ if (!mActor) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ WebBrowserPersistURIMap map;
+ uint32_t numMappedURIs;
+ if (aMap) {
+ rv = aMap->GetTargetBaseURI(map.targetBaseURI());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aMap->GetNumMappedURIs(&numMappedURIs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t i = 0; i < numMappedURIs; ++i) {
+ WebBrowserPersistURIMapEntry& nextEntry =
+ *(map.mapURIs().AppendElement());
+ rv = aMap->GetURIMapping(i, nextEntry.mapFrom(), nextEntry.mapTo());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ auto* subActor =
+ new WebBrowserPersistSerializeParent(this, aStream, aCompletion);
+ nsCString requestedContentType(aRequestedContentType); // Sigh.
+ return mActor->SendPWebBrowserPersistSerializeConstructor(
+ subActor, map, requestedContentType, aEncoderFlags, aWrapColumn)
+ ? NS_OK
+ : NS_ERROR_FAILURE;
+}
+
+// Forcing WebBrowserPersistRemoteDocument to implement GetHistory is the
+// easiest way to ensure that we can call GetHistory in
+// WebBrowserPersistDocumentChild::Start
+already_AddRefed<nsISHEntry> WebBrowserPersistRemoteDocument::GetHistory() {
+ MOZ_CRASH("We should not call GetHistory on WebBrowserPersistRemoteDocument");
+ return nullptr;
+}
+
+} // namespace mozilla
diff --git a/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.h b/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.h
new file mode 100644
index 0000000000..e33976b3d2
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistRemoteDocument_h__
+#define WebBrowserPersistRemoteDocument_h__
+
+#include "mozilla/Maybe.h"
+#include "mozilla/PWebBrowserPersistDocumentParent.h"
+#include "nsCOMPtr.h"
+#include "nsIWebBrowserPersistDocument.h"
+#include "nsIInputStream.h"
+
+class nsIPrincipal;
+
+// This class is the XPCOM half of the glue between the
+// nsIWebBrowserPersistDocument interface and a remote document; it is
+// created by WebBrowserPersistDocumentParent when (and if) it
+// receives the information needed to populate the interface's
+// properties.
+//
+// This object has a normal refcounted lifetime. The corresponding
+// IPC actor holds a weak reference to this class; when the last
+// strong reference is released, it sends an IPC delete message and
+// thereby removes that reference.
+
+namespace mozilla {
+
+class WebBrowserPersistDocumentParent;
+
+class WebBrowserPersistRemoteDocument final
+ : public nsIWebBrowserPersistDocument {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBBROWSERPERSISTDOCUMENT
+
+ private:
+ using Attrs = WebBrowserPersistDocumentAttrs;
+ WebBrowserPersistDocumentParent* mActor;
+ Attrs mAttrs;
+ nsCOMPtr<nsISHEntry> mSHEntry;
+ nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
+ nsCOMPtr<nsIInputStream> mPostData;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+
+ friend class WebBrowserPersistDocumentParent;
+ WebBrowserPersistRemoteDocument(WebBrowserPersistDocumentParent* aActor,
+ const Attrs& aAttrs,
+ nsIInputStream* aPostData);
+ ~WebBrowserPersistRemoteDocument();
+
+ void ActorDestroy(void);
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistRemoteDocument_h__
diff --git a/dom/webbrowserpersist/WebBrowserPersistResourcesChild.cpp b/dom/webbrowserpersist/WebBrowserPersistResourcesChild.cpp
new file mode 100644
index 0000000000..7ec6f7ebfb
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistResourcesChild.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistResourcesChild.h"
+
+#include "WebBrowserPersistDocumentChild.h"
+#include "mozilla/dom/PContentChild.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(WebBrowserPersistResourcesChild,
+ nsIWebBrowserPersistResourceVisitor)
+
+WebBrowserPersistResourcesChild::WebBrowserPersistResourcesChild() = default;
+
+WebBrowserPersistResourcesChild::~WebBrowserPersistResourcesChild() = default;
+
+NS_IMETHODIMP
+WebBrowserPersistResourcesChild::VisitResource(
+ nsIWebBrowserPersistDocument* aDocument, const nsACString& aURI,
+ nsContentPolicyType aContentPolicyType) {
+ nsCString copiedURI(aURI); // Yay, XPIDL/IPDL mismatch.
+ SendVisitResource(copiedURI, aContentPolicyType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistResourcesChild::VisitDocument(
+ nsIWebBrowserPersistDocument* aDocument,
+ nsIWebBrowserPersistDocument* aSubDocument) {
+ auto* subActor = new WebBrowserPersistDocumentChild();
+ // As a consequence of how PWebBrowserPersistDocumentConstructor
+ // can be sent by both the parent and the child, we must pass the
+ // aBrowser and outerWindowID arguments here, but the values are
+ // ignored by the parent. In particular, the BrowserChild in which
+ // persistence started does not necessarily exist at this point;
+ // see bug 1203602.
+ if (!Manager()->Manager()->SendPWebBrowserPersistDocumentConstructor(
+ subActor, nullptr, nullptr)) {
+ // NOTE: subActor is freed at this point.
+ return NS_ERROR_FAILURE;
+ }
+ // ...but here, IPC won't free subActor until after this returns
+ // to the event loop.
+
+ // The order of these two messages will be preserved, because
+ // they're the same toplevel protocol and priority.
+ //
+ // With this ordering, it's always the transition out of START
+ // state that causes a document's parent actor to be exposed to
+ // XPCOM (for both parent->child and child->parent construction),
+ // which simplifies the lifetime management.
+ SendVisitDocument(WrapNotNull(subActor));
+ subActor->Start(aSubDocument);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistResourcesChild::VisitBrowsingContext(
+ nsIWebBrowserPersistDocument* aDocument,
+ dom::BrowsingContext* aBrowsingContext) {
+ SendVisitBrowsingContext(aBrowsingContext);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistResourcesChild::EndVisit(
+ nsIWebBrowserPersistDocument* aDocument, nsresult aStatus) {
+ Send__delete__(this, aStatus);
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/dom/webbrowserpersist/WebBrowserPersistResourcesChild.h b/dom/webbrowserpersist/WebBrowserPersistResourcesChild.h
new file mode 100644
index 0000000000..ff0a11ee8f
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistResourcesChild.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistResourcesChild_h__
+#define WebBrowserPersistResourcesChild_h__
+
+#include "mozilla/PWebBrowserPersistResourcesChild.h"
+
+#include "nsIWebBrowserPersistDocument.h"
+
+namespace mozilla {
+
+class WebBrowserPersistResourcesChild final
+ : public PWebBrowserPersistResourcesChild,
+ public nsIWebBrowserPersistResourceVisitor {
+ public:
+ WebBrowserPersistResourcesChild();
+
+ NS_DECL_NSIWEBBROWSERPERSISTRESOURCEVISITOR
+ NS_DECL_ISUPPORTS
+ private:
+ virtual ~WebBrowserPersistResourcesChild();
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistDocumentChild_h__
diff --git a/dom/webbrowserpersist/WebBrowserPersistResourcesParent.cpp b/dom/webbrowserpersist/WebBrowserPersistResourcesParent.cpp
new file mode 100644
index 0000000000..888e79fcd7
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistResourcesParent.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistResourcesParent.h"
+
+#include "nsThreadUtils.h"
+
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(WebBrowserPersistResourcesParent,
+ nsIWebBrowserPersistDocumentReceiver)
+
+WebBrowserPersistResourcesParent::WebBrowserPersistResourcesParent(
+ nsIWebBrowserPersistDocument* aDocument,
+ nsIWebBrowserPersistResourceVisitor* aVisitor)
+ : mDocument(aDocument), mVisitor(aVisitor) {
+ MOZ_ASSERT(aDocument);
+ MOZ_ASSERT(aVisitor);
+}
+
+WebBrowserPersistResourcesParent::~WebBrowserPersistResourcesParent() = default;
+
+void WebBrowserPersistResourcesParent::ActorDestroy(ActorDestroyReason aWhy) {
+ if (aWhy != Deletion && mVisitor) {
+ // See comment in WebBrowserPersistDocumentParent::ActorDestroy
+ // (or bug 1202887) for why this is deferred.
+ nsCOMPtr<nsIRunnable> errorLater =
+ NewRunnableMethod<nsCOMPtr<nsIWebBrowserPersistDocument>, nsresult>(
+ "nsIWebBrowserPersistResourceVisitor::EndVisit", mVisitor,
+ &nsIWebBrowserPersistResourceVisitor::EndVisit, mDocument,
+ NS_ERROR_FAILURE);
+ NS_DispatchToCurrentThread(errorLater);
+ }
+ mVisitor = nullptr;
+}
+
+mozilla::ipc::IPCResult WebBrowserPersistResourcesParent::Recv__delete__(
+ const nsresult& aStatus) {
+ mVisitor->EndVisit(mDocument, aStatus);
+ mVisitor = nullptr;
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebBrowserPersistResourcesParent::RecvVisitResource(
+ const nsACString& aURI, const nsContentPolicyType& aContentPolicyType) {
+ mVisitor->VisitResource(mDocument, aURI, aContentPolicyType);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebBrowserPersistResourcesParent::RecvVisitDocument(
+ NotNull<PWebBrowserPersistDocumentParent*> aSubDocument) {
+ // Don't expose the subdocument to the visitor until it's ready
+ // (until the actor isn't in START state).
+ static_cast<WebBrowserPersistDocumentParent*>(aSubDocument.get())
+ ->SetOnReady(this);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+WebBrowserPersistResourcesParent::RecvVisitBrowsingContext(
+ const dom::MaybeDiscarded<dom::BrowsingContext>& aContext) {
+ if (aContext.IsNullOrDiscarded()) {
+ // Nothing useful to do but ignore the discarded context.
+ return IPC_OK();
+ }
+
+ mVisitor->VisitBrowsingContext(mDocument, aContext.get());
+ return IPC_OK();
+}
+
+NS_IMETHODIMP
+WebBrowserPersistResourcesParent::OnDocumentReady(
+ nsIWebBrowserPersistDocument* aSubDocument) {
+ if (!mVisitor) {
+ return NS_ERROR_FAILURE;
+ }
+ mVisitor->VisitDocument(mDocument, aSubDocument);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistResourcesParent::OnError(nsresult aFailure) {
+ // Nothing useful to do but ignore the failed document.
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/dom/webbrowserpersist/WebBrowserPersistResourcesParent.h b/dom/webbrowserpersist/WebBrowserPersistResourcesParent.h
new file mode 100644
index 0000000000..bc33792482
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistResourcesParent.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistResourcesParent_h__
+#define WebBrowserPersistResourcesParent_h__
+
+#include "mozilla/PWebBrowserPersistResourcesParent.h"
+
+#include "WebBrowserPersistDocumentParent.h"
+#include "nsCOMPtr.h"
+#include "nsIWebBrowserPersistDocument.h"
+
+namespace mozilla {
+
+class WebBrowserPersistResourcesParent final
+ : public PWebBrowserPersistResourcesParent,
+ public nsIWebBrowserPersistDocumentReceiver {
+ public:
+ WebBrowserPersistResourcesParent(
+ nsIWebBrowserPersistDocument* aDocument,
+ nsIWebBrowserPersistResourceVisitor* aVisitor);
+
+ virtual mozilla::ipc::IPCResult RecvVisitResource(
+ const nsACString& aURI,
+ const nsContentPolicyType& aContentPolicyType) override;
+
+ virtual mozilla::ipc::IPCResult RecvVisitDocument(
+ NotNull<PWebBrowserPersistDocumentParent*> aSubDocument) override;
+
+ virtual mozilla::ipc::IPCResult RecvVisitBrowsingContext(
+ const dom::MaybeDiscarded<dom::BrowsingContext>& aContext) override;
+
+ virtual mozilla::ipc::IPCResult Recv__delete__(
+ const nsresult& aStatus) override;
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER
+ NS_DECL_ISUPPORTS
+
+ private:
+ // Note: even if the XPIDL didn't need mDocument for visitor
+ // callbacks, this object still needs to hold a strong reference
+ // to it to defer actor subtree deletion until after the
+ // visitation is finished.
+ nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
+ nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor;
+
+ virtual ~WebBrowserPersistResourcesParent();
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistResourcesParent_h__
diff --git a/dom/webbrowserpersist/WebBrowserPersistSerializeChild.cpp b/dom/webbrowserpersist/WebBrowserPersistSerializeChild.cpp
new file mode 100644
index 0000000000..8c052f6eca
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistSerializeChild.cpp
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistSerializeChild.h"
+
+#include <algorithm>
+
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(WebBrowserPersistSerializeChild,
+ nsIWebBrowserPersistWriteCompletion,
+ nsIWebBrowserPersistURIMap, nsIOutputStream)
+
+WebBrowserPersistSerializeChild::WebBrowserPersistSerializeChild(
+ const WebBrowserPersistURIMap& aMap)
+ : mMap(aMap) {}
+
+WebBrowserPersistSerializeChild::~WebBrowserPersistSerializeChild() = default;
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::OnFinish(
+ nsIWebBrowserPersistDocument* aDocument, nsIOutputStream* aStream,
+ const nsACString& aContentType, nsresult aStatus) {
+ MOZ_ASSERT(aStream == this);
+ nsCString contentType(aContentType);
+ Send__delete__(this, contentType, aStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::GetNumMappedURIs(uint32_t* aNum) {
+ *aNum = static_cast<uint32_t>(mMap.mapURIs().Length());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::GetURIMapping(uint32_t aIndex,
+ nsACString& aMapFrom,
+ nsACString& aMapTo) {
+ if (aIndex >= mMap.mapURIs().Length()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ aMapFrom = mMap.mapURIs()[aIndex].mapFrom();
+ aMapTo = mMap.mapURIs()[aIndex].mapTo();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::GetTargetBaseURI(nsACString& aURI) {
+ aURI = mMap.targetBaseURI();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::Close() {
+ NS_WARNING("WebBrowserPersistSerializeChild::Close()");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::Flush() {
+ NS_WARNING("WebBrowserPersistSerializeChild::Flush()");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::StreamStatus() {
+ // XXX: This stream doesn't appear to have a closed state.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::Write(const char* aBuf, uint32_t aCount,
+ uint32_t* aWritten) {
+ // Normally an nsIOutputStream would have to be thread-safe, but
+ // nsDocumentEncoder currently doesn't call this off the main
+ // thread (which also means it's difficult to test the
+ // thread-safety code this class doesn't yet have).
+ //
+ // This is *not* an NS_ERROR_NOT_IMPLEMENTED, because at this
+ // point we've probably already misused the non-thread-safe
+ // refcounting.
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Fix this class to be thread-safe.");
+
+ // Limit the message size to 64k because large messages are
+ // potentially bad for the latency of other messages on the same channel.
+ static const uint32_t kMaxWrite = 65536;
+
+ // Work around bug 1181433 by sending multiple messages if
+ // necessary to write the entire aCount bytes, even though
+ // nsIOutputStream.idl says we're allowed to do a short write.
+ const char* buf = aBuf;
+ uint32_t count = aCount;
+ *aWritten = 0;
+ while (count > 0) {
+ uint32_t toWrite = std::min(kMaxWrite, count);
+ nsTArray<uint8_t> arrayBuf;
+ // It would be nice if this extra copy could be avoided.
+ arrayBuf.AppendElements(buf, toWrite);
+ SendWriteData(std::move(arrayBuf));
+ *aWritten += toWrite;
+ buf += toWrite;
+ count -= toWrite;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::WriteFrom(nsIInputStream* aFrom,
+ uint32_t aCount,
+ uint32_t* aWritten) {
+ NS_WARNING("WebBrowserPersistSerializeChild::WriteFrom()");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::WriteSegments(nsReadSegmentFun aFun,
+ void* aCtx, uint32_t aCount,
+ uint32_t* aWritten) {
+ NS_WARNING("WebBrowserPersistSerializeChild::WriteSegments()");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::IsNonBlocking(bool* aNonBlocking) {
+ // Writes will never fail with NS_BASE_STREAM_WOULD_BLOCK, so:
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/dom/webbrowserpersist/WebBrowserPersistSerializeChild.h b/dom/webbrowserpersist/WebBrowserPersistSerializeChild.h
new file mode 100644
index 0000000000..86bcf0e1e7
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistSerializeChild.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistSerializeChild_h__
+#define WebBrowserPersistSerializeChild_h__
+
+#include "mozilla/PWebBrowserPersistSerializeChild.h"
+
+#include "mozilla/PWebBrowserPersistDocument.h"
+#include "nsIWebBrowserPersistDocument.h"
+#include "nsIOutputStream.h"
+
+namespace mozilla {
+
+class WebBrowserPersistSerializeChild final
+ : public PWebBrowserPersistSerializeChild,
+ public nsIWebBrowserPersistWriteCompletion,
+ public nsIWebBrowserPersistURIMap,
+ public nsIOutputStream {
+ public:
+ explicit WebBrowserPersistSerializeChild(const WebBrowserPersistURIMap& aMap);
+
+ NS_DECL_NSIWEBBROWSERPERSISTWRITECOMPLETION
+ NS_DECL_NSIWEBBROWSERPERSISTURIMAP
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_ISUPPORTS
+ private:
+ WebBrowserPersistURIMap mMap;
+
+ virtual ~WebBrowserPersistSerializeChild();
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistSerializeChild_h__
diff --git a/dom/webbrowserpersist/WebBrowserPersistSerializeParent.cpp b/dom/webbrowserpersist/WebBrowserPersistSerializeParent.cpp
new file mode 100644
index 0000000000..1fe1af95ff
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistSerializeParent.cpp
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistSerializeParent.h"
+
+#include "nsReadableUtils.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+WebBrowserPersistSerializeParent::WebBrowserPersistSerializeParent(
+ nsIWebBrowserPersistDocument* aDocument, nsIOutputStream* aStream,
+ nsIWebBrowserPersistWriteCompletion* aFinish)
+ : mDocument(aDocument),
+ mStream(aStream),
+ mFinish(aFinish),
+ mOutputError(NS_OK) {
+ MOZ_ASSERT(aDocument);
+ MOZ_ASSERT(aStream);
+ MOZ_ASSERT(aFinish);
+}
+
+WebBrowserPersistSerializeParent::~WebBrowserPersistSerializeParent() = default;
+
+mozilla::ipc::IPCResult WebBrowserPersistSerializeParent::RecvWriteData(
+ nsTArray<uint8_t>&& aData) {
+ if (NS_FAILED(mOutputError)) {
+ return IPC_OK();
+ }
+
+ uint32_t written = 0;
+ static_assert(sizeof(char) == sizeof(uint8_t),
+ "char must be (at least?) 8 bits");
+ const char* data = reinterpret_cast<const char*>(aData.Elements());
+ // nsIOutputStream::Write is allowed to return short writes.
+ while (written < aData.Length()) {
+ uint32_t writeReturn;
+ nsresult rv =
+ mStream->Write(data + written, aData.Length() - written, &writeReturn);
+ if (NS_FAILED(rv)) {
+ mOutputError = rv;
+ return IPC_OK();
+ }
+ written += writeReturn;
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebBrowserPersistSerializeParent::Recv__delete__(
+ const nsACString& aContentType, const nsresult& aStatus) {
+ if (NS_SUCCEEDED(mOutputError)) {
+ mOutputError = aStatus;
+ }
+ mFinish->OnFinish(mDocument, mStream, aContentType, mOutputError);
+ mFinish = nullptr;
+ return IPC_OK();
+}
+
+void WebBrowserPersistSerializeParent::ActorDestroy(ActorDestroyReason aWhy) {
+ if (mFinish) {
+ MOZ_ASSERT(aWhy != Deletion);
+ // See comment in WebBrowserPersistDocumentParent::ActorDestroy
+ // (or bug 1202887) for why this is deferred.
+ nsCOMPtr<nsIRunnable> errorLater =
+ NewRunnableMethod<nsCOMPtr<nsIWebBrowserPersistDocument>,
+ nsCOMPtr<nsIOutputStream>, nsCString, nsresult>(
+ "nsIWebBrowserPersistWriteCompletion::OnFinish", mFinish,
+ &nsIWebBrowserPersistWriteCompletion::OnFinish, mDocument, mStream,
+ ""_ns, NS_ERROR_FAILURE);
+ NS_DispatchToCurrentThread(errorLater);
+ mFinish = nullptr;
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/webbrowserpersist/WebBrowserPersistSerializeParent.h b/dom/webbrowserpersist/WebBrowserPersistSerializeParent.h
new file mode 100644
index 0000000000..56bd789c55
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistSerializeParent.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistSerializeParent_h__
+#define WebBrowserPersistSerializeParent_h__
+
+#include "mozilla/PWebBrowserPersistSerializeParent.h"
+
+#include "nsCOMPtr.h"
+#include "nsIOutputStream.h"
+#include "nsIWebBrowserPersistDocument.h"
+
+namespace mozilla {
+
+class WebBrowserPersistSerializeParent
+ : public PWebBrowserPersistSerializeParent {
+ public:
+ WebBrowserPersistSerializeParent(
+ nsIWebBrowserPersistDocument* aDocument, nsIOutputStream* aStream,
+ nsIWebBrowserPersistWriteCompletion* aFinish);
+ virtual ~WebBrowserPersistSerializeParent();
+
+ virtual mozilla::ipc::IPCResult RecvWriteData(
+ nsTArray<uint8_t>&& aData) override;
+
+ virtual mozilla::ipc::IPCResult Recv__delete__(
+ const nsACString& aContentType, const nsresult& aStatus) override;
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ private:
+ // See also ...ReadParent::mDocument for the other reason this
+ // strong reference needs to be here.
+ nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
+ nsCOMPtr<nsIOutputStream> mStream;
+ nsCOMPtr<nsIWebBrowserPersistWriteCompletion> mFinish;
+ nsresult mOutputError;
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistSerializeParent_h__
diff --git a/dom/webbrowserpersist/moz.build b/dom/webbrowserpersist/moz.build
new file mode 100644
index 0000000000..2c3e6a4d0f
--- /dev/null
+++ b/dom/webbrowserpersist/moz.build
@@ -0,0 +1,49 @@
+# -*- 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/.
+
+# moved from embedding/components/webbrowserpersist Jan 13, 2017
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+XPIDL_SOURCES += [
+ "nsIWebBrowserPersist.idl",
+ "nsIWebBrowserPersistDocument.idl",
+]
+
+XPIDL_MODULE = "webbrowserpersist"
+
+IPDL_SOURCES += [
+ "PWebBrowserPersistDocument.ipdl",
+ "PWebBrowserPersistResources.ipdl",
+ "PWebBrowserPersistSerialize.ipdl",
+]
+
+UNIFIED_SOURCES += [
+ "nsWebBrowserPersist.cpp",
+ "WebBrowserPersistDocumentChild.cpp",
+ "WebBrowserPersistDocumentParent.cpp",
+ "WebBrowserPersistLocalDocument.cpp",
+ "WebBrowserPersistRemoteDocument.cpp",
+ "WebBrowserPersistResourcesChild.cpp",
+ "WebBrowserPersistResourcesParent.cpp",
+ "WebBrowserPersistSerializeChild.cpp",
+ "WebBrowserPersistSerializeParent.cpp",
+]
+
+EXPORTS.mozilla += [
+ "WebBrowserPersistDocumentChild.h",
+ "WebBrowserPersistDocumentParent.h",
+ "WebBrowserPersistLocalDocument.h",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [
+ "/docshell/shistory",
+ "/dom/base",
+ "/dom/html",
+]
diff --git a/dom/webbrowserpersist/nsIWebBrowserPersist.idl b/dom/webbrowserpersist/nsIWebBrowserPersist.idl
new file mode 100644
index 0000000000..577334118f
--- /dev/null
+++ b/dom/webbrowserpersist/nsIWebBrowserPersist.idl
@@ -0,0 +1,265 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICancelable.idl"
+#include "nsIContentPolicy.idl"
+
+interface nsIURI;
+interface nsIInputStream;
+interface nsIWebProgressListener;
+interface nsIFile;
+interface nsIChannel;
+interface nsILoadContext;
+interface nsIPrincipal;
+interface nsIReferrerInfo;
+interface nsICookieJarSettings;
+
+/**
+ * Interface for persisting DOM documents and URIs to local or remote storage.
+ */
+[scriptable, uuid(8cd752a4-60b1-42c3-a819-65c7a1138a28)]
+interface nsIWebBrowserPersist : nsICancelable
+{
+ /** No special persistence behaviour. */
+ const unsigned long PERSIST_FLAGS_NONE = 0;
+ /** Use cached data if present (skipping validation), else load from network */
+ const unsigned long PERSIST_FLAGS_FROM_CACHE = 1;
+ /** Bypass the cached data. */
+ const unsigned long PERSIST_FLAGS_BYPASS_CACHE = 2;
+ /** Ignore any redirected data (usually adverts). */
+ const unsigned long PERSIST_FLAGS_IGNORE_REDIRECTED_DATA = 4;
+ /** Ignore IFRAME content (usually adverts). */
+ const unsigned long PERSIST_FLAGS_IGNORE_IFRAMES = 8;
+ /** Do not run the incoming data through a content converter e.g. to decompress it */
+ const unsigned long PERSIST_FLAGS_NO_CONVERSION = 16;
+ /** Replace existing files on the disk (use with due diligence!) */
+ const unsigned long PERSIST_FLAGS_REPLACE_EXISTING_FILES = 32;
+ /** Don't modify or add base tags */
+ const unsigned long PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS = 64;
+ /** Make changes to original dom rather than cloning nodes */
+ const unsigned long PERSIST_FLAGS_FIXUP_ORIGINAL_DOM = 128;
+ /** Fix links relative to destination location (not origin) */
+ const unsigned long PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION = 256;
+ /** Don't make any adjustments to links */
+ const unsigned long PERSIST_FLAGS_DONT_FIXUP_LINKS = 512;
+ /** Force serialization of output (one file at a time; not concurrent) */
+ const unsigned long PERSIST_FLAGS_SERIALIZE_OUTPUT = 1024;
+ /** Don't make any adjustments to filenames */
+ const unsigned long PERSIST_FLAGS_DONT_CHANGE_FILENAMES = 2048;
+ /** Fail on broken inline links */
+ const unsigned long PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS = 4096;
+ /**
+ * Automatically cleanup after a failed or cancelled operation, deleting all
+ * created files and directories. This flag does nothing for failed upload
+ * operations to remote servers.
+ */
+ const unsigned long PERSIST_FLAGS_CLEANUP_ON_FAILURE = 8192;
+ /**
+ * Let the WebBrowserPersist decide whether the incoming data is encoded
+ * and whether it needs to go through a content converter e.g. to
+ * decompress it.
+ */
+ const unsigned long PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION = 16384;
+ /**
+ * Append the downloaded data to the target file.
+ * This can only be used when persisting to a local file.
+ */
+ const unsigned long PERSIST_FLAGS_APPEND_TO_FILE = 32768;
+
+ /**
+ * Flags governing how data is fetched and saved from the network.
+ * It is best to set this value explicitly unless you are prepared
+ * to accept the default values.
+ */
+ attribute unsigned long persistFlags;
+
+ /** Persister is ready to save data */
+ const unsigned long PERSIST_STATE_READY = 1;
+ /** Persister is saving data */
+ const unsigned long PERSIST_STATE_SAVING = 2;
+ /** Persister has finished saving data */
+ const unsigned long PERSIST_STATE_FINISHED = 3;
+
+ /**
+ * Current state of the persister object.
+ */
+ readonly attribute unsigned long currentState;
+
+ /**
+ * Value indicating the success or failure of the persist
+ * operation.
+ *
+ * @throws NS_BINDING_ABORTED Operation cancelled.
+ * @throws NS_ERROR_FAILURE Non-specific failure.
+ */
+ readonly attribute nsresult result;
+
+ /**
+ * Callback listener for progress notifications. The object that the
+ * embbedder supplies may also implement nsIInterfaceRequestor and be
+ * prepared to return nsIAuthPrompt or other interfaces that may be required
+ * to download data.
+ *
+ * @see nsIAuthPrompt
+ * @see nsIInterfaceRequestor
+ */
+ attribute nsIWebProgressListener progressListener;
+
+ /**
+ * Save the specified URI to file.
+ *
+ * @param aURI URI to save to file. Some implementations of this interface
+ * may also support <CODE>nullptr</CODE> to imply the currently
+ * loaded URI.
+ * @param aTriggeringPrincipal
+ * The triggering principal for the URI we're saving.
+ * @param aCacheKey The necko cache key integer.
+ * @param aReferrerInfo The referrer info for compute and send referrer via
+ * HTTP Referer header.
+ * @param aCookieJarSettings The cookieJarSettings for the HTTP channel which
+ * is saving the URI.
+ * @param aPostData Post data to pass with an HTTP request or
+ * <CODE>nullptr</CODE>.
+ * @param aExtraHeaders Additional headers to supply with an HTTP request
+ * or <CODE>nullptr</CODE>.
+ * @param aFile Target file. This may be a nsIFile object or an
+ * nsIURI object with a file scheme or a scheme that
+ * supports uploading (e.g. ftp).
+ * @param aContentPolicyType The type of content we're saving.
+ * @param aIsPrivate Treat the save operation as private (ie. with
+ * regards to networking operations and persistence
+ * of intermediate data, etc.)
+ *
+ * @see nsIFile
+ * @see nsIURI
+ * @see nsIInputStream
+ *
+ * @throws NS_ERROR_INVALID_ARG One or more arguments was invalid.
+ */
+ void saveURI(in nsIURI aURI,
+ in nsIPrincipal aTriggeringPrincipal, in unsigned long aCacheKey,
+ in nsIReferrerInfo aReferrerInfo,
+ in nsICookieJarSettings aCookieJarSettings,
+ in nsIInputStream aPostData,
+ in string aExtraHeaders, in nsISupports aFile,
+ in nsContentPolicyType aContentPolicyType,
+ in boolean aIsPrivate);
+
+ /**
+ * Save a channel to a file. It must not be opened yet.
+ * @see saveURI
+ */
+ void saveChannel(in nsIChannel aChannel, in nsISupports aFile);
+
+ /** Output only the current selection as opposed to the whole document. */
+ const unsigned long ENCODE_FLAGS_SELECTION_ONLY = 1;
+ /**
+ * For plaintext output. Convert html to plaintext that looks like the html.
+ * Implies wrap (except inside &lt;pre&gt;), since html wraps.
+ * HTML output: always do prettyprinting, ignoring existing formatting.
+ */
+ const unsigned long ENCODE_FLAGS_FORMATTED = 2;
+ /**
+ * Output without formatting or wrapping the content. This flag
+ * may be used to preserve the original formatting as much as possible.
+ */
+ const unsigned long ENCODE_FLAGS_RAW = 4;
+ /** Output only the body section, no HTML tags. */
+ const unsigned long ENCODE_FLAGS_BODY_ONLY = 8;
+ /** Wrap even if when not doing formatted output (e.g. for text fields). */
+ const unsigned long ENCODE_FLAGS_PREFORMATTED = 16;
+ /** Wrap documents at the specified column. */
+ const unsigned long ENCODE_FLAGS_WRAP = 32;
+ /**
+ * For plaintext output. Output for format flowed (RFC 2646). This is used
+ * when converting to text for mail sending. This differs just slightly
+ * but in an important way from normal formatted, and that is that
+ * lines are space stuffed. This can't (correctly) be done later.
+ */
+ const unsigned long ENCODE_FLAGS_FORMAT_FLOWED = 64;
+ /** Convert links to absolute links where possible. */
+ const unsigned long ENCODE_FLAGS_ABSOLUTE_LINKS = 128;
+
+ /**
+ * Output with carriage return line breaks. May also be combined with
+ * ENCODE_FLAGS_LF_LINEBREAKS and if neither is specified, the platform
+ * default format is used.
+ */
+ const unsigned long ENCODE_FLAGS_CR_LINEBREAKS = 512;
+ /**
+ * Output with linefeed line breaks. May also be combined with
+ * ENCODE_FLAGS_CR_LINEBREAKS and if neither is specified, the platform
+ * default format is used.
+ */
+ const unsigned long ENCODE_FLAGS_LF_LINEBREAKS = 1024;
+ /** For plaintext output. Output the content of noscript elements. */
+ const unsigned long ENCODE_FLAGS_NOSCRIPT_CONTENT = 2048;
+ /** For plaintext output. Output the content of noframes elements. */
+ const unsigned long ENCODE_FLAGS_NOFRAMES_CONTENT = 4096;
+
+ /**
+ * Encode basic entities, e.g. output &nbsp; instead of character code 0xa0.
+ * The basic set is just &nbsp; &amp; &lt; &gt; &quot; for interoperability
+ * with older products that don't support &alpha; and friends.
+ */
+ const unsigned long ENCODE_FLAGS_ENCODE_BASIC_ENTITIES = 8192;
+
+ /**
+ * Save the specified DOM document to file and optionally all linked files
+ * (e.g. images, CSS, JS & subframes). Do not call this method until the
+ * document has finished loading!
+ *
+ * @param aDocument Document to save to file. Some implementations of
+ * this interface may also support <CODE>nullptr</CODE>
+ * to imply the currently loaded document. Can be an
+ * nsIWebBrowserPersistDocument or Document.
+ * @param aFile Target local file. This may be a nsIFile object or an
+ * nsIURI object with a file scheme or a scheme that
+ * supports uploading (e.g. ftp).
+ * @param aDataPath Path to directory where URIs linked to the document
+ * are saved or nullptr if no linked URIs should be saved.
+ * This may be a nsIFile object or an nsIURI object
+ * with a file scheme.
+ * @param aOutputContentType The desired MIME type format to save the
+ * document and all subdocuments into or nullptr to use
+ * the default behaviour.
+ * @param aEncodingFlags Flags to pass to the encoder.
+ * @param aWrapColumn For text documents, indicates the desired width to
+ * wrap text at. Parameter is ignored if wrapping is not
+ * specified by the encoding flags.
+ *
+ * @see nsIWebBrowserPersistDocument
+ * @see WebBrowserPersistable
+ * @see nsIFile
+ * @see nsIURI
+ *
+ * @throws NS_ERROR_INVALID_ARG One or more arguments was invalid.
+ */
+ void saveDocument(in nsISupports aDocument,
+ in nsISupports aFile, in nsISupports aDataPath,
+ in string aOutputContentType, in unsigned long aEncodingFlags,
+ in unsigned long aWrapColumn);
+
+ /**
+ * Cancels the current operation. The caller is responsible for cleaning up
+ * partially written files or directories. This has the same effect as calling
+ * cancel with an argument of NS_BINDING_ABORTED.
+ */
+ void cancelSave();
+};
+
+/**
+ * We don't export nsWebBrowserPersist.h as a public header, so we need a place
+ * to put the CID/ContractID. All places uses the WebBrowserPersist include
+ * nsIWebBrowserPersist.h, so we define our contract IDs here for now.
+ */
+%{ C++
+// {7E677795-C582-4cd1-9E8D-8271B3474D2A}
+#define NS_WEBBROWSERPERSIST_CID \
+ { 0x7e677795, 0xc582, 0x4cd1, { 0x9e, 0x8d, 0x82, 0x71, 0xb3, 0x47, 0x4d, 0x2a } }
+#define NS_WEBBROWSERPERSIST_CONTRACTID \
+ "@mozilla.org/embedding/browser/nsWebBrowserPersist;1"
+%}
diff --git a/dom/webbrowserpersist/nsIWebBrowserPersistDocument.idl b/dom/webbrowserpersist/nsIWebBrowserPersistDocument.idl
new file mode 100644
index 0000000000..9f1f77fad5
--- /dev/null
+++ b/dom/webbrowserpersist/nsIWebBrowserPersistDocument.idl
@@ -0,0 +1,227 @@
+/* -*- Mode: IDL; 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 "nsISupports.idl"
+#include "nsIContentPolicy.idl"
+
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsIPrincipal;
+interface nsIRemoteTab;
+interface nsIWebBrowserPersistResourceVisitor;
+interface nsIWebBrowserPersistWriteCompletion;
+interface nsIReferrerInfo;
+interface nsISHEntry;
+interface nsICookieJarSettings;
+
+webidl BrowsingContext;
+
+native SHEntryRef(already_AddRefed<nsISHEntry>);
+
+/**
+ * Interface for the URI-mapping information that can be supplied when
+ * serializing the DOM of an nsIWebBrowserPersistDocument.
+ *
+ * @see nsIWebBrowserPersistDocument
+ */
+[scriptable, uuid(d52e8b93-2771-45e8-a5b0-6e12b667046b)]
+interface nsIWebBrowserPersistURIMap : nsISupports
+{
+ /**
+ * The number of URI mappings.
+ */
+ readonly attribute unsigned long numMappedURIs;
+
+ /**
+ * Obtain the URI mapping at the given index, which must be less than
+ * numMappedURIs, as a pair of URI spec strings.
+ */
+ void getURIMapping(in unsigned long aIndex,
+ out AUTF8String aMapFrom,
+ out AUTF8String aMapTo);
+
+ /**
+ * The spec of the base URI that the document will have after it is
+ * serialized.
+ */
+ readonly attribute AUTF8String targetBaseURI;
+};
+
+/**
+ * Interface representing a document that can be serialized with
+ * nsIWebBrowserPersist; it may or may not be in this process. Some
+ * information is exposed as attributes, which may or may not reflect
+ * changes made to the underlying document; most of these are
+ * self-explanatory from their names and types.
+ */
+[scriptable, builtinclass, uuid(74aa4918-5d15-46b6-9ccf-74f9696d721d)]
+interface nsIWebBrowserPersistDocument : nsISupports
+{
+ readonly attribute boolean isClosed;
+ readonly attribute boolean isPrivate;
+ readonly attribute AUTF8String documentURI;
+ readonly attribute AUTF8String baseURI;
+ readonly attribute ACString contentType;
+ readonly attribute ACString characterSet;
+ readonly attribute AString title;
+ readonly attribute nsIReferrerInfo referrerInfo;
+ readonly attribute nsICookieJarSettings cookieJarSettings;
+ readonly attribute AString contentDisposition;
+ readonly attribute nsIInputStream postData;
+ readonly attribute nsIPrincipal principal;
+
+ /**
+ * The cache key. Unlike in nsISHEntry, where it's wrapped in an
+ * nsISupportsPRUint32, this is just the integer.
+ */
+ [infallible]
+ readonly attribute unsigned long cacheKey;
+
+ /**
+ * This attribute is set by nsIWebBrowserPersist implementations to
+ * propagate persist flags that apply to the DOM traversal and
+ * serialization (rather than to managing file I/O).
+ */
+ attribute unsigned long persistFlags;
+
+ /**
+ * Walk the DOM searching for external resources needed to render it.
+ * The visitor callbacks may be called either before or after
+ * readResources returns.
+ *
+ * @see nsIWebBrowserPersistResourceVisitor
+ */
+ void readResources(in nsIWebBrowserPersistResourceVisitor aVisitor);
+
+ /**
+ * Serialize the document's DOM.
+ *
+ * @param aStream The output stream to write the document to.
+ *
+ * @param aURIMap Optional; specifies URI rewriting to perform on
+ * external references (as read by readResources).
+ * If given, also causes relative hyperlinks to be
+ * converted to absolute in the written text.
+ *
+ * @param aRequestedContentType
+ * The desired MIME type to save the document as;
+ * optional and defaults to the document's type.
+ * (If no encoder exists for that type, "text/html"
+ * is used instead.)
+ *
+ * @param aEncoderFlags Flags to pass to the encoder.
+ *
+ * @param aWrapColumn Desired text width, ignored if wrapping is not
+ * specified by the encoding flags, or if 0.
+ *
+ * @param aCompletion Callback invoked when writing is complete.
+ * It may be called either before or after writeContent
+ * returns.
+ *
+ * @see nsIDocumentEncoder
+ */
+ void writeContent(in nsIOutputStream aStream,
+ in nsIWebBrowserPersistURIMap aURIMap,
+ in ACString aRequestedContentType,
+ in unsigned long aEncoderFlags,
+ in unsigned long aWrapColumn,
+ in nsIWebBrowserPersistWriteCompletion aCompletion);
+
+ [notxpcom, nostdcall] SHEntryRef GetHistory();
+};
+
+/**
+ * Asynchronous visitor that receives external resources linked by an
+ * nsIWebBrowserPersistDocument and which are needed to render the
+ * document.
+ */
+[scriptable, uuid(8ce37706-b7d3-481a-be68-54f174fc0d0a)]
+interface nsIWebBrowserPersistResourceVisitor : nsISupports
+{
+ /**
+ * Indicates a resource that is not a document; e.g., an image, script,
+ * or stylesheet.
+ *
+ * @param aDocument The document containing the reference.
+ * @param aURI The absolute URI spec for the referenced resource.
+ * @param aContentPolicyType The type of resource.
+ */
+ void visitResource(in nsIWebBrowserPersistDocument aDocument,
+ in AUTF8String aURI,
+ in nsContentPolicyType aContentPolicyType);
+
+ /**
+ * Indicates a subdocument resource; e.g., a frame or iframe.
+ *
+ * @param aDocument The document containing the reference.
+ * @param aSubDocument The referenced document.
+ */
+ void visitDocument(in nsIWebBrowserPersistDocument aDocument,
+ in nsIWebBrowserPersistDocument aSubDocument);
+
+ /**
+ * Indicates a cross origin subdocument resource; e.g., a frame
+ * or iframe loaded in another process.
+ *
+ * @param aDocument The document containing the reference.
+ * @param aContext The referenced document's browsing context.
+ */
+ void visitBrowsingContext(in nsIWebBrowserPersistDocument aDocument,
+ in BrowsingContext aContext);
+
+ /**
+ * Indicates that the document traversal is complete.
+ *
+ * @param aDocument The document that was being traversed.
+ * @param aStatus Indicates whether the traversal encountered an error.
+ */
+ void endVisit(in nsIWebBrowserPersistDocument aDocument,
+ in nsresult aStatus);
+};
+
+/**
+ * Asynchronous callback for when nsIWebBrowserPersistDocument is finished
+ * serializing the document's DOM.
+ */
+[scriptable, function, uuid(a07e6892-38ae-4207-8340-7fa6ec446ed6)]
+interface nsIWebBrowserPersistWriteCompletion : nsISupports
+{
+ /**
+ * Indicates that serialization is finished.
+ *
+ * @param aDocument The document that was being serialized.
+ *
+ * @param aStream The stream that was being written to. If it
+ * needs to be closed, the callback must do that;
+ * the serialization process leaves it open.
+ *
+ * @param aContentType The content type with which the document was
+ * actually serialized; this may be useful to set
+ * metadata on the result, or if uploading it.
+ *
+ * @param aStatus Indicates whether serialization encountered an error.
+ */
+ void onFinish(in nsIWebBrowserPersistDocument aDocument,
+ in nsIOutputStream aStream,
+ in ACString aContentType,
+ in nsresult aStatus);
+};
+
+/**
+ * Asynchronous callback for creating a persistable document from some
+ * other object.
+ *
+ * XXXbz This should really be changed to just return a promise that
+ * then gets resolved or rejected...
+ *
+ * @see WebBrowserPersistable in FrameLoader.webidl.
+ */
+[scriptable, uuid(321e3174-594f-4036-b7be-791b821bd376)]
+interface nsIWebBrowserPersistDocumentReceiver : nsISupports
+{
+ void onDocumentReady(in nsIWebBrowserPersistDocument aDocument);
+ void onError(in nsresult aFailure);
+};
diff --git a/dom/webbrowserpersist/nsWebBrowserPersist.cpp b/dom/webbrowserpersist/nsWebBrowserPersist.cpp
new file mode 100644
index 0000000000..2a83a166cf
--- /dev/null
+++ b/dom/webbrowserpersist/nsWebBrowserPersist.cpp
@@ -0,0 +1,2717 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/TextUtils.h"
+
+#include "nspr.h"
+
+#include "nsIFileStreams.h" // New Necko file streams
+#include <algorithm>
+
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIClassOfService.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadContext.h"
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIStorageStream.h"
+#include "nsISeekableStream.h"
+#include "nsIHttpChannel.h"
+#include "nsIEncodedChannel.h"
+#include "nsIUploadChannel.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIFileChannel.h"
+#include "nsEscape.h"
+#include "nsUnicharUtils.h"
+#include "nsIStringEnumerator.h"
+#include "nsContentCID.h"
+#include "nsStreamUtils.h"
+
+#include "nsCExternalHandlerService.h"
+
+#include "nsIURL.h"
+#include "nsIFileURL.h"
+#include "nsIWebProgressListener.h"
+#include "nsIAuthPrompt.h"
+#include "nsIPrompt.h"
+#include "nsIFormControl.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsContentUtils.h"
+
+#include "nsIStringBundle.h"
+#include "nsIProtocolHandler.h"
+
+#include "nsWebBrowserPersist.h"
+#include "WebBrowserPersistLocalDocument.h"
+
+#include "nsIContent.h"
+#include "nsIMIMEInfo.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/HTMLSharedElement.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Printf.h"
+#include "ReferrerInfo.h"
+#include "nsIURIMutator.h"
+#include "mozilla/WebBrowserPersistDocumentParent.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/PContentParent.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "nsIDocumentEncoder.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// Buffer file writes in 32kb chunks
+#define BUFFERED_OUTPUT_SIZE (1024 * 32)
+
+struct nsWebBrowserPersist::WalkData {
+ nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
+ nsCOMPtr<nsIURI> mFile;
+ nsCOMPtr<nsIURI> mDataPath;
+};
+
+// Information about a DOM document
+struct nsWebBrowserPersist::DocData {
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
+ nsCOMPtr<nsIURI> mFile;
+ nsCString mCharset;
+};
+
+// Information about a URI
+struct nsWebBrowserPersist::URIData {
+ bool mNeedsPersisting;
+ bool mSaved;
+ bool mIsSubFrame;
+ bool mDataPathIsRelative;
+ bool mNeedsFixup;
+ nsString mFilename;
+ nsString mSubFrameExt;
+ nsCOMPtr<nsIURI> mFile;
+ nsCOMPtr<nsIURI> mDataPath;
+ nsCOMPtr<nsIURI> mRelativeDocumentURI;
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
+ nsContentPolicyType mContentPolicyType;
+ nsCString mRelativePathToData;
+ nsCString mCharset;
+
+ nsresult GetLocalURI(nsIURI* targetBaseURI, nsCString& aSpecOut);
+};
+
+// Information about the output stream
+// Note that this data structure (and the map that nsWebBrowserPersist keeps,
+// where these are values) is used from two threads: the main thread,
+// and the background task thread.
+// The background thread only writes to mStream (from OnDataAvailable), and
+// this access is guarded using mStreamMutex. It reads the mFile member, which
+// is only written to on the main thread when the object is constructed and
+// from OnStartRequest (if mCalcFileExt), both guaranteed to happen before
+// OnDataAvailable is fired.
+// The main thread gets OnStartRequest, OnStopRequest, and progress sink events,
+// and accesses the other members.
+struct nsWebBrowserPersist::OutputData {
+ nsCOMPtr<nsIURI> mFile;
+ nsCOMPtr<nsIURI> mOriginalLocation;
+ nsCOMPtr<nsIOutputStream> mStream;
+ Mutex mStreamMutex MOZ_UNANNOTATED;
+ int64_t mSelfProgress;
+ int64_t mSelfProgressMax;
+ bool mCalcFileExt;
+
+ OutputData(nsIURI* aFile, nsIURI* aOriginalLocation, bool aCalcFileExt)
+ : mFile(aFile),
+ mOriginalLocation(aOriginalLocation),
+ mStreamMutex("nsWebBrowserPersist::OutputData::mStreamMutex"),
+ mSelfProgress(0),
+ mSelfProgressMax(10000),
+ mCalcFileExt(aCalcFileExt) {}
+ ~OutputData() {
+ // Gaining this lock in the destructor is pretty icky. It should be OK
+ // because the only other place we lock the mutex is in OnDataAvailable,
+ // which will never itself cause the OutputData instance to be
+ // destroyed.
+ MutexAutoLock lock(mStreamMutex);
+ if (mStream) {
+ mStream->Close();
+ }
+ }
+};
+
+struct nsWebBrowserPersist::UploadData {
+ nsCOMPtr<nsIURI> mFile;
+ int64_t mSelfProgress;
+ int64_t mSelfProgressMax;
+
+ explicit UploadData(nsIURI* aFile)
+ : mFile(aFile), mSelfProgress(0), mSelfProgressMax(10000) {}
+};
+
+struct nsWebBrowserPersist::CleanupData {
+ nsCOMPtr<nsIFile> mFile;
+ // Snapshot of what the file actually is at the time of creation so that if
+ // it transmutes into something else later on it can be ignored. For example,
+ // catch files that turn into dirs or vice versa.
+ bool mIsDirectory;
+};
+
+class nsWebBrowserPersist::OnWalk final
+ : public nsIWebBrowserPersistResourceVisitor {
+ public:
+ OnWalk(nsWebBrowserPersist* aParent, nsIURI* aFile, nsIFile* aDataPath)
+ : mParent(aParent),
+ mFile(aFile),
+ mDataPath(aDataPath),
+ mPendingDocuments(1),
+ mStatus(NS_OK) {}
+
+ NS_DECL_NSIWEBBROWSERPERSISTRESOURCEVISITOR
+ NS_DECL_ISUPPORTS
+ private:
+ RefPtr<nsWebBrowserPersist> mParent;
+ nsCOMPtr<nsIURI> mFile;
+ nsCOMPtr<nsIFile> mDataPath;
+
+ uint32_t mPendingDocuments;
+ nsresult mStatus;
+
+ virtual ~OnWalk() = default;
+};
+
+NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWalk,
+ nsIWebBrowserPersistResourceVisitor)
+
+class nsWebBrowserPersist::OnRemoteWalk final
+ : public nsIWebBrowserPersistDocumentReceiver {
+ public:
+ OnRemoteWalk(nsIWebBrowserPersistResourceVisitor* aVisitor,
+ nsIWebBrowserPersistDocument* aDocument)
+ : mVisitor(aVisitor), mDocument(aDocument) {}
+
+ NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER
+ NS_DECL_ISUPPORTS
+ private:
+ nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor;
+ nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
+
+ virtual ~OnRemoteWalk() = default;
+};
+
+NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnRemoteWalk,
+ nsIWebBrowserPersistDocumentReceiver)
+
+class nsWebBrowserPersist::OnWrite final
+ : public nsIWebBrowserPersistWriteCompletion {
+ public:
+ OnWrite(nsWebBrowserPersist* aParent, nsIURI* aFile, nsIFile* aLocalFile)
+ : mParent(aParent), mFile(aFile), mLocalFile(aLocalFile) {}
+
+ NS_DECL_NSIWEBBROWSERPERSISTWRITECOMPLETION
+ NS_DECL_ISUPPORTS
+ private:
+ RefPtr<nsWebBrowserPersist> mParent;
+ nsCOMPtr<nsIURI> mFile;
+ nsCOMPtr<nsIFile> mLocalFile;
+
+ virtual ~OnWrite() = default;
+};
+
+NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWrite,
+ nsIWebBrowserPersistWriteCompletion)
+
+class nsWebBrowserPersist::FlatURIMap final
+ : public nsIWebBrowserPersistURIMap {
+ public:
+ explicit FlatURIMap(const nsACString& aTargetBase)
+ : mTargetBase(aTargetBase) {}
+
+ void Add(const nsACString& aMapFrom, const nsACString& aMapTo) {
+ mMapFrom.AppendElement(aMapFrom);
+ mMapTo.AppendElement(aMapTo);
+ }
+
+ NS_DECL_NSIWEBBROWSERPERSISTURIMAP
+ NS_DECL_ISUPPORTS
+
+ private:
+ nsTArray<nsCString> mMapFrom;
+ nsTArray<nsCString> mMapTo;
+ nsCString mTargetBase;
+
+ virtual ~FlatURIMap() = default;
+};
+
+NS_IMPL_ISUPPORTS(nsWebBrowserPersist::FlatURIMap, nsIWebBrowserPersistURIMap)
+
+NS_IMETHODIMP
+nsWebBrowserPersist::FlatURIMap::GetNumMappedURIs(uint32_t* aNum) {
+ MOZ_ASSERT(mMapFrom.Length() == mMapTo.Length());
+ *aNum = mMapTo.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserPersist::FlatURIMap::GetTargetBaseURI(nsACString& aTargetBase) {
+ aTargetBase = mTargetBase;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserPersist::FlatURIMap::GetURIMapping(uint32_t aIndex,
+ nsACString& aMapFrom,
+ nsACString& aMapTo) {
+ MOZ_ASSERT(mMapFrom.Length() == mMapTo.Length());
+ if (aIndex >= mMapTo.Length()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ aMapFrom = mMapFrom[aIndex];
+ aMapTo = mMapTo[aIndex];
+ return NS_OK;
+}
+
+// Maximum file length constant. The max file name length is
+// volume / server dependent but it is difficult to obtain
+// that information. Instead this constant is a reasonable value that
+// modern systems should able to cope with.
+const uint32_t kDefaultMaxFilenameLength = 64;
+
+// Default flags for persistence
+const uint32_t kDefaultPersistFlags =
+ nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION |
+ nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES;
+
+// String bundle where error messages come from
+const char* kWebBrowserPersistStringBundle =
+ "chrome://global/locale/nsWebBrowserPersist.properties";
+
+nsWebBrowserPersist::nsWebBrowserPersist()
+ : mCurrentDataPathIsRelative(false),
+ mCurrentThingsToPersist(0),
+ mOutputMapMutex("nsWebBrowserPersist::mOutputMapMutex"),
+ mFirstAndOnlyUse(true),
+ mSavingDocument(false),
+ mCancel(false),
+ mEndCalled(false),
+ mCompleted(false),
+ mStartSaving(false),
+ mReplaceExisting(true),
+ mSerializingOutput(false),
+ mIsPrivate(false),
+ mPersistFlags(kDefaultPersistFlags),
+ mPersistResult(NS_OK),
+ mTotalCurrentProgress(0),
+ mTotalMaxProgress(0),
+ mWrapColumn(72),
+ mEncodingFlags(0) {}
+
+nsWebBrowserPersist::~nsWebBrowserPersist() { Cleanup(); }
+
+//*****************************************************************************
+// nsWebBrowserPersist::nsISupports
+//*****************************************************************************
+
+NS_IMPL_ADDREF(nsWebBrowserPersist)
+NS_IMPL_RELEASE(nsWebBrowserPersist)
+
+NS_INTERFACE_MAP_BEGIN(nsWebBrowserPersist)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebBrowserPersist)
+ NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersist)
+ NS_INTERFACE_MAP_ENTRY(nsICancelable)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
+NS_INTERFACE_MAP_END
+
+//*****************************************************************************
+// nsWebBrowserPersist::nsIInterfaceRequestor
+//*****************************************************************************
+
+NS_IMETHODIMP nsWebBrowserPersist::GetInterface(const nsIID& aIID,
+ void** aIFace) {
+ NS_ENSURE_ARG_POINTER(aIFace);
+
+ *aIFace = nullptr;
+
+ nsresult rv = QueryInterface(aIID, aIFace);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ if (mProgressListener && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
+ aIID.Equals(NS_GET_IID(nsIPrompt)))) {
+ mProgressListener->QueryInterface(aIID, aIFace);
+ if (*aIFace) return NS_OK;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> req = do_QueryInterface(mProgressListener);
+ if (req) {
+ return req->GetInterface(aIID, aIFace);
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+//*****************************************************************************
+// nsWebBrowserPersist::nsIWebBrowserPersist
+//*****************************************************************************
+
+NS_IMETHODIMP nsWebBrowserPersist::GetPersistFlags(uint32_t* aPersistFlags) {
+ NS_ENSURE_ARG_POINTER(aPersistFlags);
+ *aPersistFlags = mPersistFlags;
+ return NS_OK;
+}
+NS_IMETHODIMP nsWebBrowserPersist::SetPersistFlags(uint32_t aPersistFlags) {
+ mPersistFlags = aPersistFlags;
+ mReplaceExisting = (mPersistFlags & PERSIST_FLAGS_REPLACE_EXISTING_FILES);
+ mSerializingOutput = (mPersistFlags & PERSIST_FLAGS_SERIALIZE_OUTPUT);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWebBrowserPersist::GetCurrentState(uint32_t* aCurrentState) {
+ NS_ENSURE_ARG_POINTER(aCurrentState);
+ if (mCompleted) {
+ *aCurrentState = PERSIST_STATE_FINISHED;
+ } else if (mFirstAndOnlyUse) {
+ *aCurrentState = PERSIST_STATE_SAVING;
+ } else {
+ *aCurrentState = PERSIST_STATE_READY;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWebBrowserPersist::GetResult(nsresult* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mPersistResult;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWebBrowserPersist::GetProgressListener(
+ nsIWebProgressListener** aProgressListener) {
+ NS_ENSURE_ARG_POINTER(aProgressListener);
+ *aProgressListener = mProgressListener;
+ NS_IF_ADDREF(*aProgressListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWebBrowserPersist::SetProgressListener(
+ nsIWebProgressListener* aProgressListener) {
+ mProgressListener = aProgressListener;
+ mProgressListener2 = do_QueryInterface(aProgressListener);
+ mEventSink = do_GetInterface(aProgressListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWebBrowserPersist::SaveURI(
+ nsIURI* aURI, nsIPrincipal* aPrincipal, uint32_t aCacheKey,
+ nsIReferrerInfo* aReferrerInfo, nsICookieJarSettings* aCookieJarSettings,
+ nsIInputStream* aPostData, const char* aExtraHeaders, nsISupports* aFile,
+ nsContentPolicyType aContentPolicy, bool aIsPrivate) {
+ NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE);
+ mFirstAndOnlyUse = false; // Stop people from reusing this object!
+
+ nsCOMPtr<nsIURI> fileAsURI;
+ nsresult rv;
+ rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
+
+ // SaveURIInternal doesn't like broken uris.
+ mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS;
+ rv = SaveURIInternal(aURI, aPrincipal, aContentPolicy, aCacheKey,
+ aReferrerInfo, aCookieJarSettings, aPostData,
+ aExtraHeaders, fileAsURI, false, aIsPrivate);
+ return NS_FAILED(rv) ? rv : NS_OK;
+}
+
+NS_IMETHODIMP nsWebBrowserPersist::SaveChannel(nsIChannel* aChannel,
+ nsISupports* aFile) {
+ NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE);
+ mFirstAndOnlyUse = false; // Stop people from reusing this object!
+
+ nsCOMPtr<nsIURI> fileAsURI;
+ nsresult rv;
+ rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
+
+ rv = aChannel->GetURI(getter_AddRefs(mURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // SaveChannelInternal doesn't like broken uris.
+ mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS;
+ rv = SaveChannelInternal(aChannel, fileAsURI, false);
+ return NS_FAILED(rv) ? rv : NS_OK;
+}
+
+NS_IMETHODIMP nsWebBrowserPersist::SaveDocument(nsISupports* aDocument,
+ nsISupports* aFile,
+ nsISupports* aDataPath,
+ const char* aOutputContentType,
+ uint32_t aEncodingFlags,
+ uint32_t aWrapColumn) {
+ NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE);
+ mFirstAndOnlyUse = false; // Stop people from reusing this object!
+
+ // We need a STATE_IS_NETWORK start/stop pair to bracket the
+ // notification callbacks. For a whole document we generate those
+ // here and in EndDownload(), but for the single-request methods
+ // that's done in On{Start,Stop}Request instead.
+ mSavingDocument = true;
+
+ NS_ENSURE_ARG_POINTER(aDocument);
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ nsCOMPtr<nsIURI> fileAsURI;
+ nsCOMPtr<nsIURI> datapathAsURI;
+ nsresult rv;
+
+ rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
+ if (aDataPath) {
+ rv = GetValidURIFromObject(aDataPath, getter_AddRefs(datapathAsURI));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
+ }
+
+ mWrapColumn = aWrapColumn;
+ mEncodingFlags = aEncodingFlags;
+
+ if (aOutputContentType) {
+ mContentType.AssignASCII(aOutputContentType);
+ }
+
+ // State start notification
+ if (mProgressListener) {
+ mProgressListener->OnStateChange(
+ nullptr, nullptr,
+ nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_NETWORK,
+ NS_OK);
+ }
+
+ nsCOMPtr<nsIWebBrowserPersistDocument> doc = do_QueryInterface(aDocument);
+ if (!doc) {
+ nsCOMPtr<Document> localDoc = do_QueryInterface(aDocument);
+ if (localDoc) {
+ doc = new mozilla::WebBrowserPersistLocalDocument(localDoc);
+ } else {
+ rv = NS_ERROR_NO_INTERFACE;
+ }
+ }
+
+ bool closed = false;
+ if (doc && NS_SUCCEEDED(doc->GetIsClosed(&closed)) && !closed) {
+ rv = SaveDocumentInternal(doc, fileAsURI, datapathAsURI);
+ }
+
+ if (NS_FAILED(rv) || closed) {
+ SendErrorStatusChange(true, rv, nullptr, mURI);
+ EndDownload(rv);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsWebBrowserPersist::Cancel(nsresult aReason) {
+ // No point cancelling if we're already complete.
+ if (mEndCalled) {
+ return NS_OK;
+ }
+ mCancel = true;
+ EndDownload(aReason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWebBrowserPersist::CancelSave() {
+ return Cancel(NS_BINDING_ABORTED);
+}
+
+nsresult nsWebBrowserPersist::StartUpload(nsIStorageStream* storStream,
+ nsIURI* aDestinationURI,
+ const nsACString& aContentType) {
+ // setup the upload channel if the destination is not local
+ nsCOMPtr<nsIInputStream> inputstream;
+ nsresult rv = storStream->NewInputStream(0, getter_AddRefs(inputstream));
+ NS_ENSURE_TRUE(inputstream, NS_ERROR_FAILURE);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ return StartUpload(inputstream, aDestinationURI, aContentType);
+}
+
+nsresult nsWebBrowserPersist::StartUpload(nsIInputStream* aInputStream,
+ nsIURI* aDestinationURI,
+ const nsACString& aContentType) {
+ nsCOMPtr<nsIChannel> destChannel;
+ CreateChannelFromURI(aDestinationURI, getter_AddRefs(destChannel));
+ nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(destChannel));
+ NS_ENSURE_TRUE(uploadChannel, NS_ERROR_FAILURE);
+
+ // Set the upload stream
+ // NOTE: ALL data must be available in "inputstream"
+ nsresult rv = uploadChannel->SetUploadStream(aInputStream, aContentType, -1);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ rv = destChannel->AsyncOpen(this);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ // add this to the upload list
+ nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(destChannel);
+ mUploadList.InsertOrUpdate(keyPtr, MakeUnique<UploadData>(aDestinationURI));
+
+ return NS_OK;
+}
+
+void nsWebBrowserPersist::SerializeNextFile() {
+ nsresult rv = NS_OK;
+ MOZ_ASSERT(mWalkStack.Length() == 0);
+
+ // First, handle gathered URIs.
+ // This is potentially O(n^2), when taking into account the
+ // number of times this method is called. If it becomes a
+ // bottleneck, the count of not-yet-persisted URIs could be
+ // maintained separately, and we can skip iterating mURIMap if there are none.
+
+ // Persist each file in the uri map. The document(s)
+ // will be saved after the last one of these is saved.
+ for (const auto& entry : mURIMap) {
+ URIData* data = entry.GetWeak();
+
+ if (!data->mNeedsPersisting || data->mSaved) {
+ continue;
+ }
+
+ // Create a URI from the key.
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), entry.GetKey(), data->mCharset.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+
+ // Make a URI to save the data to.
+ nsCOMPtr<nsIURI> fileAsURI = data->mDataPath;
+ rv = AppendPathToURI(fileAsURI, data->mFilename, fileAsURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+
+ rv = SaveURIInternal(uri, data->mTriggeringPrincipal,
+ data->mContentPolicyType, 0, nullptr,
+ data->mCookieJarSettings, nullptr, nullptr, fileAsURI,
+ true, mIsPrivate);
+ // If SaveURIInternal fails, then it will have called EndDownload,
+ // which means that |data| is no longer valid memory. We MUST bail.
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+
+ if (rv == NS_OK) {
+ // URIData.mFile will be updated to point to the correct
+ // URI object when it is fixed up with the right file extension
+ // in OnStartRequest
+ data->mFile = fileAsURI;
+ data->mSaved = true;
+ } else {
+ data->mNeedsFixup = false;
+ }
+
+ if (mSerializingOutput) {
+ break;
+ }
+ }
+
+ // If there are downloads happening, wait until they're done; the
+ // OnStopRequest handler will call this method again.
+ if (mOutputMap.Count() > 0) {
+ return;
+ }
+
+ // If serializing, also wait until last upload is done.
+ if (mSerializingOutput && mUploadList.Count() > 0) {
+ return;
+ }
+
+ // If there are also no more documents, then we're done.
+ if (mDocList.Length() == 0) {
+ // ...or not quite done, if there are still uploads.
+ if (mUploadList.Count() > 0) {
+ return;
+ }
+ // Finish and clean things up. Defer this because the caller
+ // may have been expecting to use the listeners that that
+ // method will clear.
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod("nsWebBrowserPersist::FinishDownload", this,
+ &nsWebBrowserPersist::FinishDownload));
+ return;
+ }
+
+ // There are no URIs to save, so just save the next document.
+ mStartSaving = true;
+ mozilla::UniquePtr<DocData> docData(mDocList.ElementAt(0));
+ mDocList.RemoveElementAt(0); // O(n^2) but probably doesn't matter.
+ MOZ_ASSERT(docData);
+ if (!docData) {
+ EndDownload(NS_ERROR_FAILURE);
+ return;
+ }
+
+ mCurrentBaseURI = docData->mBaseURI;
+ mCurrentCharset = docData->mCharset;
+ mTargetBaseURI = docData->mFile;
+
+ // Save the document, fixing it up with the new URIs as we do
+
+ nsAutoCString targetBaseSpec;
+ if (mTargetBaseURI) {
+ rv = mTargetBaseURI->GetSpec(targetBaseSpec);
+ if (NS_FAILED(rv)) {
+ SendErrorStatusChange(true, rv, nullptr, nullptr);
+ EndDownload(rv);
+ return;
+ }
+ }
+
+ // mFlatURIMap must be rebuilt each time through SerializeNextFile, as
+ // mTargetBaseURI is used to create the relative URLs and will be different
+ // with each serialized document.
+ RefPtr<FlatURIMap> flatMap = new FlatURIMap(targetBaseSpec);
+ for (const auto& uriEntry : mURIMap) {
+ nsAutoCString mapTo;
+ nsresult rv = uriEntry.GetWeak()->GetLocalURI(mTargetBaseURI, mapTo);
+ if (NS_SUCCEEDED(rv) || !mapTo.IsVoid()) {
+ flatMap->Add(uriEntry.GetKey(), mapTo);
+ }
+ }
+ mFlatURIMap = std::move(flatMap);
+
+ nsCOMPtr<nsIFile> localFile;
+ GetLocalFileFromURI(docData->mFile, getter_AddRefs(localFile));
+ if (localFile) {
+ // if we're not replacing an existing file but the file
+ // exists, something is wrong
+ bool fileExists = false;
+ rv = localFile->Exists(&fileExists);
+ if (NS_SUCCEEDED(rv) && !mReplaceExisting && fileExists) {
+ rv = NS_ERROR_FILE_ALREADY_EXISTS;
+ }
+ if (NS_FAILED(rv)) {
+ SendErrorStatusChange(false, rv, nullptr, docData->mFile);
+ EndDownload(rv);
+ return;
+ }
+ }
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = MakeOutputStream(docData->mFile, getter_AddRefs(outputStream));
+ if (NS_SUCCEEDED(rv) && !outputStream) {
+ rv = NS_ERROR_FAILURE;
+ }
+ if (NS_FAILED(rv)) {
+ SendErrorStatusChange(false, rv, nullptr, docData->mFile);
+ EndDownload(rv);
+ return;
+ }
+
+ RefPtr<OnWrite> finish = new OnWrite(this, docData->mFile, localFile);
+ rv = docData->mDocument->WriteContent(outputStream, mFlatURIMap,
+ NS_ConvertUTF16toUTF8(mContentType),
+ mEncodingFlags, mWrapColumn, finish);
+ if (NS_FAILED(rv)) {
+ SendErrorStatusChange(false, rv, nullptr, docData->mFile);
+ EndDownload(rv);
+ }
+}
+
+NS_IMETHODIMP
+nsWebBrowserPersist::OnWrite::OnFinish(nsIWebBrowserPersistDocument* aDoc,
+ nsIOutputStream* aStream,
+ const nsACString& aContentType,
+ nsresult aStatus) {
+ nsresult rv = aStatus;
+
+ if (NS_FAILED(rv)) {
+ mParent->SendErrorStatusChange(false, rv, nullptr, mFile);
+ mParent->EndDownload(rv);
+ return NS_OK;
+ }
+ if (!mLocalFile) {
+ nsCOMPtr<nsIStorageStream> storStream(do_QueryInterface(aStream));
+ if (storStream) {
+ aStream->Close();
+ rv = mParent->StartUpload(storStream, mFile, aContentType);
+ if (NS_FAILED(rv)) {
+ mParent->SendErrorStatusChange(false, rv, nullptr, mFile);
+ mParent->EndDownload(rv);
+ }
+ // Either we failed and we're done, or we're uploading and
+ // the OnStopRequest callback is responsible for the next
+ // SerializeNextFile().
+ return NS_OK;
+ }
+ }
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod("nsWebBrowserPersist::SerializeNextFile", mParent,
+ &nsWebBrowserPersist::SerializeNextFile));
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsWebBrowserPersist::nsIRequestObserver
+//*****************************************************************************
+
+NS_IMETHODIMP nsWebBrowserPersist::OnStartRequest(nsIRequest* request) {
+ if (mProgressListener) {
+ uint32_t stateFlags = nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_REQUEST;
+ if (!mSavingDocument) {
+ stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK;
+ }
+ mProgressListener->OnStateChange(nullptr, request, stateFlags, NS_OK);
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
+ OutputData* data = mOutputMap.Get(keyPtr);
+
+ // NOTE: This code uses the channel as a hash key so it will not
+ // recognize redirected channels because the key is not the same.
+ // When that happens we remove and add the data entry to use the
+ // new channel as the hash key.
+ if (!data) {
+ UploadData* upData = mUploadList.Get(keyPtr);
+ if (!upData) {
+ // Redirect? Try and fixup the output table
+ nsresult rv = FixRedirectedChannelEntry(channel);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ // Should be able to find the data after fixup unless redirects
+ // are disabled.
+ data = mOutputMap.Get(keyPtr);
+ if (!data) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ if (data && data->mFile) {
+ nsCOMPtr<nsIThreadRetargetableRequest> r = do_QueryInterface(request);
+ // Determine if we're uploading. Only use OMT onDataAvailable if not.
+ nsCOMPtr<nsIFile> localFile;
+ GetLocalFileFromURI(data->mFile, getter_AddRefs(localFile));
+ if (r && localFile) {
+ if (!mBackgroundQueue) {
+ NS_CreateBackgroundTaskQueue("WebBrowserPersist",
+ getter_AddRefs(mBackgroundQueue));
+ }
+ if (mBackgroundQueue) {
+ r->RetargetDeliveryTo(mBackgroundQueue);
+ }
+ }
+
+ // If PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION is set in mPersistFlags,
+ // try to determine whether this channel needs to apply Content-Encoding
+ // conversions.
+ NS_ASSERTION(
+ !((mPersistFlags & PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION) &&
+ (mPersistFlags & PERSIST_FLAGS_NO_CONVERSION)),
+ "Conflict in persist flags: both AUTODETECT and NO_CONVERSION set");
+ if (mPersistFlags & PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION)
+ SetApplyConversionIfNeeded(channel);
+
+ if (data->mCalcFileExt &&
+ !(mPersistFlags & PERSIST_FLAGS_DONT_CHANGE_FILENAMES)) {
+ nsCOMPtr<nsIURI> uriWithExt;
+ // this is the first point at which the server can tell us the mimetype
+ nsresult rv = CalculateAndAppendFileExt(
+ data->mFile, channel, data->mOriginalLocation, uriWithExt);
+ if (NS_SUCCEEDED(rv)) {
+ data->mFile = uriWithExt;
+ }
+
+ // now make filename conformant and unique
+ nsCOMPtr<nsIURI> uniqueFilenameURI;
+ rv = CalculateUniqueFilename(data->mFile, uniqueFilenameURI);
+ if (NS_SUCCEEDED(rv)) {
+ data->mFile = uniqueFilenameURI;
+ }
+
+ // The URIData entry is pointing to the old unfixed URI, so we need
+ // to update it.
+ nsCOMPtr<nsIURI> chanURI;
+ rv = channel->GetOriginalURI(getter_AddRefs(chanURI));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString spec;
+ chanURI->GetSpec(spec);
+ URIData* uridata;
+ if (mURIMap.Get(spec, &uridata)) {
+ uridata->mFile = data->mFile;
+ }
+ }
+ }
+
+ // compare uris and bail before we add to output map if they are equal
+ bool isEqual = false;
+ if (NS_SUCCEEDED(data->mFile->Equals(data->mOriginalLocation, &isEqual)) &&
+ isEqual) {
+ {
+ MutexAutoLock lock(mOutputMapMutex);
+ // remove from output map
+ mOutputMap.Remove(keyPtr);
+ }
+
+ // cancel; we don't need to know any more
+ // stop request will get called
+ request->Cancel(NS_BINDING_ABORTED);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWebBrowserPersist::OnStopRequest(nsIRequest* request,
+ nsresult status) {
+ nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
+ OutputData* data = mOutputMap.Get(keyPtr);
+ if (data) {
+ if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(status)) {
+ SendErrorStatusChange(true, status, request, data->mFile);
+ }
+
+ // If there is a stream ref and we weren't canceled,
+ // close it away from the main thread.
+ // We don't do this when there's an error/cancelation,
+ // because our consumer may try to delete the file, which will error
+ // if we're still holding on to it, so we have to close it pronto.
+ {
+ MutexAutoLock lock(data->mStreamMutex);
+ if (data->mStream && NS_SUCCEEDED(status) && !mCancel) {
+ if (!mBackgroundQueue) {
+ nsresult rv = NS_CreateBackgroundTaskQueue(
+ "WebBrowserPersist", getter_AddRefs(mBackgroundQueue));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ // Now steal the stream ref and close it away from the main thread,
+ // keeping the promise around so we don't finish before all files
+ // are flushed and closed.
+ mFileClosePromises.AppendElement(InvokeAsync(
+ mBackgroundQueue, __func__, [stream = std::move(data->mStream)]() {
+ nsresult rv = stream->Close();
+ // We don't care if closing failed; we don't care in the
+ // destructor either...
+ return ClosePromise::CreateAndResolve(rv, __func__);
+ }));
+ }
+ }
+ MutexAutoLock lock(mOutputMapMutex);
+ mOutputMap.Remove(keyPtr);
+ } else {
+ // if we didn't find the data in mOutputMap, try mUploadList
+ UploadData* upData = mUploadList.Get(keyPtr);
+ if (upData) {
+ mUploadList.Remove(keyPtr);
+ }
+ }
+
+ // Do more work.
+ SerializeNextFile();
+
+ if (mProgressListener) {
+ uint32_t stateFlags = nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_REQUEST;
+ if (!mSavingDocument) {
+ stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK;
+ }
+ mProgressListener->OnStateChange(nullptr, request, stateFlags, status);
+ }
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsWebBrowserPersist::nsIStreamListener
+//*****************************************************************************
+
+// Note: this is supposed to (but not guaranteed to) fire on a background
+// thread when used to save to local disk (channels not using local files will
+// use the main thread).
+// (Read) Access to mOutputMap is guarded via mOutputMapMutex.
+// Access to individual OutputData::mStream is guarded via its mStreamMutex.
+// mCancel is atomic, as is mPersistFlags (accessed via MakeOutputStream).
+// If you end up touching this method and needing other member access, bear
+// this in mind.
+NS_IMETHODIMP
+nsWebBrowserPersist::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* aIStream, uint64_t aOffset,
+ uint32_t aLength) {
+ // MOZ_ASSERT(!NS_IsMainThread()); // no guarantees, but it's likely.
+
+ bool cancel = mCancel;
+ if (!cancel) {
+ nsresult rv = NS_OK;
+ uint32_t bytesRemaining = aLength;
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
+
+ MutexAutoLock lock(mOutputMapMutex);
+ nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
+ OutputData* data = mOutputMap.Get(keyPtr);
+ if (!data) {
+ // might be uploadData; consume necko's buffer and bail...
+ uint32_t n;
+ return aIStream->ReadSegments(NS_DiscardSegment, nullptr, aLength, &n);
+ }
+
+ bool readError = true;
+
+ MutexAutoLock streamLock(data->mStreamMutex);
+ // Make the output stream
+ if (!data->mStream) {
+ rv = MakeOutputStream(data->mFile, getter_AddRefs(data->mStream));
+ if (NS_FAILED(rv)) {
+ readError = false;
+ cancel = true;
+ }
+ }
+
+ // Read data from the input and write to the output
+ char buffer[8192];
+ uint32_t bytesRead;
+ while (!cancel && bytesRemaining) {
+ readError = true;
+ rv = aIStream->Read(buffer,
+ std::min(uint32_t(sizeof(buffer)), bytesRemaining),
+ &bytesRead);
+ if (NS_SUCCEEDED(rv)) {
+ readError = false;
+ // Write out the data until something goes wrong, or, it is
+ // all written. We loop because for some errors (e.g., disk
+ // full), we get NS_OK with some bytes written, then an error.
+ // So, we want to write again in that case to get the actual
+ // error code.
+ const char* bufPtr = buffer; // Where to write from.
+ while (NS_SUCCEEDED(rv) && bytesRead) {
+ uint32_t bytesWritten = 0;
+ rv = data->mStream->Write(bufPtr, bytesRead, &bytesWritten);
+ if (NS_SUCCEEDED(rv)) {
+ bytesRead -= bytesWritten;
+ bufPtr += bytesWritten;
+ bytesRemaining -= bytesWritten;
+ // Force an error if (for some reason) we get NS_OK but
+ // no bytes written.
+ if (!bytesWritten) {
+ rv = NS_ERROR_FAILURE;
+ cancel = true;
+ }
+ } else {
+ // Disaster - can't write out the bytes - disk full / permission?
+ cancel = true;
+ }
+ }
+ } else {
+ // Disaster - can't read the bytes - broken link / file error?
+ cancel = true;
+ }
+ }
+
+ int64_t channelContentLength = -1;
+ if (!cancel &&
+ NS_SUCCEEDED(channel->GetContentLength(&channelContentLength))) {
+ // if we get -1 at this point, we didn't get content-length header
+ // assume that we got all of the data and push what we have;
+ // that's the best we can do now
+ if ((-1 == channelContentLength) ||
+ ((channelContentLength - (aOffset + aLength)) == 0)) {
+ NS_WARNING_ASSERTION(
+ channelContentLength != -1,
+ "nsWebBrowserPersist::OnDataAvailable() no content length "
+ "header, pushing what we have");
+ // we're done with this pass; see if we need to do upload
+ nsAutoCString contentType;
+ channel->GetContentType(contentType);
+ // if we don't have the right type of output stream then it's a local
+ // file
+ nsCOMPtr<nsIStorageStream> storStream(do_QueryInterface(data->mStream));
+ if (storStream) {
+ data->mStream->Close();
+ data->mStream =
+ nullptr; // null out stream so we don't close it later
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Uploads should be on the main thread.");
+ rv = StartUpload(storStream, data->mFile, contentType);
+ if (NS_FAILED(rv)) {
+ readError = false;
+ cancel = true;
+ }
+ }
+ }
+ }
+
+ // Notify listener if an error occurred.
+ if (cancel) {
+ RefPtr<nsIRequest> req = readError ? request : nullptr;
+ nsCOMPtr<nsIURI> file = data->mFile;
+ RefPtr<Runnable> errorOnMainThread = NS_NewRunnableFunction(
+ "nsWebBrowserPersist::SendErrorStatusChange",
+ [self = RefPtr{this}, req, file, readError, rv]() {
+ self->SendErrorStatusChange(readError, rv, req, file);
+ });
+ NS_DispatchToMainThread(errorOnMainThread);
+
+ // And end the download on the main thread.
+ nsCOMPtr<nsIRunnable> endOnMainThread = NewRunnableMethod<nsresult>(
+ "nsWebBrowserPersist::EndDownload", this,
+ &nsWebBrowserPersist::EndDownload, NS_BINDING_ABORTED);
+ NS_DispatchToMainThread(endOnMainThread);
+ }
+ }
+
+ return cancel ? NS_BINDING_ABORTED : NS_OK;
+}
+
+//*****************************************************************************
+// nsWebBrowserPersist::nsIThreadRetargetableStreamListener
+//*****************************************************************************
+
+NS_IMETHODIMP nsWebBrowserPersist::CheckListenerChain() { return NS_OK; }
+
+//*****************************************************************************
+// nsWebBrowserPersist::nsIProgressEventSink
+//*****************************************************************************
+
+NS_IMETHODIMP nsWebBrowserPersist::OnProgress(nsIRequest* request,
+ int64_t aProgress,
+ int64_t aProgressMax) {
+ if (!mProgressListener) {
+ return NS_OK;
+ }
+
+ // Store the progress of this request
+ nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
+ OutputData* data = mOutputMap.Get(keyPtr);
+ if (data) {
+ data->mSelfProgress = aProgress;
+ data->mSelfProgressMax = aProgressMax;
+ } else {
+ UploadData* upData = mUploadList.Get(keyPtr);
+ if (upData) {
+ upData->mSelfProgress = aProgress;
+ upData->mSelfProgressMax = aProgressMax;
+ }
+ }
+
+ // Notify listener of total progress
+ CalcTotalProgress();
+ if (mProgressListener2) {
+ mProgressListener2->OnProgressChange64(nullptr, request, aProgress,
+ aProgressMax, mTotalCurrentProgress,
+ mTotalMaxProgress);
+ } else {
+ // have to truncate 64-bit to 32bit
+ mProgressListener->OnProgressChange(
+ nullptr, request, uint64_t(aProgress), uint64_t(aProgressMax),
+ mTotalCurrentProgress, mTotalMaxProgress);
+ }
+
+ // If our progress listener implements nsIProgressEventSink,
+ // forward the notification
+ if (mEventSink) {
+ mEventSink->OnProgress(request, aProgress, aProgressMax);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWebBrowserPersist::OnStatus(nsIRequest* request,
+ nsresult status,
+ const char16_t* statusArg) {
+ if (mProgressListener) {
+ // We need to filter out non-error error codes.
+ // Is the only NS_SUCCEEDED value NS_OK?
+ switch (status) {
+ case NS_NET_STATUS_RESOLVING_HOST:
+ case NS_NET_STATUS_RESOLVED_HOST:
+ case NS_NET_STATUS_CONNECTING_TO:
+ case NS_NET_STATUS_CONNECTED_TO:
+ case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
+ case NS_NET_STATUS_TLS_HANDSHAKE_ENDED:
+ case NS_NET_STATUS_SENDING_TO:
+ case NS_NET_STATUS_RECEIVING_FROM:
+ case NS_NET_STATUS_WAITING_FOR:
+ case NS_NET_STATUS_READING:
+ case NS_NET_STATUS_WRITING:
+ break;
+
+ default:
+ // Pass other notifications (for legitimate errors) along.
+ mProgressListener->OnStatusChange(nullptr, request, status, statusArg);
+ break;
+ }
+ }
+
+ // If our progress listener implements nsIProgressEventSink,
+ // forward the notification
+ if (mEventSink) {
+ mEventSink->OnStatus(request, status, statusArg);
+ }
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsWebBrowserPersist private methods
+//*****************************************************************************
+
+// Convert error info into proper message text and send OnStatusChange
+// notification to the web progress listener.
+nsresult nsWebBrowserPersist::SendErrorStatusChange(bool aIsReadError,
+ nsresult aResult,
+ nsIRequest* aRequest,
+ nsIURI* aURI) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ if (!mProgressListener) {
+ // Do nothing
+ return NS_OK;
+ }
+
+ // Get the file path or spec from the supplied URI
+ nsCOMPtr<nsIFile> file;
+ GetLocalFileFromURI(aURI, getter_AddRefs(file));
+ AutoTArray<nsString, 1> strings;
+ nsresult rv;
+ if (file) {
+ file->GetPath(*strings.AppendElement());
+ } else {
+ nsAutoCString fileurl;
+ rv = aURI->GetSpec(fileurl);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF8toUTF16(fileurl, *strings.AppendElement());
+ }
+
+ const char* msgId;
+ switch (aResult) {
+ case NS_ERROR_FILE_NAME_TOO_LONG:
+ // File name too long.
+ msgId = "fileNameTooLongError";
+ break;
+ case NS_ERROR_FILE_ALREADY_EXISTS:
+ // File exists with same name as directory.
+ msgId = "fileAlreadyExistsError";
+ break;
+ case NS_ERROR_FILE_NO_DEVICE_SPACE:
+ // Out of space on target volume.
+ msgId = "diskFull";
+ break;
+
+ case NS_ERROR_FILE_READ_ONLY:
+ // Attempt to write to read/only file.
+ msgId = "readOnly";
+ break;
+
+ case NS_ERROR_FILE_ACCESS_DENIED:
+ // Attempt to write without sufficient permissions.
+ msgId = "accessError";
+ break;
+
+ default:
+ // Generic read/write error message.
+ if (aIsReadError)
+ msgId = "readError";
+ else
+ msgId = "writeError";
+ break;
+ }
+ // Get properties file bundle and extract status string.
+ nsCOMPtr<nsIStringBundleService> s =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && s, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = s->CreateBundle(kWebBrowserPersistStringBundle, getter_AddRefs(bundle));
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && bundle, NS_ERROR_FAILURE);
+
+ nsAutoString msgText;
+ rv = bundle->FormatStringFromName(msgId, strings, msgText);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ mProgressListener->OnStatusChange(nullptr, aRequest, aResult, msgText.get());
+
+ return NS_OK;
+}
+
+nsresult nsWebBrowserPersist::GetValidURIFromObject(nsISupports* aObject,
+ nsIURI** aURI) const {
+ NS_ENSURE_ARG_POINTER(aObject);
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIFile> objAsFile = do_QueryInterface(aObject);
+ if (objAsFile) {
+ return NS_NewFileURI(aURI, objAsFile);
+ }
+ nsCOMPtr<nsIURI> objAsURI = do_QueryInterface(aObject);
+ if (objAsURI) {
+ *aURI = objAsURI;
+ NS_ADDREF(*aURI);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+/* static */
+nsresult nsWebBrowserPersist::GetLocalFileFromURI(nsIURI* aURI,
+ nsIFile** aLocalFile) {
+ nsresult rv;
+
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ file.forget(aLocalFile);
+ return NS_OK;
+}
+
+/* static */
+nsresult nsWebBrowserPersist::AppendPathToURI(nsIURI* aURI,
+ const nsAString& aPath,
+ nsCOMPtr<nsIURI>& aOutURI) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsAutoCString newPath;
+ nsresult rv = aURI->GetPathQueryRef(newPath);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ // Append a forward slash if necessary
+ int32_t len = newPath.Length();
+ if (len > 0 && newPath.CharAt(len - 1) != '/') {
+ newPath.Append('/');
+ }
+
+ // Store the path back on the URI
+ AppendUTF16toUTF8(aPath, newPath);
+
+ return NS_MutateURI(aURI).SetPathQueryRef(newPath).Finalize(aOutURI);
+}
+
+nsresult nsWebBrowserPersist::SaveURIInternal(
+ nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal,
+ nsContentPolicyType aContentPolicyType, uint32_t aCacheKey,
+ nsIReferrerInfo* aReferrerInfo, nsICookieJarSettings* aCookieJarSettings,
+ nsIInputStream* aPostData, const char* aExtraHeaders, nsIURI* aFile,
+ bool aCalcFileExt, bool aIsPrivate) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(aFile);
+ NS_ENSURE_ARG_POINTER(aTriggeringPrincipal);
+
+ nsresult rv = NS_OK;
+
+ mURI = aURI;
+
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
+ if (mPersistFlags & PERSIST_FLAGS_BYPASS_CACHE) {
+ loadFlags |= nsIRequest::LOAD_BYPASS_CACHE;
+ } else if (mPersistFlags & PERSIST_FLAGS_FROM_CACHE) {
+ loadFlags |= nsIRequest::LOAD_FROM_CACHE;
+ }
+
+ // If there is no cookieJarSetting given, we need to create a new
+ // cookieJarSettings for this download in order to send cookies based on the
+ // current state of the prefs/permissions.
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings = aCookieJarSettings;
+ if (!cookieJarSettings) {
+ // Although the variable is called 'triggering principal', it is used as the
+ // loading principal in the download channel, so we treat it as a loading
+ // principal also.
+ bool shouldResistFingerprinting =
+ nsContentUtils::ShouldResistFingerprinting_dangerous(
+ aTriggeringPrincipal,
+ "We are creating a new CookieJar Settings, so none exists "
+ "currently. Although the variable is called 'triggering principal',"
+ "it is used as the loading principal in the download channel, so we"
+ "treat it as a loading principal also.");
+ cookieJarSettings =
+ aIsPrivate
+ ? net::CookieJarSettings::Create(net::CookieJarSettings::ePrivate,
+ shouldResistFingerprinting)
+ : net::CookieJarSettings::Create(net::CookieJarSettings::eRegular,
+ shouldResistFingerprinting);
+ }
+
+ // Open a channel to the URI
+ nsCOMPtr<nsIChannel> inputChannel;
+ rv = NS_NewChannel(getter_AddRefs(inputChannel), aURI, aTriggeringPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ aContentPolicyType, cookieJarSettings,
+ nullptr, // aPerformanceStorage
+ nullptr, // aLoadGroup
+ static_cast<nsIInterfaceRequestor*>(this), loadFlags);
+
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel =
+ do_QueryInterface(inputChannel);
+ if (pbChannel) {
+ pbChannel->SetPrivate(aIsPrivate);
+ }
+
+ if (NS_FAILED(rv) || inputChannel == nullptr) {
+ EndDownload(NS_ERROR_FAILURE);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Disable content conversion
+ if (mPersistFlags & PERSIST_FLAGS_NO_CONVERSION) {
+ nsCOMPtr<nsIEncodedChannel> encodedChannel(do_QueryInterface(inputChannel));
+ if (encodedChannel) {
+ encodedChannel->SetApplyConversion(false);
+ }
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = inputChannel->LoadInfo();
+ loadInfo->SetIsUserTriggeredSave(true);
+
+ // Set the referrer, post data and headers if any
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(inputChannel));
+ if (httpChannel) {
+ if (aReferrerInfo) {
+ DebugOnly<nsresult> success = httpChannel->SetReferrerInfo(aReferrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(success));
+ }
+
+ // Post data
+ if (aPostData) {
+ nsCOMPtr<nsISeekableStream> stream(do_QueryInterface(aPostData));
+ if (stream) {
+ // Rewind the postdata stream
+ stream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ nsCOMPtr<nsIUploadChannel> uploadChannel(
+ do_QueryInterface(httpChannel));
+ NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel");
+ // Attach the postdata to the http channel
+ uploadChannel->SetUploadStream(aPostData, ""_ns, -1);
+ }
+ }
+
+ // Cache key
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(httpChannel));
+ if (cacheChannel && aCacheKey != 0) {
+ cacheChannel->SetCacheKey(aCacheKey);
+ }
+
+ // Headers
+ if (aExtraHeaders) {
+ nsAutoCString oneHeader;
+ nsAutoCString headerName;
+ nsAutoCString headerValue;
+ int32_t crlf = 0;
+ int32_t colon = 0;
+ const char* kWhitespace = "\b\t\r\n ";
+ nsAutoCString extraHeaders(aExtraHeaders);
+ while (true) {
+ crlf = extraHeaders.Find("\r\n");
+ if (crlf == -1) break;
+ extraHeaders.Mid(oneHeader, 0, crlf);
+ extraHeaders.Cut(0, crlf + 2);
+ colon = oneHeader.Find(":");
+ if (colon == -1) break; // Should have a colon
+ oneHeader.Left(headerName, colon);
+ colon++;
+ oneHeader.Mid(headerValue, colon, oneHeader.Length() - colon);
+ headerName.Trim(kWhitespace);
+ headerValue.Trim(kWhitespace);
+ // Add the header (merging if required)
+ rv = httpChannel->SetRequestHeader(headerName, headerValue, true);
+ if (NS_FAILED(rv)) {
+ EndDownload(NS_ERROR_FAILURE);
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+ }
+ return SaveChannelInternal(inputChannel, aFile, aCalcFileExt);
+}
+
+nsresult nsWebBrowserPersist::SaveChannelInternal(nsIChannel* aChannel,
+ nsIURI* aFile,
+ bool aCalcFileExt) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ // The default behaviour of SaveChannelInternal is to download the source
+ // into a storage stream and upload that to the target. MakeOutputStream
+ // special-cases a file target and creates a file output stream directly.
+ // We want to special-case a file source and create a file input stream,
+ // but we don't need to do this in the case of a file target.
+ nsCOMPtr<nsIFileChannel> fc(do_QueryInterface(aChannel));
+ nsCOMPtr<nsIFileURL> fu(do_QueryInterface(aFile));
+
+ if (fc && !fu) {
+ nsCOMPtr<nsIInputStream> fileInputStream, bufferedInputStream;
+ nsresult rv = aChannel->Open(getter_AddRefs(fileInputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedInputStream),
+ fileInputStream.forget(),
+ BUFFERED_OUTPUT_SIZE);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString contentType;
+ aChannel->GetContentType(contentType);
+ return StartUpload(bufferedInputStream, aFile, contentType);
+ }
+
+ // Mark save channel as throttleable.
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(aChannel));
+ if (cos) {
+ cos->AddClassFlags(nsIClassOfService::Throttleable);
+ }
+
+ // Read from the input channel
+ nsresult rv = aChannel->AsyncOpen(this);
+ if (rv == NS_ERROR_NO_CONTENT) {
+ // Assume this is a protocol such as mailto: which does not feed out
+ // data and just ignore it.
+ return NS_SUCCESS_DONT_FIXUP;
+ }
+
+ if (NS_FAILED(rv)) {
+ // Opening failed, but do we care?
+ if (mPersistFlags & PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS) {
+ SendErrorStatusChange(true, rv, aChannel, aFile);
+ EndDownload(NS_ERROR_FAILURE);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_SUCCESS_DONT_FIXUP;
+ }
+
+ MutexAutoLock lock(mOutputMapMutex);
+ // Add the output transport to the output map with the channel as the key
+ nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aChannel);
+ mOutputMap.InsertOrUpdate(keyPtr,
+ MakeUnique<OutputData>(aFile, mURI, aCalcFileExt));
+
+ return NS_OK;
+}
+
+nsresult nsWebBrowserPersist::GetExtensionForContentType(
+ const char16_t* aContentType, char16_t** aExt) {
+ NS_ENSURE_ARG_POINTER(aContentType);
+ NS_ENSURE_ARG_POINTER(aExt);
+
+ *aExt = nullptr;
+
+ nsresult rv;
+ if (!mMIMEService) {
+ mMIMEService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_TRUE(mMIMEService, NS_ERROR_FAILURE);
+ }
+
+ nsAutoCString contentType;
+ LossyCopyUTF16toASCII(MakeStringSpan(aContentType), contentType);
+ nsAutoCString ext;
+ rv = mMIMEService->GetPrimaryExtension(contentType, ""_ns, ext);
+ if (NS_SUCCEEDED(rv)) {
+ *aExt = UTF8ToNewUnicode(ext);
+ NS_ENSURE_TRUE(*aExt, NS_ERROR_OUT_OF_MEMORY);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsWebBrowserPersist::SaveDocumentDeferred(
+ mozilla::UniquePtr<WalkData>&& aData) {
+ nsresult rv =
+ SaveDocumentInternal(aData->mDocument, aData->mFile, aData->mDataPath);
+ if (NS_FAILED(rv)) {
+ SendErrorStatusChange(true, rv, nullptr, mURI);
+ EndDownload(rv);
+ }
+ return rv;
+}
+
+nsresult nsWebBrowserPersist::SaveDocumentInternal(
+ nsIWebBrowserPersistDocument* aDocument, nsIURI* aFile, nsIURI* aDataPath) {
+ mURI = nullptr;
+ NS_ENSURE_ARG_POINTER(aDocument);
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ nsresult rv = aDocument->SetPersistFlags(mPersistFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aDocument->GetIsPrivate(&mIsPrivate);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // See if we can get the local file representation of this URI
+ nsCOMPtr<nsIFile> localFile;
+ rv = GetLocalFileFromURI(aFile, getter_AddRefs(localFile));
+
+ nsCOMPtr<nsIFile> localDataPath;
+ if (NS_SUCCEEDED(rv) && aDataPath) {
+ // See if we can get the local file representation of this URI
+ rv = GetLocalFileFromURI(aDataPath, getter_AddRefs(localDataPath));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ }
+
+ // Persist the main document
+ rv = aDocument->GetCharacterSet(mCurrentCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString uriSpec;
+ rv = aDocument->GetDocumentURI(uriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NS_NewURI(getter_AddRefs(mURI), uriSpec, mCurrentCharset.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aDocument->GetBaseURI(uriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NS_NewURI(getter_AddRefs(mCurrentBaseURI), uriSpec,
+ mCurrentCharset.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Does the caller want to fixup the referenced URIs and save those too?
+ if (aDataPath) {
+ // Basic steps are these.
+ //
+ // 1. Iterate through the document (and subdocuments) building a list
+ // of unique URIs.
+ // 2. For each URI create an OutputData entry and open a channel to save
+ // it. As each URI is saved, discover the mime type and fix up the
+ // local filename with the correct extension.
+ // 3. Store the document in a list and wait for URI persistence to finish
+ // 4. After URI persistence completes save the list of documents,
+ // fixing it up as it goes out to file.
+
+ mCurrentDataPathIsRelative = false;
+ mCurrentDataPath = aDataPath;
+ mCurrentRelativePathToData = "";
+ mCurrentThingsToPersist = 0;
+ mTargetBaseURI = aFile;
+
+ // Determine if the specified data path is relative to the
+ // specified file, (e.g. c:\docs\htmldata is relative to
+ // c:\docs\myfile.htm, but not to d:\foo\data.
+
+ // Starting with the data dir work back through its parents
+ // checking if one of them matches the base directory.
+
+ if (localDataPath && localFile) {
+ nsCOMPtr<nsIFile> baseDir;
+ localFile->GetParent(getter_AddRefs(baseDir));
+
+ nsAutoCString relativePathToData;
+ nsCOMPtr<nsIFile> dataDirParent;
+ dataDirParent = localDataPath;
+ while (dataDirParent) {
+ bool sameDir = false;
+ dataDirParent->Equals(baseDir, &sameDir);
+ if (sameDir) {
+ mCurrentRelativePathToData = relativePathToData;
+ mCurrentDataPathIsRelative = true;
+ break;
+ }
+
+ nsAutoString dirName;
+ dataDirParent->GetLeafName(dirName);
+
+ nsAutoCString newRelativePathToData;
+ newRelativePathToData =
+ NS_ConvertUTF16toUTF8(dirName) + "/"_ns + relativePathToData;
+ relativePathToData = newRelativePathToData;
+
+ nsCOMPtr<nsIFile> newDataDirParent;
+ rv = dataDirParent->GetParent(getter_AddRefs(newDataDirParent));
+ dataDirParent = newDataDirParent;
+ }
+ } else {
+ // generate a relative path if possible
+ nsCOMPtr<nsIURL> pathToBaseURL(do_QueryInterface(aFile));
+ if (pathToBaseURL) {
+ nsAutoCString relativePath; // nsACString
+ if (NS_SUCCEEDED(
+ pathToBaseURL->GetRelativeSpec(aDataPath, relativePath))) {
+ mCurrentDataPathIsRelative = true;
+ mCurrentRelativePathToData = relativePath;
+ }
+ }
+ }
+
+ // Store the document in a list so when URI persistence is done and the
+ // filenames of saved URIs are known, the documents can be fixed up and
+ // saved
+
+ auto* docData = new DocData;
+ docData->mBaseURI = mCurrentBaseURI;
+ docData->mCharset = mCurrentCharset;
+ docData->mDocument = aDocument;
+ docData->mFile = aFile;
+ mDocList.AppendElement(docData);
+
+ // Walk the DOM gathering a list of externally referenced URIs in the uri
+ // map
+ nsCOMPtr<nsIWebBrowserPersistResourceVisitor> visit =
+ new OnWalk(this, aFile, localDataPath);
+ return aDocument->ReadResources(visit);
+ } else {
+ auto* docData = new DocData;
+ docData->mBaseURI = mCurrentBaseURI;
+ docData->mCharset = mCurrentCharset;
+ docData->mDocument = aDocument;
+ docData->mFile = aFile;
+ mDocList.AppendElement(docData);
+
+ // Not walking DOMs, so go directly to serialization.
+ SerializeNextFile();
+ return NS_OK;
+ }
+}
+
+NS_IMETHODIMP
+nsWebBrowserPersist::OnWalk::VisitResource(
+ nsIWebBrowserPersistDocument* aDoc, const nsACString& aURI,
+ nsContentPolicyType aContentPolicyType) {
+ return mParent->StoreURI(aURI, aDoc, aContentPolicyType);
+}
+
+NS_IMETHODIMP
+nsWebBrowserPersist::OnWalk::VisitDocument(
+ nsIWebBrowserPersistDocument* aDoc, nsIWebBrowserPersistDocument* aSubDoc) {
+ URIData* data = nullptr;
+ nsAutoCString uriSpec;
+ nsresult rv = aSubDoc->GetDocumentURI(uriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mParent->StoreURI(uriSpec, aDoc, nsIContentPolicy::TYPE_SUBDOCUMENT,
+ false, &data);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!data) {
+ // If the URI scheme isn't persistable, then don't persist.
+ return NS_OK;
+ }
+ data->mIsSubFrame = true;
+ return mParent->SaveSubframeContent(aSubDoc, aDoc, uriSpec, data);
+}
+
+NS_IMETHODIMP
+nsWebBrowserPersist::OnWalk::VisitBrowsingContext(
+ nsIWebBrowserPersistDocument* aDoc, BrowsingContext* aContext) {
+ RefPtr<dom::CanonicalBrowsingContext> context = aContext->Canonical();
+
+ if (NS_WARN_IF(!context->GetCurrentWindowGlobal())) {
+ EndVisit(nullptr, NS_ERROR_FAILURE);
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<WebBrowserPersistDocumentParent> actor(
+ new WebBrowserPersistDocumentParent());
+
+ nsCOMPtr<nsIWebBrowserPersistDocumentReceiver> receiver =
+ new OnRemoteWalk(this, aDoc);
+ actor->SetOnReady(receiver);
+
+ RefPtr<dom::BrowserParent> browserParent =
+ context->GetCurrentWindowGlobal()->GetBrowserParent();
+
+ bool ok =
+ context->GetContentParent()->SendPWebBrowserPersistDocumentConstructor(
+ actor.release(), browserParent, context);
+
+ if (NS_WARN_IF(!ok)) {
+ // (The actor will be destroyed on constructor failure.)
+ EndVisit(nullptr, NS_ERROR_FAILURE);
+ return NS_ERROR_FAILURE;
+ }
+
+ ++mPendingDocuments;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserPersist::OnWalk::EndVisit(nsIWebBrowserPersistDocument* aDoc,
+ nsresult aStatus) {
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ if (NS_FAILED(aStatus)) {
+ mStatus = aStatus;
+ mParent->SendErrorStatusChange(true, aStatus, nullptr, mFile);
+ mParent->EndDownload(aStatus);
+ return aStatus;
+ }
+
+ if (--mPendingDocuments) {
+ // We're not done yet, wait for more.
+ return NS_OK;
+ }
+
+ mParent->FinishSaveDocumentInternal(mFile, mDataPath);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserPersist::OnRemoteWalk::OnDocumentReady(
+ nsIWebBrowserPersistDocument* aSubDocument) {
+ mVisitor->VisitDocument(mDocument, aSubDocument);
+ mVisitor->EndVisit(mDocument, NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserPersist::OnRemoteWalk::OnError(nsresult aFailure) {
+ mVisitor->EndVisit(nullptr, aFailure);
+ return NS_OK;
+}
+
+void nsWebBrowserPersist::FinishSaveDocumentInternal(nsIURI* aFile,
+ nsIFile* aDataPath) {
+ // If there are things to persist, create a directory to hold them
+ if (mCurrentThingsToPersist > 0) {
+ if (aDataPath) {
+ bool exists = false;
+ bool haveDir = false;
+
+ aDataPath->Exists(&exists);
+ if (exists) {
+ aDataPath->IsDirectory(&haveDir);
+ }
+ if (!haveDir) {
+ nsresult rv = aDataPath->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (NS_SUCCEEDED(rv)) {
+ haveDir = true;
+ } else {
+ SendErrorStatusChange(false, rv, nullptr, aFile);
+ }
+ }
+ if (!haveDir) {
+ EndDownload(NS_ERROR_FAILURE);
+ return;
+ }
+ if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE) {
+ // Add to list of things to delete later if all goes wrong
+ auto* cleanupData = new CleanupData;
+ cleanupData->mFile = aDataPath;
+ cleanupData->mIsDirectory = true;
+ mCleanupList.AppendElement(cleanupData);
+ }
+ }
+ }
+
+ if (mWalkStack.Length() > 0) {
+ mozilla::UniquePtr<WalkData> toWalk = mWalkStack.PopLastElement();
+ // Bounce this off the event loop to avoid stack overflow.
+ using WalkStorage = StoreCopyPassByRRef<decltype(toWalk)>;
+ auto saveMethod = &nsWebBrowserPersist::SaveDocumentDeferred;
+ nsCOMPtr<nsIRunnable> saveLater = NewRunnableMethod<WalkStorage>(
+ "nsWebBrowserPersist::FinishSaveDocumentInternal", this, saveMethod,
+ std::move(toWalk));
+ NS_DispatchToCurrentThread(saveLater);
+ } else {
+ // Done walking DOMs; on to the serialization phase.
+ SerializeNextFile();
+ }
+}
+
+void nsWebBrowserPersist::Cleanup() {
+ mURIMap.Clear();
+ nsClassHashtable<nsISupportsHashKey, OutputData> outputMapCopy;
+ {
+ MutexAutoLock lock(mOutputMapMutex);
+ mOutputMap.SwapElements(outputMapCopy);
+ }
+ for (const auto& key : outputMapCopy.Keys()) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(key);
+ if (channel) {
+ channel->Cancel(NS_BINDING_ABORTED);
+ }
+ }
+ outputMapCopy.Clear();
+
+ for (const auto& key : mUploadList.Keys()) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(key);
+ if (channel) {
+ channel->Cancel(NS_BINDING_ABORTED);
+ }
+ }
+ mUploadList.Clear();
+
+ uint32_t i;
+ for (i = 0; i < mDocList.Length(); i++) {
+ DocData* docData = mDocList.ElementAt(i);
+ delete docData;
+ }
+ mDocList.Clear();
+
+ for (i = 0; i < mCleanupList.Length(); i++) {
+ CleanupData* cleanupData = mCleanupList.ElementAt(i);
+ delete cleanupData;
+ }
+ mCleanupList.Clear();
+
+ mFilenameList.Clear();
+}
+
+void nsWebBrowserPersist::CleanupLocalFiles() {
+ // Two passes, the first pass cleans up files, the second pass tests
+ // for and then deletes empty directories. Directories that are not
+ // empty after the first pass must contain files from something else
+ // and are not deleted.
+ int pass;
+ for (pass = 0; pass < 2; pass++) {
+ uint32_t i;
+ for (i = 0; i < mCleanupList.Length(); i++) {
+ CleanupData* cleanupData = mCleanupList.ElementAt(i);
+ nsCOMPtr<nsIFile> file = cleanupData->mFile;
+
+ // Test if the dir / file exists (something in an earlier loop
+ // may have already removed it)
+ bool exists = false;
+ file->Exists(&exists);
+ if (!exists) continue;
+
+ // Test if the file has changed in between creation and deletion
+ // in some way that means it should be ignored
+ bool isDirectory = false;
+ file->IsDirectory(&isDirectory);
+ if (isDirectory != cleanupData->mIsDirectory)
+ continue; // A file has become a dir or vice versa !
+
+ if (pass == 0 && !isDirectory) {
+ file->Remove(false);
+ } else if (pass == 1 && isDirectory) // Directory
+ {
+ // Directories are more complicated. Enumerate through
+ // children looking for files. Any files created by the
+ // persist object would have been deleted by the first
+ // pass so if there are any there at this stage, the dir
+ // cannot be deleted because it has someone else's files
+ // in it. Empty child dirs are deleted but they must be
+ // recursed through to ensure they are actually empty.
+
+ bool isEmptyDirectory = true;
+ nsCOMArray<nsIDirectoryEnumerator> dirStack;
+ int32_t stackSize = 0;
+
+ // Push the top level enum onto the stack
+ nsCOMPtr<nsIDirectoryEnumerator> pos;
+ if (NS_SUCCEEDED(file->GetDirectoryEntries(getter_AddRefs(pos))))
+ dirStack.AppendObject(pos);
+
+ while (isEmptyDirectory && (stackSize = dirStack.Count())) {
+ // Pop the last element
+ nsCOMPtr<nsIDirectoryEnumerator> curPos;
+ curPos = dirStack[stackSize - 1];
+ dirStack.RemoveObjectAt(stackSize - 1);
+
+ nsCOMPtr<nsIFile> child;
+ if (NS_FAILED(curPos->GetNextFile(getter_AddRefs(child))) || !child) {
+ continue;
+ }
+
+ bool childIsSymlink = false;
+ child->IsSymlink(&childIsSymlink);
+ bool childIsDir = false;
+ child->IsDirectory(&childIsDir);
+ if (!childIsDir || childIsSymlink) {
+ // Some kind of file or symlink which means dir
+ // is not empty so just drop out.
+ isEmptyDirectory = false;
+ break;
+ }
+ // Push parent enumerator followed by child enumerator
+ nsCOMPtr<nsIDirectoryEnumerator> childPos;
+ child->GetDirectoryEntries(getter_AddRefs(childPos));
+ dirStack.AppendObject(curPos);
+ if (childPos) dirStack.AppendObject(childPos);
+ }
+ dirStack.Clear();
+
+ // If after all that walking the dir is deemed empty, delete it
+ if (isEmptyDirectory) {
+ file->Remove(true);
+ }
+ }
+ }
+ }
+}
+
+nsresult nsWebBrowserPersist::CalculateUniqueFilename(
+ nsIURI* aURI, nsCOMPtr<nsIURI>& aOutURI) {
+ nsCOMPtr<nsIURL> url(do_QueryInterface(aURI));
+ NS_ENSURE_TRUE(url, NS_ERROR_FAILURE);
+
+ bool nameHasChanged = false;
+ nsresult rv;
+
+ // Get the old filename
+ nsAutoCString filename;
+ rv = url->GetFileName(filename);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ nsAutoCString directory;
+ rv = url->GetDirectory(directory);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ // Split the filename into a base and an extension.
+ // e.g. "foo.html" becomes "foo" & ".html"
+ //
+ // The nsIURL methods GetFileBaseName & GetFileExtension don't
+ // preserve the dot whereas this code does to save some effort
+ // later when everything is put back together.
+ int32_t lastDot = filename.RFind(".");
+ nsAutoCString base;
+ nsAutoCString ext;
+ if (lastDot >= 0) {
+ filename.Mid(base, 0, lastDot);
+ filename.Mid(ext, lastDot, filename.Length() - lastDot); // includes dot
+ } else {
+ // filename contains no dot
+ base = filename;
+ }
+
+ // Test if the filename is longer than allowed by the OS
+ int32_t needToChop = filename.Length() - kDefaultMaxFilenameLength;
+ if (needToChop > 0) {
+ // Truncate the base first and then the ext if necessary
+ if (base.Length() > (uint32_t)needToChop) {
+ base.Truncate(base.Length() - needToChop);
+ } else {
+ needToChop -= base.Length() - 1;
+ base.Truncate(1);
+ if (ext.Length() > (uint32_t)needToChop) {
+ ext.Truncate(ext.Length() - needToChop);
+ } else {
+ ext.Truncate(0);
+ }
+ // If kDefaultMaxFilenameLength were 1 we'd be in trouble here,
+ // but that won't happen because it will be set to a sensible
+ // value.
+ }
+
+ filename.Assign(base);
+ filename.Append(ext);
+ nameHasChanged = true;
+ }
+
+ // Ensure the filename is unique
+ // Create a filename if it's empty, or if the filename / datapath is
+ // already taken by another URI and create an alternate name.
+
+ if (base.IsEmpty() || !mFilenameList.IsEmpty()) {
+ nsAutoCString tmpPath;
+ nsAutoCString tmpBase;
+ uint32_t duplicateCounter = 1;
+ while (true) {
+ // Make a file name,
+ // Foo become foo_001, foo_002, etc.
+ // Empty files become _001, _002 etc.
+
+ if (base.IsEmpty() || duplicateCounter > 1) {
+ SmprintfPointer tmp = mozilla::Smprintf("_%03d", duplicateCounter);
+ NS_ENSURE_TRUE(tmp, NS_ERROR_OUT_OF_MEMORY);
+ if (filename.Length() < kDefaultMaxFilenameLength - 4) {
+ tmpBase = base;
+ } else {
+ base.Mid(tmpBase, 0, base.Length() - 4);
+ }
+ tmpBase.Append(tmp.get());
+ } else {
+ tmpBase = base;
+ }
+
+ tmpPath.Assign(directory);
+ tmpPath.Append(tmpBase);
+ tmpPath.Append(ext);
+
+ // Test if the name is a duplicate
+ if (!mFilenameList.Contains(tmpPath)) {
+ if (!base.Equals(tmpBase)) {
+ filename.Assign(tmpBase);
+ filename.Append(ext);
+ nameHasChanged = true;
+ }
+ break;
+ }
+ duplicateCounter++;
+ }
+ }
+
+ // Add name to list of those already used
+ nsAutoCString newFilepath(directory);
+ newFilepath.Append(filename);
+ mFilenameList.AppendElement(newFilepath);
+
+ // Update the uri accordingly if the filename actually changed
+ if (nameHasChanged) {
+ // Final sanity test
+ if (filename.Length() > kDefaultMaxFilenameLength) {
+ NS_WARNING(
+ "Filename wasn't truncated less than the max file length - how can "
+ "that be?");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIFile> localFile;
+ GetLocalFileFromURI(aURI, getter_AddRefs(localFile));
+
+ if (localFile) {
+ nsAutoString filenameAsUnichar;
+ CopyASCIItoUTF16(filename, filenameAsUnichar);
+ localFile->SetLeafName(filenameAsUnichar);
+
+ // Resync the URI with the file after the extension has been appended
+ return NS_MutateURI(aURI)
+ .Apply(&nsIFileURLMutator::SetFile, localFile)
+ .Finalize(aOutURI);
+ }
+ return NS_MutateURI(url)
+ .Apply(&nsIURLMutator::SetFileName, filename, nullptr)
+ .Finalize(aOutURI);
+ }
+
+ // TODO (:valentin) This method should always clone aURI
+ aOutURI = aURI;
+ return NS_OK;
+}
+
+nsresult nsWebBrowserPersist::MakeFilenameFromURI(nsIURI* aURI,
+ nsString& aFilename) {
+ // Try to get filename from the URI.
+ nsAutoString fileName;
+
+ // Get a suggested file name from the URL but strip it of characters
+ // likely to cause the name to be illegal.
+
+ nsCOMPtr<nsIURL> url(do_QueryInterface(aURI));
+ if (url) {
+ nsAutoCString nameFromURL;
+ url->GetFileName(nameFromURL);
+ if (mPersistFlags & PERSIST_FLAGS_DONT_CHANGE_FILENAMES) {
+ CopyASCIItoUTF16(NS_UnescapeURL(nameFromURL), fileName);
+ aFilename = fileName;
+ return NS_OK;
+ }
+ if (!nameFromURL.IsEmpty()) {
+ // Unescape the file name (GetFileName escapes it)
+ NS_UnescapeURL(nameFromURL);
+ uint32_t nameLength = 0;
+ const char* p = nameFromURL.get();
+ for (; *p && *p != ';' && *p != '?' && *p != '#' && *p != '.'; p++) {
+ if (IsAsciiAlpha(*p) || IsAsciiDigit(*p) || *p == '.' || *p == '-' ||
+ *p == '_' || (*p == ' ')) {
+ fileName.Append(char16_t(*p));
+ if (++nameLength == kDefaultMaxFilenameLength) {
+ // Note:
+ // There is no point going any further since it will be
+ // truncated in CalculateUniqueFilename anyway.
+ // More importantly, certain implementations of
+ // nsIFile (e.g. the Mac impl) might truncate
+ // names in undesirable ways, such as truncating from
+ // the middle, inserting ellipsis and so on.
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Empty filenames can confuse the local file object later
+ // when it attempts to set the leaf name in CalculateUniqueFilename
+ // for duplicates and ends up replacing the parent dir. To avoid
+ // the problem, all filenames are made at least one character long.
+ if (fileName.IsEmpty()) {
+ fileName.Append(char16_t('a')); // 'a' is for arbitrary
+ }
+
+ aFilename = fileName;
+ return NS_OK;
+}
+
+nsresult nsWebBrowserPersist::CalculateAndAppendFileExt(
+ nsIURI* aURI, nsIChannel* aChannel, nsIURI* aOriginalURIWithExtension,
+ nsCOMPtr<nsIURI>& aOutURI) {
+ nsresult rv = NS_OK;
+
+ if (!mMIMEService) {
+ mMIMEService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_TRUE(mMIMEService, NS_ERROR_FAILURE);
+ }
+
+ nsAutoCString contentType;
+
+ // Get the content type from the channel
+ aChannel->GetContentType(contentType);
+
+ // Get the content type from the MIME service
+ if (contentType.IsEmpty()) {
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetOriginalURI(getter_AddRefs(uri));
+ mMIMEService->GetTypeFromURI(uri, contentType);
+ }
+
+ // Validate the filename
+ if (!contentType.IsEmpty()) {
+ nsAutoString newFileName;
+ if (NS_SUCCEEDED(mMIMEService->GetValidFileName(
+ aChannel, contentType, aOriginalURIWithExtension,
+ nsIMIMEService::VALIDATE_DEFAULT, newFileName))) {
+ nsCOMPtr<nsIFile> localFile;
+ GetLocalFileFromURI(aURI, getter_AddRefs(localFile));
+ if (localFile) {
+ localFile->SetLeafName(newFileName);
+
+ // Resync the URI with the file after the extension has been appended
+ return NS_MutateURI(aURI)
+ .Apply(&nsIFileURLMutator::SetFile, localFile)
+ .Finalize(aOutURI);
+ }
+ return NS_MutateURI(aURI)
+ .Apply(&nsIURLMutator::SetFileName,
+ NS_ConvertUTF16toUTF8(newFileName), nullptr)
+ .Finalize(aOutURI);
+ }
+ }
+
+ // TODO (:valentin) This method should always clone aURI
+ aOutURI = aURI;
+ return NS_OK;
+}
+
+// Note: the MakeOutputStream helpers can be called from a background thread.
+nsresult nsWebBrowserPersist::MakeOutputStream(
+ nsIURI* aURI, nsIOutputStream** aOutputStream) {
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> localFile;
+ GetLocalFileFromURI(aURI, getter_AddRefs(localFile));
+ if (localFile)
+ rv = MakeOutputStreamFromFile(localFile, aOutputStream);
+ else
+ rv = MakeOutputStreamFromURI(aURI, aOutputStream);
+
+ return rv;
+}
+
+nsresult nsWebBrowserPersist::MakeOutputStreamFromFile(
+ nsIFile* aFile, nsIOutputStream** aOutputStream) {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIFileOutputStream> fileOutputStream =
+ do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ // XXX brade: get the right flags here!
+ int32_t ioFlags = -1;
+ if (mPersistFlags & nsIWebBrowserPersist::PERSIST_FLAGS_APPEND_TO_FILE)
+ ioFlags = PR_APPEND | PR_CREATE_FILE | PR_WRONLY;
+ rv = fileOutputStream->Init(aFile, ioFlags, -1, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewBufferedOutputStream(aOutputStream, fileOutputStream.forget(),
+ BUFFERED_OUTPUT_SIZE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE) {
+ // Add to cleanup list in event of failure
+ auto* cleanupData = new CleanupData;
+ cleanupData->mFile = aFile;
+ cleanupData->mIsDirectory = false;
+ if (NS_IsMainThread()) {
+ mCleanupList.AppendElement(cleanupData);
+ } else {
+ // If we're on a background thread, add the cleanup back on the main
+ // thread.
+ RefPtr<Runnable> addCleanup = NS_NewRunnableFunction(
+ "nsWebBrowserPersist::AddCleanupToList",
+ [self = RefPtr{this}, cleanup = std::move(cleanupData)]() {
+ self->mCleanupList.AppendElement(cleanup);
+ });
+ NS_DispatchToMainThread(addCleanup);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsWebBrowserPersist::MakeOutputStreamFromURI(
+ nsIURI* aURI, nsIOutputStream** aOutputStream) {
+ uint32_t segsize = 8192;
+ uint32_t maxsize = uint32_t(-1);
+ nsCOMPtr<nsIStorageStream> storStream;
+ nsresult rv =
+ NS_NewStorageStream(segsize, maxsize, getter_AddRefs(storStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_SUCCESS(CallQueryInterface(storStream, aOutputStream),
+ NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+void nsWebBrowserPersist::FinishDownload() {
+ // We call FinishDownload when we run out of things to download for this
+ // persist operation, by dispatching this method to the main thread. By now,
+ // it's possible that we have been canceled or encountered an error earlier
+ // in the download, or something else called EndDownload. In that case, don't
+ // re-run EndDownload.
+ if (mEndCalled) {
+ return;
+ }
+ EndDownload(NS_OK);
+}
+
+void nsWebBrowserPersist::EndDownload(nsresult aResult) {
+ MOZ_ASSERT(NS_IsMainThread(), "Should end download on the main thread.");
+
+ // Really this should just never happen, but if it does, at least avoid
+ // no-op notifications or pretending we succeeded if we already failed.
+ if (mEndCalled && (NS_SUCCEEDED(aResult) || mPersistResult == aResult)) {
+ return;
+ }
+
+ // Store the error code in the result if it is an error
+ if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(aResult)) {
+ mPersistResult = aResult;
+ }
+
+ if (mEndCalled) {
+ MOZ_ASSERT(!mEndCalled, "Should only end the download once.");
+ return;
+ }
+ mEndCalled = true;
+
+ ClosePromise::All(GetCurrentSerialEventTarget(), mFileClosePromises)
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}, aResult]() {
+ self->EndDownloadInternal(aResult);
+ });
+}
+
+void nsWebBrowserPersist::EndDownloadInternal(nsresult aResult) {
+ // mCompleted needs to be set before issuing the stop notification.
+ // (Bug 1224437)
+ mCompleted = true;
+ // State stop notification
+ if (mProgressListener) {
+ mProgressListener->OnStateChange(
+ nullptr, nullptr,
+ nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_NETWORK,
+ mPersistResult);
+ }
+
+ // Do file cleanup if required
+ if (NS_FAILED(aResult) &&
+ (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE)) {
+ CleanupLocalFiles();
+ }
+
+ // Cleanup the channels
+ Cleanup();
+
+ mProgressListener = nullptr;
+ mProgressListener2 = nullptr;
+ mEventSink = nullptr;
+}
+
+nsresult nsWebBrowserPersist::FixRedirectedChannelEntry(
+ nsIChannel* aNewChannel) {
+ NS_ENSURE_ARG_POINTER(aNewChannel);
+
+ // Iterate through existing open channels looking for one with a URI
+ // matching the one specified.
+ nsCOMPtr<nsIURI> originalURI;
+ aNewChannel->GetOriginalURI(getter_AddRefs(originalURI));
+ nsISupports* matchingKey = nullptr;
+ for (nsISupports* key : mOutputMap.Keys()) {
+ nsCOMPtr<nsIChannel> thisChannel = do_QueryInterface(key);
+ nsCOMPtr<nsIURI> thisURI;
+
+ thisChannel->GetOriginalURI(getter_AddRefs(thisURI));
+
+ // Compare this channel's URI to the one passed in.
+ bool matchingURI = false;
+ thisURI->Equals(originalURI, &matchingURI);
+ if (matchingURI) {
+ matchingKey = key;
+ break;
+ }
+ }
+
+ if (matchingKey) {
+ // We only get called from OnStartRequest, so this is always on the
+ // main thread. Make sure we don't pull the rug from under anything else.
+ MutexAutoLock lock(mOutputMapMutex);
+ // If a match was found, remove the data entry with the old channel
+ // key and re-add it with the new channel key.
+ mozilla::UniquePtr<OutputData> outputData;
+ mOutputMap.Remove(matchingKey, &outputData);
+ NS_ENSURE_TRUE(outputData, NS_ERROR_FAILURE);
+
+ // Store data again with new channel unless told to ignore redirects.
+ if (!(mPersistFlags & PERSIST_FLAGS_IGNORE_REDIRECTED_DATA)) {
+ nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aNewChannel);
+ mOutputMap.InsertOrUpdate(keyPtr, std::move(outputData));
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsWebBrowserPersist::CalcTotalProgress() {
+ mTotalCurrentProgress = 0;
+ mTotalMaxProgress = 0;
+
+ if (mOutputMap.Count() > 0) {
+ // Total up the progress of each output stream
+ for (const auto& data : mOutputMap.Values()) {
+ // Only count toward total progress if destination file is local.
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(data->mFile);
+ if (fileURL) {
+ mTotalCurrentProgress += data->mSelfProgress;
+ mTotalMaxProgress += data->mSelfProgressMax;
+ }
+ }
+ }
+
+ if (mUploadList.Count() > 0) {
+ // Total up the progress of each upload
+ for (const auto& data : mUploadList.Values()) {
+ if (data) {
+ mTotalCurrentProgress += data->mSelfProgress;
+ mTotalMaxProgress += data->mSelfProgressMax;
+ }
+ }
+ }
+
+ // XXX this code seems pretty bogus and pointless
+ if (mTotalCurrentProgress == 0 && mTotalMaxProgress == 0) {
+ // No output streams so we must be complete
+ mTotalCurrentProgress = 10000;
+ mTotalMaxProgress = 10000;
+ }
+}
+
+nsresult nsWebBrowserPersist::StoreURI(const nsACString& aURI,
+ nsIWebBrowserPersistDocument* aDoc,
+ nsContentPolicyType aContentPolicyType,
+ bool aNeedsPersisting, URIData** aData) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI, mCurrentCharset.get(),
+ mCurrentBaseURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return StoreURI(uri, aDoc, aContentPolicyType, aNeedsPersisting, aData);
+}
+
+nsresult nsWebBrowserPersist::StoreURI(nsIURI* aURI,
+ nsIWebBrowserPersistDocument* aDoc,
+ nsContentPolicyType aContentPolicyType,
+ bool aNeedsPersisting, URIData** aData) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ if (aData) {
+ *aData = nullptr;
+ }
+
+ // Test if this URI should be persisted. By default
+ // we should assume the URI is persistable.
+ bool doNotPersistURI;
+ nsresult rv = NS_URIChainHasFlags(
+ aURI, nsIProtocolHandler::URI_NON_PERSISTABLE, &doNotPersistURI);
+ if (NS_FAILED(rv)) {
+ doNotPersistURI = false;
+ }
+
+ if (doNotPersistURI) {
+ return NS_OK;
+ }
+
+ URIData* data = nullptr;
+ MakeAndStoreLocalFilenameInURIMap(aURI, aDoc, aContentPolicyType,
+ aNeedsPersisting, &data);
+ if (aData) {
+ *aData = data;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsWebBrowserPersist::URIData::GetLocalURI(nsIURI* targetBaseURI,
+ nsCString& aSpecOut) {
+ aSpecOut.SetIsVoid(true);
+ if (!mNeedsFixup) {
+ return NS_OK;
+ }
+ nsresult rv;
+ nsCOMPtr<nsIURI> fileAsURI;
+ if (mFile) {
+ fileAsURI = mFile;
+ } else {
+ fileAsURI = mDataPath;
+ rv = AppendPathToURI(fileAsURI, mFilename, fileAsURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // remove username/password if present
+ Unused << NS_MutateURI(fileAsURI).SetUserPass(""_ns).Finalize(fileAsURI);
+
+ // reset node attribute
+ // Use relative or absolute links
+ if (mDataPathIsRelative) {
+ bool isEqual = false;
+ if (NS_SUCCEEDED(mRelativeDocumentURI->Equals(targetBaseURI, &isEqual)) &&
+ isEqual) {
+ nsCOMPtr<nsIURL> url(do_QueryInterface(fileAsURI));
+ if (!url) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString filename;
+ url->GetFileName(filename);
+
+ nsAutoCString rawPathURL(mRelativePathToData);
+ rawPathURL.Append(filename);
+
+ rv = NS_EscapeURL(rawPathURL, esc_FilePath, aSpecOut, fallible);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ nsAutoCString rawPathURL;
+
+ nsCOMPtr<nsIFile> dataFile;
+ rv = GetLocalFileFromURI(mFile, getter_AddRefs(dataFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> docFile;
+ rv = GetLocalFileFromURI(targetBaseURI, getter_AddRefs(docFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> parentDir;
+ rv = docFile->GetParent(getter_AddRefs(parentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = dataFile->GetRelativePath(parentDir, rawPathURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_EscapeURL(rawPathURL, esc_FilePath, aSpecOut, fallible);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else {
+ fileAsURI->GetSpec(aSpecOut);
+ }
+ if (mIsSubFrame) {
+ AppendUTF16toUTF8(mSubFrameExt, aSpecOut);
+ }
+
+ return NS_OK;
+}
+
+bool nsWebBrowserPersist::DocumentEncoderExists(const char* aContentType) {
+ return do_getDocumentTypeSupportedForEncoding(aContentType);
+}
+
+nsresult nsWebBrowserPersist::SaveSubframeContent(
+ nsIWebBrowserPersistDocument* aFrameContent,
+ nsIWebBrowserPersistDocument* aParentDocument, const nsCString& aURISpec,
+ URIData* aData) {
+ NS_ENSURE_ARG_POINTER(aData);
+
+ // Extract the content type for the frame's contents.
+ nsAutoCString contentType;
+ nsresult rv = aFrameContent->GetContentType(contentType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString ext;
+ GetExtensionForContentType(NS_ConvertASCIItoUTF16(contentType).get(),
+ getter_Copies(ext));
+
+ // We must always have an extension so we will try to re-assign
+ // the original extension if GetExtensionForContentType fails.
+ if (ext.IsEmpty()) {
+ nsCOMPtr<nsIURI> docURI;
+ rv = NS_NewURI(getter_AddRefs(docURI), aURISpec, mCurrentCharset.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURL> url(do_QueryInterface(docURI, &rv));
+ nsAutoCString extension;
+ if (NS_SUCCEEDED(rv)) {
+ url->GetFileExtension(extension);
+ } else {
+ extension.AssignLiteral("htm");
+ }
+ aData->mSubFrameExt.Assign(char16_t('.'));
+ AppendUTF8toUTF16(extension, aData->mSubFrameExt);
+ } else {
+ aData->mSubFrameExt.Assign(char16_t('.'));
+ aData->mSubFrameExt.Append(ext);
+ }
+
+ nsString filenameWithExt = aData->mFilename;
+ filenameWithExt.Append(aData->mSubFrameExt);
+
+ // Work out the path for the subframe
+ nsCOMPtr<nsIURI> frameURI = mCurrentDataPath;
+ rv = AppendPathToURI(frameURI, filenameWithExt, frameURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Work out the path for the subframe data
+ nsCOMPtr<nsIURI> frameDataURI = mCurrentDataPath;
+ nsAutoString newFrameDataPath(aData->mFilename);
+
+ // Append _data
+ newFrameDataPath.AppendLiteral("_data");
+ rv = AppendPathToURI(frameDataURI, newFrameDataPath, frameDataURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make frame document & data path conformant and unique
+ nsCOMPtr<nsIURI> out;
+ rv = CalculateUniqueFilename(frameURI, out);
+ NS_ENSURE_SUCCESS(rv, rv);
+ frameURI = out;
+
+ rv = CalculateUniqueFilename(frameDataURI, out);
+ NS_ENSURE_SUCCESS(rv, rv);
+ frameDataURI = out;
+
+ mCurrentThingsToPersist++;
+
+ // We shouldn't use SaveDocumentInternal for the contents
+ // of frames that are not documents, e.g. images.
+ if (DocumentEncoderExists(contentType.get())) {
+ auto toWalk = mozilla::MakeUnique<WalkData>();
+ toWalk->mDocument = aFrameContent;
+ toWalk->mFile = frameURI;
+ toWalk->mDataPath = frameDataURI;
+ mWalkStack.AppendElement(std::move(toWalk));
+ } else {
+ nsContentPolicyType policyType = nsIContentPolicy::TYPE_OTHER;
+ if (StringBeginsWith(contentType, "image/"_ns)) {
+ policyType = nsIContentPolicy::TYPE_IMAGE;
+ } else if (StringBeginsWith(contentType, "audio/"_ns) ||
+ StringBeginsWith(contentType, "video/"_ns)) {
+ policyType = nsIContentPolicy::TYPE_MEDIA;
+ }
+ rv = StoreURI(aURISpec, aParentDocument, policyType);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Store the updated uri to the frame
+ aData->mFile = frameURI;
+ aData->mSubFrameExt.Truncate(); // we already put this in frameURI
+
+ return NS_OK;
+}
+
+nsresult nsWebBrowserPersist::CreateChannelFromURI(nsIURI* aURI,
+ nsIChannel** aChannel) {
+ nsresult rv = NS_OK;
+ *aChannel = nullptr;
+
+ rv = NS_NewChannel(aChannel, aURI, nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_ARG_POINTER(*aChannel);
+
+ rv = (*aChannel)->SetNotificationCallbacks(
+ static_cast<nsIInterfaceRequestor*>(this));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+// we store the current location as the key (absolutized version of domnode's
+// attribute's value)
+nsresult nsWebBrowserPersist::MakeAndStoreLocalFilenameInURIMap(
+ nsIURI* aURI, nsIWebBrowserPersistDocument* aDoc,
+ nsContentPolicyType aContentPolicyType, bool aNeedsPersisting,
+ URIData** aData) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsAutoCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ // Create a sensibly named filename for the URI and store in the URI map
+ URIData* data;
+ if (mURIMap.Get(spec, &data)) {
+ if (aNeedsPersisting) {
+ data->mNeedsPersisting = true;
+ }
+ if (aData) {
+ *aData = data;
+ }
+ return NS_OK;
+ }
+
+ // Create a unique file name for the uri
+ nsString filename;
+ rv = MakeFilenameFromURI(aURI, filename);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ // Store the file name
+ data = new URIData;
+
+ data->mContentPolicyType = aContentPolicyType;
+ data->mNeedsPersisting = aNeedsPersisting;
+ data->mNeedsFixup = true;
+ data->mFilename = filename;
+ data->mSaved = false;
+ data->mIsSubFrame = false;
+ data->mDataPath = mCurrentDataPath;
+ data->mDataPathIsRelative = mCurrentDataPathIsRelative;
+ data->mRelativePathToData = mCurrentRelativePathToData;
+ data->mRelativeDocumentURI = mTargetBaseURI;
+ data->mCharset = mCurrentCharset;
+
+ aDoc->GetPrincipal(getter_AddRefs(data->mTriggeringPrincipal));
+ aDoc->GetCookieJarSettings(getter_AddRefs(data->mCookieJarSettings));
+
+ if (aNeedsPersisting) mCurrentThingsToPersist++;
+
+ mURIMap.InsertOrUpdate(spec, UniquePtr<URIData>(data));
+ if (aData) {
+ *aData = data;
+ }
+
+ return NS_OK;
+}
+
+// Decide if we need to apply conversion to the passed channel.
+void nsWebBrowserPersist::SetApplyConversionIfNeeded(nsIChannel* aChannel) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface(aChannel, &rv);
+ if (NS_FAILED(rv)) return;
+
+ // Set the default conversion preference:
+ encChannel->SetApplyConversion(false);
+
+ nsCOMPtr<nsIURI> thisURI;
+ aChannel->GetURI(getter_AddRefs(thisURI));
+ nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(thisURI));
+ if (!sourceURL) return;
+ nsAutoCString extension;
+ sourceURL->GetFileExtension(extension);
+
+ nsCOMPtr<nsIUTF8StringEnumerator> encEnum;
+ encChannel->GetContentEncodings(getter_AddRefs(encEnum));
+ if (!encEnum) return;
+ nsCOMPtr<nsIExternalHelperAppService> helperAppService =
+ do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return;
+ bool hasMore;
+ rv = encEnum->HasMore(&hasMore);
+ if (NS_SUCCEEDED(rv) && hasMore) {
+ nsAutoCString encType;
+ rv = encEnum->GetNext(encType);
+ if (NS_SUCCEEDED(rv)) {
+ bool applyConversion = false;
+ rv = helperAppService->ApplyDecodingForExtension(extension, encType,
+ &applyConversion);
+ if (NS_SUCCEEDED(rv)) encChannel->SetApplyConversion(applyConversion);
+ }
+ }
+}
diff --git a/dom/webbrowserpersist/nsWebBrowserPersist.h b/dom/webbrowserpersist/nsWebBrowserPersist.h
new file mode 100644
index 0000000000..47f13b1cb4
--- /dev/null
+++ b/dom/webbrowserpersist/nsWebBrowserPersist.h
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWebBrowserPersist_h__
+#define nsWebBrowserPersist_h__
+
+#include "nsCOMPtr.h"
+#include "nsWeakReference.h"
+
+#include "nsIInterfaceRequestor.h"
+#include "nsIMIMEService.h"
+#include "nsIStreamListener.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsIChannel.h"
+#include "nsIProgressEventSink.h"
+#include "nsIFile.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIWebProgressListener2.h"
+#include "nsIWebBrowserPersist.h"
+#include "nsIWebBrowserPersistDocument.h"
+
+#include "mozilla/MozPromise.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/UniquePtr.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+
+class nsIStorageStream;
+class nsIWebBrowserPersistDocument;
+
+using ClosePromise = mozilla::MozPromise<nsresult, nsresult, true>;
+
+class nsWebBrowserPersist final : public nsIInterfaceRequestor,
+ public nsIWebBrowserPersist,
+ public nsIStreamListener,
+ public nsIThreadRetargetableStreamListener,
+ public nsIProgressEventSink,
+ public nsSupportsWeakReference {
+ friend class nsEncoderNodeFixup;
+
+ // Public members
+ public:
+ nsWebBrowserPersist();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICANCELABLE
+ NS_DECL_NSIWEBBROWSERPERSIST
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+ NS_DECL_NSIPROGRESSEVENTSINK
+
+ // Private members
+ private:
+ virtual ~nsWebBrowserPersist();
+ nsresult SaveURIInternal(nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal,
+ nsContentPolicyType aContentPolicyType,
+ uint32_t aCacheKey, nsIReferrerInfo* aReferrerInfo,
+ nsICookieJarSettings* aCookieJarSettings,
+ nsIInputStream* aPostData, const char* aExtraHeaders,
+ nsIURI* aFile, bool aCalcFileExt, bool aIsPrivate);
+ nsresult SaveChannelInternal(nsIChannel* aChannel, nsIURI* aFile,
+ bool aCalcFileExt);
+ nsresult SaveDocumentInternal(nsIWebBrowserPersistDocument* aDocument,
+ nsIURI* aFile, nsIURI* aDataPath);
+ nsresult SaveDocuments();
+ void FinishSaveDocumentInternal(nsIURI* aFile, nsIFile* aDataPath);
+ nsresult GetExtensionForContentType(const char16_t* aContentType,
+ char16_t** aExt);
+
+ struct CleanupData;
+ struct DocData;
+ struct OutputData;
+ struct UploadData;
+ struct URIData;
+ struct WalkData;
+
+ class OnWalk;
+ class OnRemoteWalk;
+ class OnWrite;
+ class FlatURIMap;
+ friend class OnWalk;
+ friend class OnWrite;
+
+ nsresult SaveDocumentDeferred(mozilla::UniquePtr<WalkData>&& aData);
+ void Cleanup();
+ void CleanupLocalFiles();
+ nsresult GetValidURIFromObject(nsISupports* aObject, nsIURI** aURI) const;
+ static nsresult GetLocalFileFromURI(nsIURI* aURI, nsIFile** aLocalFile);
+ static nsresult AppendPathToURI(nsIURI* aURI, const nsAString& aPath,
+ nsCOMPtr<nsIURI>& aOutURI);
+ nsresult MakeAndStoreLocalFilenameInURIMap(
+ nsIURI* aURI, nsIWebBrowserPersistDocument* aDoc,
+ nsContentPolicyType aContentPolicyType, bool aNeedsPersisting,
+ URIData** aData);
+ nsresult MakeOutputStream(nsIURI* aFile, nsIOutputStream** aOutputStream);
+ nsresult MakeOutputStreamFromFile(nsIFile* aFile,
+ nsIOutputStream** aOutputStream);
+ nsresult MakeOutputStreamFromURI(nsIURI* aURI, nsIOutputStream** aOutStream);
+ nsresult CreateChannelFromURI(nsIURI* aURI, nsIChannel** aChannel);
+ nsresult StartUpload(nsIStorageStream* aOutStream, nsIURI* aDestinationURI,
+ const nsACString& aContentType);
+ nsresult StartUpload(nsIInputStream* aInputStream, nsIURI* aDestinationURI,
+ const nsACString& aContentType);
+ nsresult CalculateAndAppendFileExt(nsIURI* aURI, nsIChannel* aChannel,
+ nsIURI* aOriginalURIWithExtension,
+ nsCOMPtr<nsIURI>& aOutURI);
+ nsresult CalculateUniqueFilename(nsIURI* aURI, nsCOMPtr<nsIURI>& aOutURI);
+ nsresult MakeFilenameFromURI(nsIURI* aURI, nsString& aFilename);
+ nsresult StoreURI(const nsACString& aURI, nsIWebBrowserPersistDocument* aDoc,
+ nsContentPolicyType aContentPolicyType,
+ bool aNeedsPersisting = true, URIData** aData = nullptr);
+ nsresult StoreURI(nsIURI* aURI, nsIWebBrowserPersistDocument* aDoc,
+ nsContentPolicyType aContentPolicyType,
+ bool aNeedsPersisting = true, URIData** aData = nullptr);
+ bool DocumentEncoderExists(const char* aContentType);
+
+ nsresult SaveSubframeContent(nsIWebBrowserPersistDocument* aFrameContent,
+ nsIWebBrowserPersistDocument* aParentDocument,
+ const nsCString& aURISpec, URIData* aData);
+ nsresult SendErrorStatusChange(bool aIsReadError, nsresult aResult,
+ nsIRequest* aRequest, nsIURI* aURI);
+
+ nsresult FixRedirectedChannelEntry(nsIChannel* aNewChannel);
+
+ void FinishDownload();
+ void EndDownload(nsresult aResult);
+ void EndDownloadInternal(nsresult aResult);
+ void SerializeNextFile();
+ void CalcTotalProgress();
+
+ void SetApplyConversionIfNeeded(nsIChannel* aChannel);
+
+ nsCOMPtr<nsIURI> mCurrentDataPath;
+ bool mCurrentDataPathIsRelative;
+ nsCString mCurrentRelativePathToData;
+ nsCOMPtr<nsIURI> mCurrentBaseURI;
+ nsCString mCurrentCharset;
+ nsCOMPtr<nsIURI> mTargetBaseURI;
+ uint32_t mCurrentThingsToPersist;
+
+ nsCOMPtr<nsIMIMEService> mMIMEService;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIWebProgressListener> mProgressListener;
+ /**
+ * Progress listener for 64-bit values; this is the same object as
+ * mProgressListener, but is a member to avoid having to qi it for each
+ * progress notification.
+ */
+ nsCOMPtr<nsIWebProgressListener2> mProgressListener2;
+ nsCOMPtr<nsIProgressEventSink> mEventSink;
+ mozilla::Mutex mOutputMapMutex MOZ_UNANNOTATED;
+ nsClassHashtable<nsISupportsHashKey, OutputData> mOutputMap;
+ nsClassHashtable<nsISupportsHashKey, UploadData> mUploadList;
+ nsCOMPtr<nsISerialEventTarget> mBackgroundQueue;
+ nsTArray<RefPtr<ClosePromise>> mFileClosePromises;
+ nsClassHashtable<nsCStringHashKey, URIData> mURIMap;
+ nsCOMPtr<nsIWebBrowserPersistURIMap> mFlatURIMap;
+ nsTArray<mozilla::UniquePtr<WalkData>> mWalkStack;
+ nsTArray<DocData*> mDocList;
+ nsTArray<CleanupData*> mCleanupList;
+ nsTArray<nsCString> mFilenameList;
+ bool mFirstAndOnlyUse;
+ bool mSavingDocument;
+ // mCancel is used from both the main thread, and (inside OnDataAvailable)
+ // from a background thread.
+ mozilla::Atomic<bool> mCancel;
+ bool mEndCalled;
+ bool mCompleted;
+ bool mStartSaving;
+ bool mReplaceExisting;
+ bool mSerializingOutput;
+ bool mIsPrivate;
+ // mPersistFlags can be modified on the main thread, and can be read from
+ // a background thread when OnDataAvailable calls MakeOutputStreamFromFile.
+ mozilla::Atomic<uint32_t> mPersistFlags;
+ nsresult mPersistResult;
+ int64_t mTotalCurrentProgress;
+ int64_t mTotalMaxProgress;
+ int16_t mWrapColumn;
+ uint32_t mEncodingFlags;
+ nsString mContentType;
+};
+
+#endif