summaryrefslogtreecommitdiff
path: root/dom/webbrowserpersist
diff options
context:
space:
mode:
authorMoonchild <moonchild@palemoon.org>2022-02-12 17:47:03 +0000
committerMatt A. Tobin <email@mattatobin.com>2022-02-12 14:23:18 -0600
commitf66babd8b8368ada3e5aa29cdef1c77291ee4ddd (patch)
treee3842e2a6bf19090185f9c475b3846e1bb79ac97 /dom/webbrowserpersist
downloadGRE-f66babd8b8368ada3e5aa29cdef1c77291ee4ddd.tar.gz
Create the Goanna Runtime Environment
Diffstat (limited to 'dom/webbrowserpersist')
-rw-r--r--dom/webbrowserpersist/PWebBrowserPersistDocument.ipdl90
-rw-r--r--dom/webbrowserpersist/PWebBrowserPersistResources.ipdl26
-rw-r--r--dom/webbrowserpersist/PWebBrowserPersistSerialize.ipdl29
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistDocumentChild.cpp159
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistDocumentChild.h62
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistDocumentParent.cpp125
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistDocumentParent.h79
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp1475
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistLocalDocument.h51
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp186
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistRemoteDocument.h55
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistResourcesChild.cpp73
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistResourcesChild.h31
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistResourcesParent.cpp87
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistResourcesParent.h54
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistSerializeChild.cpp143
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistSerializeChild.h39
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistSerializeParent.cpp90
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistSerializeParent.h49
-rw-r--r--dom/webbrowserpersist/moz.build48
-rw-r--r--dom/webbrowserpersist/nsCWebBrowserPersist.idl15
-rw-r--r--dom/webbrowserpersist/nsIWebBrowserPersist.idl286
-rw-r--r--dom/webbrowserpersist/nsIWebBrowserPersistDocument.idl197
-rw-r--r--dom/webbrowserpersist/nsIWebBrowserPersistable.idl41
-rw-r--r--dom/webbrowserpersist/nsWebBrowserPersist.cpp2814
-rw-r--r--dom/webbrowserpersist/nsWebBrowserPersist.h181
26 files changed, 6485 insertions, 0 deletions
diff --git a/dom/webbrowserpersist/PWebBrowserPersistDocument.ipdl b/dom/webbrowserpersist/PWebBrowserPersistDocument.ipdl
new file mode 100644
index 000000000..143af0ebd
--- /dev/null
+++ b/dom/webbrowserpersist/PWebBrowserPersistDocument.ipdl
@@ -0,0 +1,90 @@
+/* -*- 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 InputStreamParams;
+
+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;
+ nsString referrer;
+ nsString contentDisposition;
+ uint32_t cacheKey;
+ uint32_t persistFlags;
+};
+
+// 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.
+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,
+ OptionalInputStreamParams postData,
+ FileDescriptor[] postFiles);
+ 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 000000000..3c36ae495
--- /dev/null
+++ b/dom/webbrowserpersist/PWebBrowserPersistResources.ipdl
@@ -0,0 +1,26 @@
+/* -*- 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 {
+
+// == nsIWebBrowserPersistResourceVisitor
+protocol PWebBrowserPersistResources {
+ manager PWebBrowserPersistDocument;
+
+parent:
+ async VisitResource(nsCString aURI);
+
+ // 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);
+
+ // 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 000000000..ff09fd388
--- /dev/null
+++ b/dom/webbrowserpersist/PWebBrowserPersistSerialize.ipdl
@@ -0,0 +1,29 @@
+/* -*- 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.
+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 000000000..9e152cd46
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistDocumentChild.cpp
@@ -0,0 +1,159 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistDocumentChild.h"
+
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "nsIDocument.h"
+#include "nsIInputStream.h"
+#include "WebBrowserPersistLocalDocument.h"
+#include "WebBrowserPersistResourcesChild.h"
+#include "WebBrowserPersistSerializeChild.h"
+
+namespace mozilla {
+
+WebBrowserPersistDocumentChild::WebBrowserPersistDocumentChild()
+{
+}
+
+WebBrowserPersistDocumentChild::~WebBrowserPersistDocumentChild()
+{
+}
+
+void
+WebBrowserPersistDocumentChild::Start(nsIDocument* 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;
+ }
+
+ WebBrowserPersistDocumentAttrs attrs;
+ nsCOMPtr<nsIInputStream> postDataStream;
+ OptionalInputStreamParams postData;
+ nsTArray<FileDescriptor> postFiles;
+#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->GetReferrer(attrs.referrer()));
+ ENSURE(aDocument->GetContentDisposition(attrs.contentDisposition()));
+ ENSURE(aDocument->GetCacheKey(&(attrs.cacheKey())));
+ ENSURE(aDocument->GetPersistFlags(&(attrs.persistFlags())));
+ ENSURE(aDocument->GetPostData(getter_AddRefs(postDataStream)));
+ ipc::SerializeInputStream(postDataStream,
+ postData,
+ postFiles);
+#undef ENSURE
+ mDocument = aDocument;
+ SendAttributes(attrs, postData, postFiles);
+}
+
+bool
+WebBrowserPersistDocumentChild::RecvSetPersistFlags(const uint32_t& aNewFlags)
+{
+ mDocument->SetPersistFlags(aNewFlags);
+ return true;
+}
+
+PWebBrowserPersistResourcesChild*
+WebBrowserPersistDocumentChild::AllocPWebBrowserPersistResourcesChild()
+{
+ auto* actor = new WebBrowserPersistResourcesChild();
+ NS_ADDREF(actor);
+ return actor;
+}
+
+bool
+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 true;
+}
+
+bool
+WebBrowserPersistDocumentChild::DeallocPWebBrowserPersistResourcesChild(PWebBrowserPersistResourcesChild* aActor)
+{
+ auto* castActor =
+ static_cast<WebBrowserPersistResourcesChild*>(aActor);
+ NS_RELEASE(castActor);
+ return true;
+}
+
+PWebBrowserPersistSerializeChild*
+WebBrowserPersistDocumentChild::AllocPWebBrowserPersistSerializeChild(
+ const WebBrowserPersistURIMap& aMap,
+ const nsCString& aRequestedContentType,
+ const uint32_t& aEncoderFlags,
+ const uint32_t& aWrapColumn)
+{
+ auto* actor = new WebBrowserPersistSerializeChild(aMap);
+ NS_ADDREF(actor);
+ return actor;
+}
+
+bool
+WebBrowserPersistDocumentChild::RecvPWebBrowserPersistSerializeConstructor(
+ PWebBrowserPersistSerializeChild* aActor,
+ const WebBrowserPersistURIMap& aMap,
+ const nsCString& 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 true;
+}
+
+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 000000000..a72291f3c
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistDocumentChild.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistDocumentChild_h__
+#define WebBrowserPersistDocumentChild_h__
+
+#include "mozilla/PWebBrowserPersistDocumentChild.h"
+#include "nsCOMPtr.h"
+#include "nsIWebBrowserPersistDocument.h"
+
+class nsIDocument;
+
+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(nsIDocument* aDocument);
+
+ virtual bool
+ RecvSetPersistFlags(const uint32_t& aNewFlags) override;
+
+ virtual PWebBrowserPersistResourcesChild*
+ AllocPWebBrowserPersistResourcesChild() override;
+ virtual bool
+ RecvPWebBrowserPersistResourcesConstructor(PWebBrowserPersistResourcesChild* aActor) override;
+ virtual bool
+ DeallocPWebBrowserPersistResourcesChild(PWebBrowserPersistResourcesChild* aActor) override;
+
+ virtual PWebBrowserPersistSerializeChild*
+ AllocPWebBrowserPersistSerializeChild(
+ const WebBrowserPersistURIMap& aMap,
+ const nsCString& aRequestedContentType,
+ const uint32_t& aEncoderFlags,
+ const uint32_t& aWrapColumn) override;
+ virtual bool
+ RecvPWebBrowserPersistSerializeConstructor(
+ PWebBrowserPersistSerializeChild* aActor,
+ const WebBrowserPersistURIMap& aMap,
+ const nsCString& aRequestedContentType,
+ const uint32_t& aEncoderFlags,
+ const uint32_t& aWrapColumn) override;
+ virtual bool
+ DeallocPWebBrowserPersistSerializeChild(PWebBrowserPersistSerializeChild* aActor) override;
+
+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 000000000..248b05b9b
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistDocumentParent.cpp
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistDocumentParent.h"
+
+#include "mozilla/ipc/InputStreamUtils.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>(mOnReady, &nsIWebBrowserPersistDocumentReceiver::OnError,
+ NS_ERROR_FAILURE);
+ NS_DispatchToCurrentThread(errorLater);
+ mOnReady = nullptr;
+ }
+}
+
+WebBrowserPersistDocumentParent::~WebBrowserPersistDocumentParent()
+{
+ MOZ_RELEASE_ASSERT(!mReflection);
+ MOZ_ASSERT(!mOnReady);
+}
+
+bool
+WebBrowserPersistDocumentParent::RecvAttributes(const Attrs& aAttrs,
+ const OptionalInputStreamParams& aPostData,
+ nsTArray<FileDescriptor>&& aPostFiles)
+{
+ // Deserialize the postData unconditionally so that fds aren't leaked.
+ nsCOMPtr<nsIInputStream> postData =
+ ipc::DeserializeInputStream(aPostData, aPostFiles);
+ if (!mOnReady || mReflection) {
+ return false;
+ }
+ mReflection = new WebBrowserPersistRemoteDocument(this, aAttrs, postData);
+ RefPtr<WebBrowserPersistRemoteDocument> reflection = mReflection;
+ mOnReady->OnDocumentReady(reflection);
+ mOnReady = nullptr;
+ return true;
+}
+
+bool
+WebBrowserPersistDocumentParent::RecvInitFailure(const nsresult& aFailure)
+{
+ if (!mOnReady || mReflection) {
+ return false;
+ }
+ mOnReady->OnError(aFailure);
+ mOnReady = nullptr;
+ // Warning: Send__delete__ deallocates this object.
+ return Send__delete__(this);
+}
+
+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 nsCString& 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 000000000..b02b37706
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistDocumentParent.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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: TabParent::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:
+ virtual bool
+ RecvAttributes(const Attrs& aAttrs,
+ const OptionalInputStreamParams& aPostData,
+ nsTArray<FileDescriptor>&& aPostFiles) override;
+ virtual bool
+ RecvInitFailure(const nsresult& aFailure) override;
+
+ virtual PWebBrowserPersistResourcesParent*
+ AllocPWebBrowserPersistResourcesParent() override;
+ virtual bool
+ DeallocPWebBrowserPersistResourcesParent(PWebBrowserPersistResourcesParent* aActor) override;
+
+ virtual PWebBrowserPersistSerializeParent*
+ AllocPWebBrowserPersistSerializeParent(
+ const WebBrowserPersistURIMap& aMap,
+ const nsCString& aRequestedContentType,
+ const uint32_t& aEncoderFlags,
+ const uint32_t& aWrapColumn) override;
+ virtual bool
+ DeallocPWebBrowserPersistSerializeParent(PWebBrowserPersistSerializeParent* aActor) override;
+
+ 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 000000000..23513387e
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp
@@ -0,0 +1,1475 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistLocalDocument.h"
+#include "WebBrowserPersistDocumentParent.h"
+
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/HTMLSharedElement.h"
+#include "mozilla/dom/HTMLSharedObjectElement.h"
+#include "mozilla/dom/TabParent.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsContentCID.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsFrameLoader.h"
+#include "nsIComponentRegistrar.h"
+#include "nsIContent.h"
+#include "nsIDOMAttr.h"
+#include "nsIDOMComment.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMHTMLAnchorElement.h"
+#include "nsIDOMHTMLAppletElement.h"
+#include "nsIDOMHTMLAreaElement.h"
+#include "nsIDOMHTMLBaseElement.h"
+#include "nsIDOMHTMLCollection.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIDOMHTMLEmbedElement.h"
+#include "nsIDOMHTMLFrameElement.h"
+#include "nsIDOMHTMLIFrameElement.h"
+#include "nsIDOMHTMLImageElement.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsIDOMHTMLLinkElement.h"
+#include "nsIDOMHTMLMediaElement.h"
+#include "nsIDOMHTMLObjectElement.h"
+#include "nsIDOMHTMLOptionElement.h"
+#include "nsIDOMHTMLScriptElement.h"
+#include "nsIDOMHTMLSourceElement.h"
+#include "nsIDOMHTMLTextAreaElement.h"
+#include "nsIDOMMozNamedAttrMap.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMNodeFilter.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMProcessingInstruction.h"
+#include "nsIDOMTreeWalker.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.h"
+#include "nsIDocumentEncoder.h"
+#include "nsILoadContext.h"
+#include "nsIProtocolHandler.h"
+#include "nsISHEntry.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITabParent.h"
+#include "nsIWebBrowserPersist.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebPageDescriptor.h"
+#include "nsNetUtil.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(nsIDocument* aDocument)
+: mDocument(aDocument)
+, mPersistFlags(0)
+{
+ MOZ_ASSERT(mDocument);
+}
+
+WebBrowserPersistLocalDocument::~WebBrowserPersistLocalDocument()
+{
+}
+
+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::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;
+ nsresult rv;
+
+ rv = mDocument->GetContentType(utf16Type);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aContentType = NS_ConvertUTF16toUTF8(utf16Type);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetCharacterSet(nsACString& aCharSet)
+{
+ aCharSet = GetCharacterSet();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetTitle(nsAString& aTitle)
+{
+ nsAutoString titleBuffer;
+ mDocument->GetTitle(titleBuffer);
+ aTitle = titleBuffer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetReferrer(nsAString& aReferrer)
+{
+ mDocument->GetReferrer(aReferrer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetContentDisposition(nsAString& aCD)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetDefaultView();
+ if (NS_WARN_IF(!window)) {
+ aCD.SetIsVoid(true);
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window);
+ if (NS_WARN_IF(!utils)) {
+ aCD.SetIsVoid(true);
+ return NS_OK;
+ }
+ nsresult rv = utils->GetDocumentMetadata(
+ NS_LITERAL_STRING("content-disposition"), aCD);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aCD.SetIsVoid(true);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetCacheKey(uint32_t* aKey)
+{
+ nsCOMPtr<nsISHEntry> history = GetHistory();
+ if (!history) {
+ *aKey = 0;
+ return NS_OK;
+ }
+ nsCOMPtr<nsISupports> abstractKey;
+ nsresult rv = history->GetCacheKey(getter_AddRefs(abstractKey));
+ if (NS_WARN_IF(NS_FAILED(rv)) || !abstractKey) {
+ *aKey = 0;
+ return NS_OK;
+ }
+ nsCOMPtr<nsISupportsPRUint32> u32 = do_QueryInterface(abstractKey);
+ if (NS_WARN_IF(!u32)) {
+ *aKey = 0;
+ return NS_OK;
+ }
+ return u32->GetData(aKey);
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetPostData(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsISHEntry> history = GetHistory();
+ if (!history) {
+ *aStream = nullptr;
+ return NS_OK;
+ }
+ return history->GetPostData(aStream);
+}
+
+already_AddRefed<nsISHEntry>
+WebBrowserPersistLocalDocument::GetHistory()
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetDefaultView();
+ 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();
+}
+
+const nsCString&
+WebBrowserPersistLocalDocument::GetCharacterSet() const
+{
+ return mDocument->GetDocumentCharacterSet();
+}
+
+uint32_t
+WebBrowserPersistLocalDocument::GetPersistFlags() const
+{
+ return mPersistFlags;
+}
+
+
+already_AddRefed<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(nsIDOMNode* 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);
+ nsresult OnWalkURI(nsIURI* aURI);
+ nsresult OnWalkAttribute(nsIDOMNode* aNode,
+ const char* aAttribute,
+ const char* aNamespaceURI = "");
+ nsresult OnWalkSubframe(nsIDOMNode* aNode);
+
+ bool IsFlagSet(uint32_t aFlag) const {
+ return mParent->GetPersistFlags() & aFlag;
+ }
+
+ ~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(nsIDOMNode* aNode)
+{
+ nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(aNode);
+ NS_ENSURE_STATE(loaderOwner);
+ RefPtr<nsFrameLoader> loader = loaderOwner->GetFrameLoader();
+ NS_ENSURE_STATE(loader);
+
+ ++mOutstandingDocuments;
+ // Pass in 0 as the outer window ID so that we start
+ // persisting the root of this subframe, and not some other
+ // subframe child of this subframe.
+ nsresult rv = loader->StartPersistence(0, this);
+ 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)
+{
+ // 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);
+}
+
+nsresult
+ResourceReader::OnWalkURI(const nsACString& aURISpec)
+{
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+
+ rv = NS_NewURI(getter_AddRefs(uri),
+ aURISpec,
+ mParent->GetCharacterSet().get(),
+ mCurrentBaseURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return OnWalkURI(uri);
+}
+
+static nsresult
+ExtractAttribute(nsIDOMNode* aNode,
+ const char* aAttribute,
+ const char* aNamespaceURI,
+ nsCString& aValue)
+{
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode);
+ MOZ_ASSERT(element);
+
+ // Find the named URI attribute on the (element) node and store
+ // a reference to the URI that maps onto a local file name
+
+ nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap;
+ nsresult rv = element->GetAttributes(getter_AddRefs(attrMap));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI);
+ NS_ConvertASCIItoUTF16 attribute(aAttribute);
+ nsCOMPtr<nsIDOMAttr> attr;
+ rv = attrMap->GetNamedItemNS(namespaceURI, attribute, getter_AddRefs(attr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (attr) {
+ nsAutoString value;
+ rv = attr->GetValue(value);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aValue = NS_ConvertUTF16toUTF8(value);
+ } else {
+ aValue.Truncate();
+ }
+ return NS_OK;
+}
+
+nsresult
+ResourceReader::OnWalkAttribute(nsIDOMNode* aNode,
+ const char* aAttribute,
+ const char* aNamespaceURI)
+{
+ nsAutoCString uriSpec;
+ nsresult rv = ExtractAttribute(aNode, aAttribute, aNamespaceURI, uriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (uriSpec.IsEmpty()) {
+ return NS_OK;
+ }
+ return OnWalkURI(uriSpec);
+}
+
+static nsresult
+GetXMLStyleSheetLink(nsIDOMProcessingInstruction *aPI, nsAString &aHref)
+{
+ nsresult rv;
+ nsAutoString data;
+ rv = aPI->GetData(data);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::href, aHref);
+ return NS_OK;
+}
+
+nsresult
+ResourceReader::OnWalkDOMNode(nsIDOMNode* aNode)
+{
+ nsresult rv;
+
+ // Fixup xml-stylesheet processing instructions
+ nsCOMPtr<nsIDOMProcessingInstruction> nodeAsPI = do_QueryInterface(aNode);
+ if (nodeAsPI) {
+ nsAutoString target;
+ rv = nodeAsPI->GetTarget(target);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (target.EqualsLiteral("xml-stylesheet")) {
+ nsAutoString href;
+ GetXMLStyleSheetLink(nodeAsPI, href);
+ if (!href.IsEmpty()) {
+ return OnWalkURI(NS_ConvertUTF16toUTF8(href));
+ }
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
+ if (!content) {
+ return NS_OK;
+ }
+
+ // Test the node to see if it's an image, frame, iframe, css, js
+ nsCOMPtr<nsIDOMHTMLImageElement> nodeAsImage = do_QueryInterface(aNode);
+ if (nodeAsImage) {
+ return OnWalkAttribute(aNode, "src");
+ }
+
+ if (content->IsSVGElement(nsGkAtoms::img)) {
+ return OnWalkAttribute(aNode, "href", "http://www.w3.org/1999/xlink");
+ }
+
+ nsCOMPtr<nsIDOMHTMLMediaElement> nodeAsMedia = do_QueryInterface(aNode);
+ if (nodeAsMedia) {
+ return OnWalkAttribute(aNode, "src");
+ }
+ nsCOMPtr<nsIDOMHTMLSourceElement> nodeAsSource = do_QueryInterface(aNode);
+ if (nodeAsSource) {
+ return OnWalkAttribute(aNode, "src");
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::body)) {
+ return OnWalkAttribute(aNode, "background");
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::table)) {
+ return OnWalkAttribute(aNode, "background");
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::tr)) {
+ return OnWalkAttribute(aNode, "background");
+ }
+
+ if (content->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) {
+ return OnWalkAttribute(aNode, "background");
+ }
+
+ nsCOMPtr<nsIDOMHTMLScriptElement> nodeAsScript = do_QueryInterface(aNode);
+ if (nodeAsScript) {
+ return OnWalkAttribute(aNode, "src");
+ }
+
+ if (content->IsSVGElement(nsGkAtoms::script)) {
+ return OnWalkAttribute(aNode, "href", "http://www.w3.org/1999/xlink");
+ }
+
+ nsCOMPtr<nsIDOMHTMLEmbedElement> nodeAsEmbed = do_QueryInterface(aNode);
+ if (nodeAsEmbed) {
+ return OnWalkAttribute(aNode, "src");
+ }
+
+ nsCOMPtr<nsIDOMHTMLObjectElement> nodeAsObject = do_QueryInterface(aNode);
+ if (nodeAsObject) {
+ return OnWalkAttribute(aNode, "data");
+ }
+
+ nsCOMPtr<nsIDOMHTMLAppletElement> nodeAsApplet = do_QueryInterface(aNode);
+ if (nodeAsApplet) {
+ // For an applet, relative URIs are resolved relative to the
+ // codebase (which is resolved relative to the base URI).
+ nsCOMPtr<nsIURI> oldBase = mCurrentBaseURI;
+ nsAutoString codebase;
+ rv = nodeAsApplet->GetCodeBase(codebase);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!codebase.IsEmpty()) {
+ nsCOMPtr<nsIURI> baseURI;
+ rv = NS_NewURI(getter_AddRefs(baseURI), codebase,
+ mParent->GetCharacterSet().get(), mCurrentBaseURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (baseURI) {
+ mCurrentBaseURI = baseURI;
+ // Must restore this before returning (or ENSURE'ing).
+ }
+ }
+
+ // We only store 'code' locally if there is no 'archive',
+ // otherwise we assume the archive file(s) contains it (bug 430283).
+ nsAutoCString archiveAttr;
+ rv = ExtractAttribute(aNode, "archive", "", archiveAttr);
+ if (NS_SUCCEEDED(rv)) {
+ if (!archiveAttr.IsEmpty()) {
+ rv = OnWalkURI(archiveAttr);
+ } else {
+ rv = OnWalkAttribute(aNode, "core");
+ }
+ }
+
+ // restore the base URI we really want to have
+ mCurrentBaseURI = oldBase;
+ return rv;
+ }
+
+ nsCOMPtr<nsIDOMHTMLLinkElement> nodeAsLink = do_QueryInterface(aNode);
+ if (nodeAsLink) {
+ // Test if the link has a rel value indicating it to be a stylesheet
+ nsAutoString linkRel;
+ if (NS_SUCCEEDED(nodeAsLink->GetRel(linkRel)) && !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, "href");
+ return NS_OK;
+ }
+ if (current == end) {
+ break;
+ }
+ }
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMHTMLFrameElement> nodeAsFrame = do_QueryInterface(aNode);
+ if (nodeAsFrame) {
+ return OnWalkSubframe(aNode);
+ }
+
+ nsCOMPtr<nsIDOMHTMLIFrameElement> nodeAsIFrame = do_QueryInterface(aNode);
+ if (nodeAsIFrame && !(mPersistFlags &
+ IWBP::PERSIST_FLAGS_IGNORE_IFRAMES)) {
+ return OnWalkSubframe(aNode);
+ }
+
+ nsCOMPtr<nsIDOMHTMLInputElement> nodeAsInput = do_QueryInterface(aNode);
+ if (nodeAsInput) {
+ return OnWalkAttribute(aNode, "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() { }
+ 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(nsIDOMNode* aNodeIn, nsIDOMNode** aNodeOut);
+ nsresult FixupURI(nsAString& aURI);
+ nsresult FixupAttribute(nsIDOMNode* aNode,
+ const char* aAttribute,
+ const char* aNamespaceURI = "");
+ nsresult FixupAnchor(nsIDOMNode* aNode);
+ nsresult FixupXMLStyleSheetLink(nsIDOMProcessingInstruction* aPI,
+ const nsAString& aHref);
+
+ 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;
+ nsCString* urlTo = new nsCString();
+
+ rv = aMap->GetURIMapping(i, urlFrom, *urlTo);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_SUCCEEDED(rv)) {
+ mMap.Put(urlFrom, urlTo);
+ }
+ }
+ }
+}
+
+nsresult
+PersistNodeFixup::GetNodeToFixup(nsIDOMNode *aNodeIn, nsIDOMNode **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)) {
+ nsresult rv = aNodeIn->CloneNode(false, 1, aNodeOut);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NS_ADDREF(*aNodeOut = aNodeIn);
+ }
+ nsCOMPtr<nsIDOMHTMLElement> element(do_QueryInterface(*aNodeOut));
+ if (element) {
+ // Make sure this is not XHTML
+ nsAutoString namespaceURI;
+ element->GetNamespaceURI(namespaceURI);
+ if (namespaceURI.IsEmpty()) {
+ // This is a tag-soup node. It may have a _base_href attribute
+ // stuck on it by the parser, but since we're fixing up all URIs
+ // relative to the overall document base that will screw us up.
+ // Just remove the _base_href.
+ element->RemoveAttribute(NS_LITERAL_STRING("_base_href"));
+ }
+ }
+ 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().get(), 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()) {
+ aURI = NS_ConvertUTF8toUTF16(*replacement);
+ }
+ return NS_OK;
+}
+
+nsresult
+PersistNodeFixup::FixupAttribute(nsIDOMNode* aNode,
+ const char* aAttribute,
+ const char* aNamespaceURI)
+{
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode);
+ MOZ_ASSERT(element);
+
+ nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap;
+ nsresult rv = element->GetAttributes(getter_AddRefs(attrMap));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ NS_ConvertASCIItoUTF16 attribute(aAttribute);
+ NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI);
+ nsCOMPtr<nsIDOMAttr> attr;
+ rv = attrMap->GetNamedItemNS(namespaceURI, attribute, getter_AddRefs(attr));
+ if (attr) {
+ nsString uri;
+ attr->GetValue(uri);
+ rv = FixupURI(uri);
+ if (NS_SUCCEEDED(rv)) {
+ attr->SetValue(uri);
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+PersistNodeFixup::FixupAnchor(nsIDOMNode *aNode)
+{
+ if (IsFlagSet(IWBP::PERSIST_FLAGS_DONT_FIXUP_LINKS)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode);
+ MOZ_ASSERT(element);
+
+ nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap;
+ nsresult rv = element->GetAttributes(getter_AddRefs(attrMap));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ // Make all anchor links absolute so they point off onto the Internet
+ nsString attribute(NS_LITERAL_STRING("href"));
+ nsCOMPtr<nsIDOMAttr> attr;
+ rv = attrMap->GetNamedItem(attribute, getter_AddRefs(attr));
+ 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;
+ rv = NS_NewURI(getter_AddRefs(newURI), oldCValue,
+ mParent->GetCharacterSet().get(), relativeURI);
+ if (NS_SUCCEEDED(rv) && newURI) {
+ newURI->SetUserPass(EmptyCString());
+ nsAutoCString uriSpec;
+ rv = newURI->GetSpec(uriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ attr->SetValue(NS_ConvertUTF8toUTF16(uriSpec));
+ }
+ }
+
+ return NS_OK;
+}
+
+static void
+AppendXMLAttr(const nsAString& key, const nsAString& aValue, nsAString& aBuffer)
+{
+ if (!aBuffer.IsEmpty()) {
+ aBuffer.Append(' ');
+ }
+ aBuffer.Append(key);
+ aBuffer.AppendLiteral("=\"");
+ 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(nsIDOMProcessingInstruction* aPI,
+ const nsAString& aHref)
+{
+ NS_ENSURE_ARG_POINTER(aPI);
+ nsresult rv = NS_OK;
+
+ nsAutoString data;
+ rv = aPI->GetData(data);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ 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(NS_LITERAL_STRING("href"), aHref, newData);
+ if (!title.IsEmpty()) {
+ AppendXMLAttr(NS_LITERAL_STRING("title"), title, newData);
+ }
+ if (!media.IsEmpty()) {
+ AppendXMLAttr(NS_LITERAL_STRING("media"), media, newData);
+ }
+ if (!type.IsEmpty()) {
+ AppendXMLAttr(NS_LITERAL_STRING("type"), type, newData);
+ }
+ if (!charset.IsEmpty()) {
+ AppendXMLAttr(NS_LITERAL_STRING("charset"), charset, newData);
+ }
+ if (!alternate.IsEmpty()) {
+ AppendXMLAttr(NS_LITERAL_STRING("alternate"), alternate, newData);
+ }
+ aPI->SetData(newData);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+PersistNodeFixup::FixupNode(nsIDOMNode *aNodeIn,
+ bool *aSerializeCloneKids,
+ nsIDOMNode **aNodeOut)
+{
+ *aNodeOut = nullptr;
+ *aSerializeCloneKids = false;
+
+ uint16_t type;
+ nsresult rv = aNodeIn->GetNodeType(&type);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (type != nsIDOMNode::ELEMENT_NODE &&
+ type != nsIDOMNode::PROCESSING_INSTRUCTION_NODE) {
+ return NS_OK;
+ }
+
+ // Fixup xml-stylesheet processing instructions
+ nsCOMPtr<nsIDOMProcessingInstruction> nodeAsPI = do_QueryInterface(aNodeIn);
+ if (nodeAsPI) {
+ nsAutoString target;
+ nodeAsPI->GetTarget(target);
+ if (target.EqualsLiteral("xml-stylesheet"))
+ {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ nsCOMPtr<nsIDOMProcessingInstruction> outNode =
+ do_QueryInterface(*aNodeOut);
+ nsAutoString href;
+ GetXMLStyleSheetLink(nodeAsPI, href);
+ if (!href.IsEmpty()) {
+ FixupURI(href);
+ FixupXMLStyleSheetLink(outNode, href);
+ }
+ }
+ }
+ 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)) {
+ nsCOMPtr<nsIDOMHTMLBaseElement> nodeAsBase = do_QueryInterface(aNodeIn);
+ if (nodeAsBase) {
+ nsCOMPtr<nsIDOMDocument> ownerDocument;
+ auto* base = static_cast<dom::HTMLSharedElement*>(nodeAsBase.get());
+ base->GetOwnerDocument(getter_AddRefs(ownerDocument));
+ if (ownerDocument) {
+ nsAutoString href;
+ base->GetHref(href); // Doesn't matter if this fails
+ nsCOMPtr<nsIDOMComment> comment;
+ nsAutoString commentText;
+ commentText.AssignLiteral(" base ");
+ if (!href.IsEmpty()) {
+ commentText += NS_LITERAL_STRING("href=\"") + href
+ + NS_LITERAL_STRING("\" ");
+ }
+ rv = ownerDocument->CreateComment(commentText,
+ getter_AddRefs(comment));
+ if (comment) {
+ return CallQueryInterface(comment, aNodeOut);
+ }
+ }
+ return NS_OK;
+ }
+ }
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aNodeIn);
+ if (!content) {
+ return NS_OK;
+ }
+
+ // Fix up href and file links in the elements
+ nsCOMPtr<nsIDOMHTMLAnchorElement> nodeAsAnchor = do_QueryInterface(aNodeIn);
+ if (nodeAsAnchor) {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAnchor(*aNodeOut);
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIDOMHTMLAreaElement> nodeAsArea = do_QueryInterface(aNodeIn);
+ if (nodeAsArea) {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAnchor(*aNodeOut);
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::body)) {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "background");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::table)) {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "background");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::tr)) {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "background");
+ }
+ return rv;
+ }
+
+ if (content->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "background");
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIDOMHTMLImageElement> nodeAsImage = do_QueryInterface(aNodeIn);
+ if (nodeAsImage) {
+ 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);
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIDOMHTMLMediaElement> nodeAsMedia = do_QueryInterface(aNodeIn);
+ if (nodeAsMedia) {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIDOMHTMLSourceElement> nodeAsSource = do_QueryInterface(aNodeIn);
+ if (nodeAsSource) {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ if (content->IsSVGElement(nsGkAtoms::img)) {
+ 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", "http://www.w3.org/1999/xlink");
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIDOMHTMLScriptElement> nodeAsScript = do_QueryInterface(aNodeIn);
+ if (nodeAsScript) {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ if (content->IsSVGElement(nsGkAtoms::script)) {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink");
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIDOMHTMLEmbedElement> nodeAsEmbed = do_QueryInterface(aNodeIn);
+ if (nodeAsEmbed) {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIDOMHTMLObjectElement> nodeAsObject = do_QueryInterface(aNodeIn);
+ if (nodeAsObject) {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "data");
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIDOMHTMLAppletElement> nodeAsApplet = do_QueryInterface(aNodeIn);
+ if (nodeAsApplet) {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ nsCOMPtr<nsIDOMHTMLAppletElement> newApplet =
+ do_QueryInterface(*aNodeOut);
+ // For an applet, relative URIs are resolved relative to the
+ // codebase (which is resolved relative to the base URI).
+ nsCOMPtr<nsIURI> oldBase = mCurrentBaseURI;
+ nsAutoString codebase;
+ nodeAsApplet->GetCodeBase(codebase);
+ if (!codebase.IsEmpty()) {
+ nsCOMPtr<nsIURI> baseURI;
+ NS_NewURI(getter_AddRefs(baseURI), codebase,
+ mParent->GetCharacterSet().get(), mCurrentBaseURI);
+ if (baseURI) {
+ mCurrentBaseURI = baseURI;
+ }
+ }
+ // Unset the codebase too, since we'll correctly relativize the
+ // code and archive paths.
+ static_cast<dom::HTMLSharedObjectElement*>(newApplet.get())->
+ RemoveAttribute(NS_LITERAL_STRING("codebase"));
+ FixupAttribute(*aNodeOut, "code");
+ FixupAttribute(*aNodeOut, "archive");
+ // restore the base URI we really want to have
+ mCurrentBaseURI = oldBase;
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIDOMHTMLLinkElement> nodeAsLink = do_QueryInterface(aNodeIn);
+ if (nodeAsLink) {
+ 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;
+ }
+
+ nsCOMPtr<nsIDOMHTMLFrameElement> nodeAsFrame = do_QueryInterface(aNodeIn);
+ if (nodeAsFrame) {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIDOMHTMLIFrameElement> nodeAsIFrame = do_QueryInterface(aNodeIn);
+ if (nodeAsIFrame) {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIDOMHTMLInputElement> nodeAsInput = do_QueryInterface(aNodeIn);
+ if (nodeAsInput) {
+ 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;
+ NS_NAMED_LITERAL_STRING(valueAttr, "value");
+ // Update element node attributes with user-entered form state
+ nsCOMPtr<nsIContent> content = do_QueryInterface(*aNodeOut);
+ RefPtr<dom::HTMLInputElement> outElt =
+ dom::HTMLInputElement::FromContentOrNull(content);
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(*aNodeOut);
+ switch (formControl->GetType()) {
+ case NS_FORM_INPUT_EMAIL:
+ case NS_FORM_INPUT_SEARCH:
+ case NS_FORM_INPUT_TEXT:
+ case NS_FORM_INPUT_TEL:
+ case NS_FORM_INPUT_URL:
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_RANGE:
+ case NS_FORM_INPUT_DATE:
+ case NS_FORM_INPUT_TIME:
+ case NS_FORM_INPUT_COLOR:
+ nodeAsInput->GetValue(valueStr);
+ // Avoid superfluous value="" serialization
+ if (valueStr.IsEmpty())
+ outElt->RemoveAttribute(valueAttr);
+ else
+ outElt->SetAttribute(valueAttr, valueStr);
+ break;
+ case NS_FORM_INPUT_CHECKBOX:
+ case NS_FORM_INPUT_RADIO:
+ bool checked;
+ nodeAsInput->GetChecked(&checked);
+ outElt->SetDefaultChecked(checked);
+ break;
+ default:
+ break;
+ }
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIDOMHTMLTextAreaElement> nodeAsTextArea = do_QueryInterface(aNodeIn);
+ if (nodeAsTextArea) {
+ 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);
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIDOMHTMLOptionElement> nodeAsOption = do_QueryInterface(aNodeIn);
+ if (nodeAsOption) {
+ rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ nsCOMPtr<nsIDOMHTMLOptionElement> outElt = do_QueryInterface(*aNodeOut);
+ bool selected;
+ nodeAsOption->GetSelected(&selected);
+ outElt->SetDefaultSelected(selected);
+ }
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+} // unnamed namespace
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::ReadResources(nsIWebBrowserPersistResourceVisitor* aVisitor)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIWebBrowserPersistResourceVisitor> visitor = aVisitor;
+
+ nsCOMPtr<nsIDOMNode> docAsNode = do_QueryInterface(mDocument);
+ NS_ENSURE_TRUE(docAsNode, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDOMTreeWalker> walker;
+ nsCOMPtr<nsIDOMDocument> oldStyleDoc = do_QueryInterface(mDocument);
+ MOZ_ASSERT(oldStyleDoc);
+ rv = oldStyleDoc->CreateTreeWalker(docAsNode,
+ nsIDOMNodeFilter::SHOW_ELEMENT |
+ nsIDOMNodeFilter::SHOW_DOCUMENT |
+ nsIDOMNodeFilter::SHOW_PROCESSING_INSTRUCTION,
+ nullptr, 1, getter_AddRefs(walker));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ MOZ_ASSERT(walker);
+
+ RefPtr<ResourceReader> reader = new ResourceReader(this, aVisitor);
+ nsCOMPtr<nsIDOMNode> currentNode;
+ walker->GetCurrentNode(getter_AddRefs(currentNode));
+ while (currentNode) {
+ rv = reader->OnWalkDOMNode(currentNode);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+ rv = walker->NextNode(getter_AddRefs(currentNode));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+ }
+ 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_ENCODE_LATIN1_ENTITIES)
+ encoderFlags |= nsIDocumentEncoder::OutputEncodeLatin1Entities;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_HTML_ENTITIES)
+ encoderFlags |= nsIDocumentEncoder::OutputEncodeHTMLEntities;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_W3C_ENTITIES)
+ encoderFlags |= nsIDocumentEncoder::OutputEncodeW3CEntities;
+ 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)
+{
+ nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE);
+ contractID.Append(aType);
+
+ nsCOMPtr<nsIComponentRegistrar> registrar;
+ nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(registrar));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_SUCCEEDED(rv) && registrar) {
+ bool result;
+ rv = registrar->IsContractIDRegistered(contractID.get(), &result);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return NS_SUCCEEDED(rv) && result;
+ }
+ return false;
+}
+
+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)
+{
+ nsresult rv;
+ nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE);
+ contractID.Append(aContentType);
+ nsCOMPtr<nsIDocumentEncoder> encoder =
+ do_CreateInstance(contractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ 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,
+ /* charset: */ nullptr, /* base: */ nullptr);
+ 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 000000000..9110d6c35
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistLocalDocument.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistLocalDocument_h__
+#define WebBrowserPersistLocalDocument_h__
+
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIDocument.h"
+#include "nsIURI.h"
+#include "nsIWebBrowserPersistDocument.h"
+
+class nsIDocumentEncoder;
+class nsISHEntry;
+
+namespace mozilla {
+
+class WebBrowserPersistLocalDocument final
+ : public nsIWebBrowserPersistDocument
+{
+public:
+ explicit WebBrowserPersistLocalDocument(nsIDocument* aDocument);
+
+ const nsCString& GetCharacterSet() const;
+ uint32_t GetPersistFlags() const;
+ already_AddRefed<nsIURI> GetBaseURI() const;
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIWEBBROWSERPERSISTDOCUMENT
+
+ NS_DECL_CYCLE_COLLECTION_CLASS(WebBrowserPersistLocalDocument)
+
+private:
+ nsCOMPtr<nsIDocument> mDocument;
+ uint32_t mPersistFlags;
+
+ void DecideContentType(nsACString& aContentType);
+ nsresult GetDocEncoder(const nsACString& aContentType,
+ uint32_t aEncoderFlags,
+ nsIDocumentEncoder** aEncoder);
+ already_AddRefed<nsISHEntry> GetHistory();
+
+ virtual ~WebBrowserPersistLocalDocument();
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistLocalDocument_h__
diff --git a/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp b/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp
new file mode 100644
index 000000000..c7dcca861
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistRemoteDocument.h"
+#include "WebBrowserPersistDocumentParent.h"
+#include "WebBrowserPersistResourcesParent.h"
+#include "WebBrowserPersistSerializeParent.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(WebBrowserPersistRemoteDocument,
+ nsIWebBrowserPersistDocument)
+
+WebBrowserPersistRemoteDocument
+::WebBrowserPersistRemoteDocument(WebBrowserPersistDocumentParent* aActor,
+ const Attrs& aAttrs,
+ nsIInputStream* aPostData)
+: mActor(aActor)
+, mAttrs(aAttrs)
+, mPostData(aPostData)
+{
+}
+
+WebBrowserPersistRemoteDocument::~WebBrowserPersistRemoteDocument()
+{
+ if (mActor) {
+ Unused << mActor->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::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::GetReferrer(nsAString& aReferrer)
+{
+ aReferrer = mAttrs.referrer();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetContentDisposition(nsAString& aDisp)
+{
+ aDisp = mAttrs.contentDisposition();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetCacheKey(uint32_t* aCacheKey)
+{
+ *aCacheKey = mAttrs.cacheKey();
+ 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::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;
+}
+
+} // namespace mozilla
diff --git a/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.h b/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.h
new file mode 100644
index 000000000..08d435903
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistRemoteDocument_h__
+#define WebBrowserPersistRemoteDocument_h__
+
+#include "mozilla/Maybe.h"
+#include "mozilla/PWebBrowserPersistDocumentParent.h"
+#include "nsCOMPtr.h"
+#include "nsIWebBrowserPersistDocument.h"
+#include "nsIInputStream.h"
+
+// 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<nsIInputStream> mPostData;
+
+ 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 000000000..c28f034c8
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistResourcesChild.cpp
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistResourcesChild.h"
+
+#include "WebBrowserPersistDocumentChild.h"
+#include "mozilla/dom/ContentChild.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(WebBrowserPersistResourcesChild,
+ nsIWebBrowserPersistResourceVisitor)
+
+WebBrowserPersistResourcesChild::WebBrowserPersistResourcesChild()
+{
+}
+
+WebBrowserPersistResourcesChild::~WebBrowserPersistResourcesChild()
+{
+}
+
+NS_IMETHODIMP
+WebBrowserPersistResourcesChild::VisitResource(nsIWebBrowserPersistDocument *aDocument,
+ const nsACString& aURI)
+{
+ nsCString copiedURI(aURI); // Yay, XPIDL/IPDL mismatch.
+ SendVisitResource(copiedURI);
+ 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 TabChild in which
+ // persistence started does not necessarily exist at this point;
+ // see bug 1203602.
+ if (!Manager()->Manager()
+ ->SendPWebBrowserPersistDocumentConstructor(subActor, nullptr, 0)) {
+ // 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(subActor);
+ subActor->Start(aSubDocument);
+ 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 000000000..d6ab10d02
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistResourcesChild.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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 000000000..195cabb58
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistResourcesParent.cpp
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistResourcesParent.h"
+
+#include "nsThreadUtils.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()
+{
+}
+
+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>
+ (mVisitor, &nsIWebBrowserPersistResourceVisitor::EndVisit,
+ mDocument, NS_ERROR_FAILURE);
+ NS_DispatchToCurrentThread(errorLater);
+ }
+ mVisitor = nullptr;
+}
+
+bool
+WebBrowserPersistResourcesParent::Recv__delete__(const nsresult& aStatus)
+{
+ mVisitor->EndVisit(mDocument, aStatus);
+ mVisitor = nullptr;
+ return true;
+}
+
+bool
+WebBrowserPersistResourcesParent::RecvVisitResource(const nsCString& aURI)
+{
+ mVisitor->VisitResource(mDocument, aURI);
+ return true;
+}
+
+bool
+WebBrowserPersistResourcesParent::RecvVisitDocument(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)
+ ->SetOnReady(this);
+ return true;
+}
+
+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 000000000..9d70fd11b
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistResourcesParent.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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 bool
+ RecvVisitResource(const nsCString& aURI) override;
+
+ virtual bool
+ RecvVisitDocument(PWebBrowserPersistDocumentParent* aSubDocument) override;
+
+ virtual bool
+ 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 000000000..2b6fcbde4
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistSerializeChild.cpp
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistSerializeChild.h"
+
+#include <algorithm>
+
+#include "nsThreadUtils.h"
+#include "ipc/IPCMessageUtils.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(WebBrowserPersistSerializeChild,
+ nsIWebBrowserPersistWriteCompletion,
+ nsIWebBrowserPersistURIMap,
+ nsIOutputStream)
+
+WebBrowserPersistSerializeChild::WebBrowserPersistSerializeChild(const WebBrowserPersistURIMap& aMap)
+: mMap(aMap)
+{
+}
+
+WebBrowserPersistSerializeChild::~WebBrowserPersistSerializeChild()
+{
+}
+
+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::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.");
+
+ // 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(IPC::MAX_MESSAGE_SIZE, count);
+ nsTArray<uint8_t> arrayBuf;
+ // It would be nice if this extra copy could be avoided.
+ arrayBuf.AppendElements(buf, toWrite);
+ SendWriteData(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 000000000..ddca44ece
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistSerializeChild.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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 000000000..2f76ffb33
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistSerializeParent.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "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()
+{
+}
+
+bool
+WebBrowserPersistSerializeParent::RecvWriteData(nsTArray<uint8_t>&& aData)
+{
+ if (NS_FAILED(mOutputError)) {
+ return true;
+ }
+
+ 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 true;
+ }
+ written += writeReturn;
+ }
+ return true;
+}
+
+bool
+WebBrowserPersistSerializeParent::Recv__delete__(const nsCString& aContentType,
+ const nsresult& aStatus)
+{
+ if (NS_SUCCEEDED(mOutputError)) {
+ mOutputError = aStatus;
+ }
+ mFinish->OnFinish(mDocument,
+ mStream,
+ aContentType,
+ mOutputError);
+ mFinish = nullptr;
+ return true;
+}
+
+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>
+ (mFinish, &nsIWebBrowserPersistWriteCompletion::OnFinish,
+ mDocument, mStream, EmptyCString(), 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 000000000..452feef4b
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistSerializeParent.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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 bool
+ RecvWriteData(nsTArray<uint8_t>&& aData) override;
+
+ virtual bool
+ Recv__delete__(const nsCString& 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 000000000..6ef2eac00
--- /dev/null
+++ b/dom/webbrowserpersist/moz.build
@@ -0,0 +1,48 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsCWebBrowserPersist.idl',
+ 'nsIWebBrowserPersist.idl',
+ 'nsIWebBrowserPersistable.idl',
+ 'nsIWebBrowserPersistDocument.idl',
+]
+
+XPIDL_MODULE = 'webbrowserpersist'
+
+IPDL_SOURCES += [
+ 'PWebBrowserPersistDocument.ipdl',
+ 'PWebBrowserPersistResources.ipdl',
+ 'PWebBrowserPersistSerialize.ipdl',
+]
+
+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 += [
+ '/dom/base',
+ '/dom/html',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/dom/webbrowserpersist/nsCWebBrowserPersist.idl b/dom/webbrowserpersist/nsCWebBrowserPersist.idl
new file mode 100644
index 000000000..69f2db7ca
--- /dev/null
+++ b/dom/webbrowserpersist/nsCWebBrowserPersist.idl
@@ -0,0 +1,15 @@
+/* -*- 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 "nsIWebBrowserPersist.idl"
+
+%{ 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/nsIWebBrowserPersist.idl b/dom/webbrowserpersist/nsIWebBrowserPersist.idl
new file mode 100644
index 000000000..cd4bd8b69
--- /dev/null
+++ b/dom/webbrowserpersist/nsIWebBrowserPersist.idl
@@ -0,0 +1,286 @@
+/* -*- 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"
+
+interface nsIURI;
+interface nsIInputStream;
+interface nsIDOMDocument;
+interface nsIWebProgressListener;
+interface nsIFile;
+interface nsIChannel;
+interface nsILoadContext;
+
+/**
+ * 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;
+
+ /**
+ * Force relevant cookies to be sent with this load even if normally they
+ * wouldn't be.
+ */
+ const unsigned long PERSIST_FLAGS_FORCE_ALLOW_COOKIES = 65536;
+
+ /**
+ * 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 aCacheKey An object representing the URI in the cache or
+ * <CODE>nullptr</CODE>. This can be a necko cache key,
+ * an nsIWebPageDescriptor, or the currentDescriptor of an
+ * nsIWebPageDescriptor.
+ * @param aReferrer The referrer URI to pass with an HTTP request or
+ * <CODE>nullptr</CODE>.
+ * @param aReferrerPolicy The referrer policy for when and what to send via
+ * HTTP Referer header. Ignored if aReferrer is
+ * <CODE>nullptr</CODE>. Taken from REFERRER_POLICY
+ * constants in nsIHttpChannel.
+ * @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 aPrivacyContext A context from which the privacy status of this
+ * save operation can be determined. Must only be null
+ * in situations in which no such context is available
+ * (eg. the operation has no logical association with any
+ * window or document)
+ *
+ * @see nsIFile
+ * @see nsIURI
+ * @see nsIInputStream
+ *
+ * @throws NS_ERROR_INVALID_ARG One or more arguments was invalid.
+ */
+ void saveURI(in nsIURI aURI, in nsISupports aCacheKey,
+ in nsIURI aReferrer, in unsigned long aReferrerPolicy,
+ in nsIInputStream aPostData,
+ in string aExtraHeaders, in nsISupports aFile,
+ in nsILoadContext aPrivacyContext);
+
+ /**
+ * @param aIsPrivate Treat the save operation as private (ie. with
+ * regards to networking operations and persistence
+ * of intermediate data, etc.)
+ * @see saveURI for all other parameter descriptions
+ */
+ void savePrivacyAwareURI(in nsIURI aURI, in nsISupports aCacheKey,
+ in nsIURI aReferrer, in unsigned long aReferrerPolicy,
+ in nsIInputStream aPostData,
+ in string aExtraHeaders, in nsISupports aFile,
+ 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;
+
+ /**
+ * Attempt to encode entities standardized at W3C (HTML, MathML, etc).
+ * This is a catch-all flag for documents with mixed contents. Beware of
+ * interoperability issues. See below for other flags which might likely
+ * do what you want.
+ */
+ const unsigned long ENCODE_FLAGS_ENCODE_W3C_ENTITIES = 256;
+
+ /**
+ * 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;
+ /**
+ * Encode Latin1 entities. This includes the basic set and
+ * accented letters between 128 and 255.
+ */
+ const unsigned long ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES = 16384;
+ /**
+ * Encode HTML4 entities. This includes the basic set, accented
+ * letters, greek letters and certain special markup symbols.
+ */
+ const unsigned long ENCODE_FLAGS_ENCODE_HTML_ENTITIES = 32768;
+
+ /**
+ * 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 nsIDOMDocument.
+ * @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 nsIWebBrowserPersistable
+ * @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();
+};
diff --git a/dom/webbrowserpersist/nsIWebBrowserPersistDocument.idl b/dom/webbrowserpersist/nsIWebBrowserPersistDocument.idl
new file mode 100644
index 000000000..ba5bea8b2
--- /dev/null
+++ b/dom/webbrowserpersist/nsIWebBrowserPersistDocument.idl
@@ -0,0 +1,197 @@
+/* -*- 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"
+
+interface nsIDOMDocument;
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsITabParent;
+interface nsIWebBrowserPersistResourceVisitor;
+interface nsIWebBrowserPersistWriteCompletion;
+
+/**
+ * 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, uuid(74aa4918-5d15-46b6-9ccf-74f9696d721d)]
+interface nsIWebBrowserPersistDocument : nsISupports
+{
+ 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 AString referrer;
+ readonly attribute AString contentDisposition;
+ readonly attribute nsIInputStream postData;
+
+ /**
+ * The cache key. Unlike in nsISHEntry, where it's wrapped in an
+ * nsISupportsPRUint32, this is just the integer.
+ */
+ 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);
+};
+
+/**
+ * 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.
+ */
+ void visitResource(in nsIWebBrowserPersistDocument aDocument,
+ in AUTF8String aURI);
+ /**
+ * 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 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.
+ *
+ * @see nsIWebBrowserPersistable.
+ */
+[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/nsIWebBrowserPersistable.idl b/dom/webbrowserpersist/nsIWebBrowserPersistable.idl
new file mode 100644
index 000000000..39ea6b33b
--- /dev/null
+++ b/dom/webbrowserpersist/nsIWebBrowserPersistable.idl
@@ -0,0 +1,41 @@
+/* -*- 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"
+
+interface nsIWebBrowserPersistDocumentReceiver;
+
+/**
+ * Interface for objects which represent a document that can be
+ * serialized with nsIWebBrowserPersist. This interface is
+ * asynchronous because the actual document can be in another process
+ * (e.g., if this object is an nsFrameLoader for an out-of-process
+ * frame).
+ *
+ * Warning: this is currently implemented only by nsFrameLoader, and
+ * may change in the future to become more frame-loader-specific or be
+ * merged into nsIFrameLoader. See bug 1101100 comment #34.
+ *
+ * @see nsIWebBrowserPersistDocumentReceiver
+ * @see nsIWebBrowserPersistDocument
+ * @see nsIWebBrowserPersist
+ *
+ * @param aOuterWindowID
+ * The outer window ID of the subframe we'd like to persist.
+ * If set at 0, nsIWebBrowserPersistable will attempt to persist
+ * the top-level document. If the outer window ID is for a subframe
+ * that does not exist, or is not held beneath the nsIWebBrowserPersistable,
+ * aRecv's onError method will be called with NS_ERROR_NO_CONTENT.
+ * @param aRecv
+ * The nsIWebBrowserPersistDocumentReceiver is a callback that
+ * will be fired once the document is ready for persisting.
+ */
+[scriptable, uuid(f4c3fa8e-83e9-49f8-ac6f-951fc7541fe4)]
+interface nsIWebBrowserPersistable : nsISupports
+{
+ void startPersistence(in unsigned long long aOuterWindowID,
+ in nsIWebBrowserPersistDocumentReceiver aRecv);
+};
diff --git a/dom/webbrowserpersist/nsWebBrowserPersist.cpp b/dom/webbrowserpersist/nsWebBrowserPersist.cpp
new file mode 100644
index 000000000..437d21997
--- /dev/null
+++ b/dom/webbrowserpersist/nsWebBrowserPersist.cpp
@@ -0,0 +1,2814 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nspr.h"
+
+#include "nsIFileStreams.h" // New Necko file streams
+#include <algorithm>
+
+#include "nsAutoPtr.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIComponentRegistrar.h"
+#include "nsIStorageStream.h"
+#include "nsISeekableStream.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIEncodedChannel.h"
+#include "nsIUploadChannel.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIFileChannel.h"
+#include "nsEscape.h"
+#include "nsUnicharUtils.h"
+#include "nsIStringEnumerator.h"
+#include "nsCRT.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 "nsISHEntry.h"
+#include "nsIWebPageDescriptor.h"
+#include "nsIFormControl.h"
+#include "nsContentUtils.h"
+
+#include "nsIImageLoadingContent.h"
+
+#include "ftpCore.h"
+#include "nsITransport.h"
+#include "nsISocketTransport.h"
+#include "nsIStringBundle.h"
+#include "nsIProtocolHandler.h"
+
+#include "nsIWebBrowserPersistable.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/dom/HTMLSharedObjectElement.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;
+ nsCString mRelativePathToData;
+ nsCString mCharset;
+
+ nsresult GetLocalURI(nsIURI *targetBaseURI, nsCString& aSpecOut);
+};
+
+// Information about the output stream
+struct nsWebBrowserPersist::OutputData
+{
+ nsCOMPtr<nsIURI> mFile;
+ nsCOMPtr<nsIURI> mOriginalLocation;
+ nsCOMPtr<nsIOutputStream> mStream;
+ int64_t mSelfProgress;
+ int64_t mSelfProgressMax;
+ bool mCalcFileExt;
+
+ OutputData(nsIURI *aFile, nsIURI *aOriginalLocation, bool aCalcFileExt) :
+ mFile(aFile),
+ mOriginalLocation(aOriginalLocation),
+ mSelfProgress(0),
+ mSelfProgressMax(10000),
+ mCalcFileExt(aCalcFileExt)
+ {
+ }
+ ~OutputData()
+ {
+ 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)
+ { }
+
+ NS_DECL_NSIWEBBROWSERPERSISTRESOURCEVISITOR
+ NS_DECL_ISUPPORTS
+private:
+ RefPtr<nsWebBrowserPersist> mParent;
+ nsCOMPtr<nsIURI> mFile;
+ nsCOMPtr<nsIFile> mDataPath;
+
+ virtual ~OnWalk() { }
+};
+
+NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWalk,
+ nsIWebBrowserPersistResourceVisitor)
+
+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() { }
+};
+
+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() { }
+};
+
+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),
+ mFirstAndOnlyUse(true),
+ mSavingDocument(false),
+ mCancel(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(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) ? true : false;
+ mSerializingOutput = (mPersistFlags & PERSIST_FLAGS_SERIALIZE_OUTPUT) ? true : false;
+ 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, nsISupports *aCacheKey,
+ nsIURI *aReferrer, uint32_t aReferrerPolicy,
+ nsIInputStream *aPostData, const char *aExtraHeaders,
+ nsISupports *aFile, nsILoadContext* aPrivacyContext)
+{
+ return SavePrivacyAwareURI(aURI, aCacheKey, aReferrer, aReferrerPolicy,
+ aPostData, aExtraHeaders, aFile,
+ aPrivacyContext && aPrivacyContext->UsePrivateBrowsing());
+}
+
+NS_IMETHODIMP nsWebBrowserPersist::SavePrivacyAwareURI(
+ nsIURI *aURI, nsISupports *aCacheKey,
+ nsIURI *aReferrer, uint32_t aReferrerPolicy,
+ nsIInputStream *aPostData, const char *aExtraHeaders,
+ nsISupports *aFile, 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);
+
+ // SaveURI doesn't like broken uris.
+ mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS;
+ rv = SaveURIInternal(aURI, aCacheKey, aReferrer, aReferrerPolicy,
+ 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);
+
+ // SaveURI 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<nsIDocument> localDoc = do_QueryInterface(aDocument);
+ if (localDoc) {
+ doc = new mozilla::WebBrowserPersistLocalDocument(localDoc);
+ } else {
+ rv = NS_ERROR_NO_INTERFACE;
+ }
+ }
+ if (doc) {
+ rv = SaveDocumentInternal(doc, fileAsURI, datapathAsURI);
+ }
+ if (NS_FAILED(rv)) {
+ SendErrorStatusChange(true, rv, nullptr, mURI);
+ EndDownload(rv);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsWebBrowserPersist::Cancel(nsresult aReason)
+{
+ 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->AsyncOpen2(this);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ // add this to the upload list
+ nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(destChannel);
+ mUploadList.Put(keyPtr, new UploadData(aDestinationURI));
+
+ return NS_OK;
+}
+
+void
+nsWebBrowserPersist::SerializeNextFile()
+{
+ nsresult rv = NS_OK;
+ MOZ_ASSERT(mWalkStack.Length() == 0);
+
+ // First, handle gathered URIs.
+ // Count how many URIs in the URI map require persisting
+ uint32_t urisToPersist = 0;
+ if (mURIMap.Count() > 0) {
+ // 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.
+ for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) {
+ URIData *data = iter.UserData();
+ if (data->mNeedsPersisting && !data->mSaved) {
+ urisToPersist++;
+ }
+ }
+ }
+
+ if (urisToPersist > 0) {
+ // Persist each file in the uri map. The document(s)
+ // will be saved after the last one of these is saved.
+ for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) {
+ URIData *data = iter.UserData();
+
+ if (!data->mNeedsPersisting || data->mSaved) {
+ continue;
+ }
+
+ nsresult rv;
+
+ // Create a URI from the key.
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), iter.Key(),
+ data->mCharset.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+
+ // Make a URI to save the data to.
+ nsCOMPtr<nsIURI> fileAsURI;
+ rv = data->mDataPath->Clone(getter_AddRefs(fileAsURI));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+ rv = AppendPathToURI(fileAsURI, data->mFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+
+ // The Referrer Policy doesn't matter here since the referrer is
+ // nullptr.
+ rv = SaveURIInternal(uri, nullptr, nullptr,
+ mozilla::net::RP_Default, 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) {
+ // Store the actual object because once it's persisted this
+ // will be fixed up with the right file extension.
+ 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(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 (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoCString mapTo;
+ nsresult rv = iter.UserData()->GetLocalURI(mTargetBaseURI, mapTo);
+ if (NS_SUCCEEDED(rv) || !mapTo.IsVoid()) {
+ flatMap->Add(iter.Key(), mapTo);
+ }
+ }
+ mFlatURIMap = flatMap.forget();
+
+ 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(mParent,
+ &nsWebBrowserPersist::SerializeNextFile));
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsWebBrowserPersist::nsIRequestObserver
+//*****************************************************************************
+
+NS_IMETHODIMP nsWebBrowserPersist::OnStartRequest(
+ nsIRequest* request, nsISupports *ctxt)
+{
+ 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)
+ {
+ // 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))
+ {
+ // this is the first point at which the server can tell us the mimetype
+ CalculateAndAppendFileExt(data->mFile, channel, data->mOriginalLocation);
+
+ // now make filename conformant and unique
+ CalculateUniqueFilename(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)
+ {
+ // 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, nsISupports *ctxt, 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);
+ }
+
+ // This will automatically close the output stream
+ 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
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsWebBrowserPersist::OnDataAvailable(
+ nsIRequest* request, nsISupports *aContext, nsIInputStream *aIStream,
+ uint64_t aOffset, uint32_t aLength)
+{
+ 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);
+
+ 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;
+
+ // 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
+ rv = StartUpload(storStream, data->mFile, contentType);
+ if (NS_FAILED(rv))
+ {
+ readError = false;
+ cancel = true;
+ }
+ }
+ }
+ }
+
+ // Notify listener if an error occurred.
+ if (cancel)
+ {
+ SendErrorStatusChange(readError, rv,
+ readError ? request : nullptr, data->mFile);
+ }
+ }
+
+ // Cancel reading?
+ if (cancel)
+ {
+ EndDownload(NS_BINDING_ABORTED);
+ }
+
+ return NS_OK;
+}
+
+
+//*****************************************************************************
+// nsWebBrowserPersist::nsIProgressEventSink
+//*****************************************************************************
+
+NS_IMETHODIMP nsWebBrowserPersist::OnProgress(
+ nsIRequest *request, nsISupports *ctxt, 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, ctxt, aProgress, aProgressMax);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWebBrowserPersist::OnStatus(
+ nsIRequest *request, nsISupports *ctxt, 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_BEGIN_FTP_TRANSACTION:
+ case NS_NET_STATUS_END_FTP_TRANSACTION:
+ 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, ctxt, 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));
+ nsAutoString path;
+ nsresult rv;
+ if (file)
+ {
+ file->GetPath(path);
+ }
+ else
+ {
+ nsAutoCString fileurl;
+ rv = aURI->GetSpec(fileurl);
+ NS_ENSURE_SUCCESS(rv, rv);
+ AppendUTF8toUTF16(fileurl, path);
+ }
+
+ nsAutoString msgId;
+ switch(aResult)
+ {
+ case NS_ERROR_FILE_NAME_TOO_LONG:
+ // File name too long.
+ msgId.AssignLiteral("fileNameTooLongError");
+ break;
+ case NS_ERROR_FILE_ALREADY_EXISTS:
+ // File exists with same name as directory.
+ msgId.AssignLiteral("fileAlreadyExistsError");
+ break;
+ case NS_ERROR_FILE_DISK_FULL:
+ case NS_ERROR_FILE_NO_DEVICE_SPACE:
+ // Out of space on target volume.
+ msgId.AssignLiteral("diskFull");
+ break;
+
+ case NS_ERROR_FILE_READ_ONLY:
+ // Attempt to write to read/only file.
+ msgId.AssignLiteral("readOnly");
+ break;
+
+ case NS_ERROR_FILE_ACCESS_DENIED:
+ // Attempt to write without sufficient permissions.
+ msgId.AssignLiteral("accessError");
+ break;
+
+ default:
+ // Generic read/write error message.
+ if (aIsReadError)
+ msgId.AssignLiteral("readError");
+ else
+ msgId.AssignLiteral("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);
+
+ nsXPIDLString msgText;
+ const char16_t *strings[1];
+ strings[0] = path.get();
+ rv = bundle->FormatStringFromName(msgId.get(), strings, 1, getter_Copies(msgText));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ mProgressListener->OnStatusChange(nullptr, aRequest, aResult, msgText);
+
+ 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)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsAutoCString newPath;
+ nsresult rv = aURI->GetPath(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);
+ aURI->SetPath(newPath);
+
+ return NS_OK;
+}
+
+nsresult nsWebBrowserPersist::SaveURIInternal(
+ nsIURI *aURI, nsISupports *aCacheKey, nsIURI *aReferrer,
+ uint32_t aReferrerPolicy, nsIInputStream *aPostData,
+ const char *aExtraHeaders, nsIURI *aFile,
+ bool aCalcFileExt, bool aIsPrivate)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ 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;
+ }
+
+ // Extract the cache key
+ nsCOMPtr<nsISupports> cacheKey;
+ if (aCacheKey)
+ {
+ // Test if the cache key is actually a web page descriptor (docshell)
+ // or session history entry.
+ nsCOMPtr<nsISHEntry> shEntry = do_QueryInterface(aCacheKey);
+ if (!shEntry)
+ {
+ nsCOMPtr<nsIWebPageDescriptor> webPageDescriptor =
+ do_QueryInterface(aCacheKey);
+ if (webPageDescriptor)
+ {
+ nsCOMPtr<nsISupports> currentDescriptor;
+ webPageDescriptor->GetCurrentDescriptor(getter_AddRefs(currentDescriptor));
+ shEntry = do_QueryInterface(currentDescriptor);
+ }
+ }
+
+ if (shEntry)
+ {
+ shEntry->GetCacheKey(getter_AddRefs(cacheKey));
+ }
+ else
+ {
+ // Assume a plain cache key
+ cacheKey = aCacheKey;
+ }
+ }
+
+ // Open a channel to the URI
+ nsCOMPtr<nsIChannel> inputChannel;
+ rv = NS_NewChannel(getter_AddRefs(inputChannel),
+ aURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ 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);
+ }
+ }
+
+ if (mPersistFlags & PERSIST_FLAGS_FORCE_ALLOW_COOKIES)
+ {
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
+ do_QueryInterface(inputChannel);
+ if (httpChannelInternal)
+ httpChannelInternal->SetThirdPartyFlags(nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+ }
+
+ // Set the referrer, post data and headers if any
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(inputChannel));
+ if (httpChannel)
+ {
+ // Referrer
+ if (aReferrer)
+ {
+ httpChannel->SetReferrerWithPolicy(aReferrer, aReferrerPolicy);
+ }
+
+ // 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, EmptyCString(), -1);
+ }
+ }
+
+ // Cache key
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(httpChannel));
+ if (cacheChannel && cacheKey)
+ {
+ cacheChannel->SetCacheKey(cacheKey);
+ }
+
+ // 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", true);
+ 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 = NS_MaybeOpenChannelUsingOpen2(aChannel,
+ getter_AddRefs(fileInputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedInputStream),
+ fileInputStream, BUFFERED_OUTPUT_SIZE);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString contentType;
+ aChannel->GetContentType(contentType);
+ return StartUpload(bufferedInputStream, aFile, contentType);
+ }
+
+ // Read from the input channel
+ nsresult rv = NS_MaybeOpenChannelUsingAsyncOpen2(aChannel, 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;
+ }
+
+ // Add the output transport to the output map with the channel as the key
+ nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aChannel);
+ mOutputMap.Put(keyPtr, new 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;
+ contentType.AssignWithConversion(aContentType);
+ nsAutoCString ext;
+ rv = mMIMEService->GetPrimaryExtension(contentType, EmptyCString(), 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_LITERAL_CSTRING("/")
+ + 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
+
+ DocData *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
+ {
+ DocData *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)
+{
+ return mParent->StoreURI(nsAutoCString(aURI).get());
+}
+
+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.get(), 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, uriSpec, data);
+}
+
+
+NS_IMETHODIMP
+nsWebBrowserPersist::OnWalk::EndVisit(nsIWebBrowserPersistDocument* aDoc,
+ nsresult aStatus)
+{
+ if (NS_FAILED(aStatus)) {
+ mParent->SendErrorStatusChange(true, aStatus, nullptr, mFile);
+ mParent->EndDownload(aStatus);
+ return aStatus;
+ }
+ mParent->FinishSaveDocumentInternal(mFile, mDataPath);
+ 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
+ CleanupData *cleanupData = new CleanupData;
+ cleanupData->mFile = aDataPath;
+ cleanupData->mIsDirectory = true;
+ mCleanupList.AppendElement(cleanupData);
+ }
+ }
+ }
+
+ if (mWalkStack.Length() > 0) {
+ mozilla::UniquePtr<WalkData> toWalk;
+ mWalkStack.LastElement().swap(toWalk);
+ mWalkStack.TruncateLength(mWalkStack.Length() - 1);
+ // Bounce this off the event loop to avoid stack overflow.
+ typedef StoreCopyPassByRRef<decltype(toWalk)> WalkStorage;
+ auto saveMethod = &nsWebBrowserPersist::SaveDocumentDeferred;
+ nsCOMPtr<nsIRunnable> saveLater =
+ NewRunnableMethod<WalkStorage>(this, saveMethod,
+ mozilla::Move(toWalk));
+ NS_DispatchToCurrentThread(saveLater);
+ } else {
+ // Done walking DOMs; on to the serialization phase.
+ SerializeNextFile();
+ }
+}
+
+void nsWebBrowserPersist::Cleanup()
+{
+ mURIMap.Clear();
+ for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(iter.Key());
+ if (channel) {
+ channel->Cancel(NS_BINDING_ABORTED);
+ }
+ }
+ mOutputMap.Clear();
+
+ for (auto iter = mUploadList.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(iter.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<nsISimpleEnumerator> dirStack;
+ int32_t stackSize = 0;
+
+ // Push the top level enum onto the stack
+ nsCOMPtr<nsISimpleEnumerator> pos;
+ if (NS_SUCCEEDED(file->GetDirectoryEntries(getter_AddRefs(pos))))
+ dirStack.AppendObject(pos);
+
+ while (isEmptyDirectory && (stackSize = dirStack.Count()))
+ {
+ // Pop the last element
+ nsCOMPtr<nsISimpleEnumerator> curPos;
+ curPos = dirStack[stackSize-1];
+ dirStack.RemoveObjectAt(stackSize - 1);
+
+ // Test if the enumerator has any more files in it
+ bool hasMoreElements = false;
+ curPos->HasMoreElements(&hasMoreElements);
+ if (!hasMoreElements)
+ {
+ continue;
+ }
+
+ // Child files automatically make this code drop out,
+ // while child dirs keep the loop going.
+ nsCOMPtr<nsISupports> child;
+ curPos->GetNext(getter_AddRefs(child));
+ NS_ASSERTION(child, "No child element, but hasMoreElements says otherwise");
+ if (!child)
+ continue;
+ nsCOMPtr<nsIFile> childAsFile = do_QueryInterface(child);
+ NS_ASSERTION(childAsFile, "This should be a file but isn't");
+
+ bool childIsSymlink = false;
+ childAsFile->IsSymlink(&childIsSymlink);
+ bool childIsDir = false;
+ childAsFile->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<nsISimpleEnumerator> childPos;
+ childAsFile->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<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 (1)
+ {
+ // Make a file name,
+ // Foo become foo_001, foo_002, etc.
+ // Empty files become _001, _002 etc.
+
+ if (base.IsEmpty() || duplicateCounter > 1)
+ {
+ char * tmp = PR_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);
+ PR_smprintf_free(tmp);
+ }
+ 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;
+ filenameAsUnichar.AssignWithConversion(filename.get());
+ localFile->SetLeafName(filenameAsUnichar);
+
+ // Resync the URI with the file after the extension has been appended
+ nsresult rv;
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ fileURL->SetFile(localFile); // this should recalculate uri
+ }
+ else
+ {
+ url->SetFileName(filename);
+ }
+ }
+
+ 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)
+ {
+ fileName.AssignWithConversion(NS_UnescapeURL(nameFromURL).BeginReading());
+ 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 (nsCRT::IsAsciiAlpha(*p) || nsCRT::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)
+{
+ nsresult rv;
+
+ 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);
+ }
+
+ // Append the extension onto the file
+ if (!contentType.IsEmpty())
+ {
+ nsCOMPtr<nsIMIMEInfo> mimeInfo;
+ mMIMEService->GetFromTypeAndExtension(
+ contentType, EmptyCString(), getter_AddRefs(mimeInfo));
+
+ nsCOMPtr<nsIFile> localFile;
+ GetLocalFileFromURI(aURI, getter_AddRefs(localFile));
+
+ if (mimeInfo)
+ {
+ nsCOMPtr<nsIURL> url(do_QueryInterface(aURI));
+ NS_ENSURE_TRUE(url, NS_ERROR_FAILURE);
+
+ nsAutoCString newFileName;
+ url->GetFileName(newFileName);
+
+ // Test if the current extension is current for the mime type
+ bool hasExtension = false;
+ int32_t ext = newFileName.RFind(".");
+ if (ext != -1)
+ {
+ mimeInfo->ExtensionExists(Substring(newFileName, ext + 1), &hasExtension);
+ }
+
+ // Append the mime file extension
+ nsAutoCString fileExt;
+ if (!hasExtension)
+ {
+ // Test if previous extension is acceptable
+ nsCOMPtr<nsIURL> oldurl(do_QueryInterface(aOriginalURIWithExtension));
+ NS_ENSURE_TRUE(oldurl, NS_ERROR_FAILURE);
+ oldurl->GetFileExtension(fileExt);
+ bool useOldExt = false;
+ if (!fileExt.IsEmpty())
+ {
+ mimeInfo->ExtensionExists(fileExt, &useOldExt);
+ }
+
+ // can't use old extension so use primary extension
+ if (!useOldExt)
+ {
+ mimeInfo->GetPrimaryExtension(fileExt);
+ }
+
+ if (!fileExt.IsEmpty())
+ {
+ uint32_t newLength = newFileName.Length() + fileExt.Length() + 1;
+ if (newLength > kDefaultMaxFilenameLength)
+ {
+ if (fileExt.Length() > kDefaultMaxFilenameLength/2)
+ fileExt.Truncate(kDefaultMaxFilenameLength/2);
+
+ uint32_t diff = kDefaultMaxFilenameLength - 1 -
+ fileExt.Length();
+ if (newFileName.Length() > diff)
+ newFileName.Truncate(diff);
+ }
+ newFileName.Append('.');
+ newFileName.Append(fileExt);
+ }
+
+ if (localFile)
+ {
+ localFile->SetLeafName(NS_ConvertUTF8toUTF16(newFileName));
+
+ // Resync the URI with the file after the extension has been appended
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ fileURL->SetFile(localFile); // this should recalculate uri
+ }
+ else
+ {
+ url->SetFileName(newFileName);
+ }
+ }
+
+ }
+ }
+
+ return NS_OK;
+}
+
+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);
+
+ *aOutputStream = NS_BufferOutputStream(fileOutputStream,
+ BUFFERED_OUTPUT_SIZE).take();
+
+ if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE)
+ {
+ // Add to cleanup list in event of failure
+ CleanupData *cleanupData = new CleanupData;
+ if (!cleanupData) {
+ NS_RELEASE(*aOutputStream);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ cleanupData->mFile = aFile;
+ cleanupData->mIsDirectory = false;
+ mCleanupList.AppendElement(cleanupData);
+ }
+
+ 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()
+{
+ EndDownload(NS_OK);
+}
+
+void
+nsWebBrowserPersist::EndDownload(nsresult aResult)
+{
+ // Store the error code in the result if it is an error
+ if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(aResult))
+ {
+ mPersistResult = 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 (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) {
+ nsISupports* key = iter.Key();
+ 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) {
+ // If a match was found, remove the data entry with the old channel
+ // key and re-add it with the new channel key.
+ nsAutoPtr<OutputData> outputData;
+ mOutputMap.RemoveAndForget(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.Put(keyPtr, outputData.forget());
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsWebBrowserPersist::CalcTotalProgress()
+{
+ mTotalCurrentProgress = 0;
+ mTotalMaxProgress = 0;
+
+ if (mOutputMap.Count() > 0) {
+ // Total up the progress of each output stream
+ for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) {
+ // Only count toward total progress if destination file is local.
+ OutputData* data = iter.UserData();
+ 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 (auto iter = mUploadList.Iter(); !iter.Done(); iter.Next()) {
+ UploadData* data = iter.UserData();
+ 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 char *aURI, bool aNeedsPersisting, URIData **aData)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri),
+ nsDependentCString(aURI),
+ mCurrentCharset.get(),
+ mCurrentBaseURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return StoreURI(uri, aNeedsPersisting, aData);
+}
+
+nsresult
+nsWebBrowserPersist::StoreURI(
+ nsIURI *aURI, 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, 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) {
+ rv = mFile->Clone(getter_AddRefs(fileAsURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ rv = mDataPath->Clone(getter_AddRefs(fileAsURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = AppendPathToURI(fileAsURI, mFilename);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // remove username/password if present
+ fileAsURI->SetUserPass(EmptyCString());
+
+ // 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)
+{
+ // Check if there is an encoder for the desired content type.
+ nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE);
+ contractID.Append(aContentType);
+
+ nsCOMPtr<nsIComponentRegistrar> registrar;
+ NS_GetComponentRegistrar(getter_AddRefs(registrar));
+ if (registrar)
+ {
+ bool result;
+ nsresult rv = registrar->IsContractIDRegistered(contractID.get(),
+ &result);
+ if (NS_SUCCEEDED(rv) && result)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsresult
+nsWebBrowserPersist::SaveSubframeContent(
+ nsIWebBrowserPersistDocument *aFrameContent,
+ 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);
+
+ nsXPIDLString 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;
+ rv = mCurrentDataPath->Clone(getter_AddRefs(frameURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = AppendPathToURI(frameURI, filenameWithExt);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Work out the path for the subframe data
+ nsCOMPtr<nsIURI> frameDataURI;
+ rv = mCurrentDataPath->Clone(getter_AddRefs(frameDataURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoString newFrameDataPath(aData->mFilename);
+
+ // Append _data
+ newFrameDataPath.AppendLiteral("_data");
+ rv = AppendPathToURI(frameDataURI, newFrameDataPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make frame document & data path conformant and unique
+ rv = CalculateUniqueFilename(frameURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = CalculateUniqueFilename(frameDataURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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(mozilla::Move(toWalk));
+ } else {
+ rv = StoreURI(aURISpec.get());
+ }
+ 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_DATA_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, 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.Contains(spec))
+ {
+ data = mURIMap.Get(spec);
+ 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;
+ NS_ENSURE_TRUE(data, NS_ERROR_OUT_OF_MEMORY);
+
+ 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;
+
+ if (aNeedsPersisting)
+ mCurrentThingsToPersist++;
+
+ mURIMap.Put(spec, 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 000000000..1816af0a6
--- /dev/null
+++ b/dom/webbrowserpersist/nsWebBrowserPersist.h
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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 "nsIDocumentEncoder.h"
+#include "nsITransport.h"
+#include "nsIProgressEventSink.h"
+#include "nsIFile.h"
+#include "nsIWebProgressListener2.h"
+#include "nsIWebBrowserPersistDocument.h"
+
+#include "mozilla/UniquePtr.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+
+#include "nsCWebBrowserPersist.h"
+
+class nsIStorageStream;
+class nsIWebBrowserPersistDocument;
+
+class nsWebBrowserPersist final : public nsIInterfaceRequestor,
+ public nsIWebBrowserPersist,
+ public nsIStreamListener,
+ public nsIProgressEventSink,
+ public nsSupportsWeakReference
+{
+ friend class nsEncoderNodeFixup;
+
+// Public members
+public:
+ nsWebBrowserPersist();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICANCELABLE
+ NS_DECL_NSIWEBBROWSERPERSIST
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIPROGRESSEVENTSINK
+
+// Private members
+private:
+ virtual ~nsWebBrowserPersist();
+ nsresult SaveURIInternal(
+ nsIURI *aURI, nsISupports *aCacheKey, nsIURI *aReferrer,
+ uint32_t aReferrerPolicy, 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;
+ struct URIFixupData;
+
+ class OnWalk;
+ 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);
+ nsresult MakeAndStoreLocalFilenameInURIMap(
+ nsIURI *aURI, 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);
+ nsresult CalculateUniqueFilename(nsIURI *aURI);
+ nsresult MakeFilenameFromURI(
+ nsIURI *aURI, nsString &aFilename);
+ nsresult StoreURI(
+ const char *aURI,
+ bool aNeedsPersisting = true,
+ URIData **aData = nullptr);
+ nsresult StoreURI(
+ nsIURI *aURI,
+ bool aNeedsPersisting = true,
+ URIData **aData = nullptr);
+ bool DocumentEncoderExists(const char *aContentType);
+
+ nsresult SaveSubframeContent(
+ nsIWebBrowserPersistDocument *aFrameContent,
+ const nsCString& aURISpec,
+ URIData *aData);
+ nsresult SendErrorStatusChange(
+ bool aIsReadError, nsresult aResult, nsIRequest *aRequest, nsIURI *aURI);
+
+ nsresult FixRedirectedChannelEntry(nsIChannel *aNewChannel);
+
+ void EndDownload(nsresult aResult);
+ void FinishDownload();
+ 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;
+ nsClassHashtable<nsISupportsHashKey, OutputData> mOutputMap;
+ nsClassHashtable<nsISupportsHashKey, UploadData> mUploadList;
+ 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;
+ bool mCancel;
+ bool mCompleted;
+ bool mStartSaving;
+ bool mReplaceExisting;
+ bool mSerializingOutput;
+ bool mIsPrivate;
+ uint32_t mPersistFlags;
+ nsresult mPersistResult;
+ int64_t mTotalCurrentProgress;
+ int64_t mTotalMaxProgress;
+ int16_t mWrapColumn;
+ uint32_t mEncodingFlags;
+ nsString mContentType;
+};
+
+#endif