summaryrefslogtreecommitdiffstats
path: root/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp')
-rw-r--r--dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp1319
1 files changed, 1319 insertions, 0 deletions
diff --git a/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp b/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp
new file mode 100644
index 0000000000..524f496d35
--- /dev/null
+++ b/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp
@@ -0,0 +1,1319 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistLocalDocument.h"
+#include "WebBrowserPersistDocumentParent.h"
+
+#include "mozilla/dom/Attr.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/Comment.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLAnchorElement.h"
+#include "mozilla/dom/HTMLAreaElement.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/HTMLLinkElement.h"
+#include "mozilla/dom/HTMLObjectElement.h"
+#include "mozilla/dom/HTMLOptionElement.h"
+#include "mozilla/dom/HTMLSharedElement.h"
+#include "mozilla/dom/HTMLTextAreaElement.h"
+#include "mozilla/dom/NodeFilterBinding.h"
+#include "mozilla/dom/ProcessingInstruction.h"
+#include "mozilla/dom/ResponsiveImageSelector.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/TreeWalker.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/Unused.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsContentCID.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMAttributeMap.h"
+#include "nsFrameLoader.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsIContent.h"
+#include "nsICookieJarSettings.h"
+#include "nsIDOMWindowUtils.h"
+#include "mozilla/dom/Document.h"
+#include "nsIDocumentEncoder.h"
+#include "nsILoadContext.h"
+#include "nsIProtocolHandler.h"
+#include "nsISHEntry.h"
+#include "nsIURIMutator.h"
+#include "nsIWebBrowserPersist.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebPageDescriptor.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+
+namespace mozilla {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebBrowserPersistLocalDocument)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebBrowserPersistLocalDocument)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebBrowserPersistLocalDocument)
+ NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersistDocument)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(WebBrowserPersistLocalDocument, mDocument)
+
+WebBrowserPersistLocalDocument::WebBrowserPersistLocalDocument(
+ dom::Document* aDocument)
+ : mDocument(aDocument), mPersistFlags(0) {
+ MOZ_ASSERT(mDocument);
+}
+
+WebBrowserPersistLocalDocument::~WebBrowserPersistLocalDocument() = default;
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::SetPersistFlags(uint32_t aFlags) {
+ mPersistFlags = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetPersistFlags(uint32_t* aFlags) {
+ *aFlags = mPersistFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetIsClosed(bool* aIsClosed) {
+ *aIsClosed = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetIsPrivate(bool* aIsPrivate) {
+ nsCOMPtr<nsILoadContext> privacyContext = mDocument->GetLoadContext();
+ *aIsPrivate = privacyContext && privacyContext->UsePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetDocumentURI(nsACString& aURISpec) {
+ nsCOMPtr<nsIURI> uri = mDocument->GetDocumentURI();
+ if (!uri) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return uri->GetSpec(aURISpec);
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetBaseURI(nsACString& aURISpec) {
+ nsCOMPtr<nsIURI> uri = GetBaseURI();
+ if (!uri) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return uri->GetSpec(aURISpec);
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetContentType(nsACString& aContentType) {
+ nsAutoString utf16Type;
+ mDocument->GetContentType(utf16Type);
+ CopyUTF16toUTF8(utf16Type, aContentType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetCharacterSet(nsACString& aCharSet) {
+ GetCharacterSet()->Name(aCharSet);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetTitle(nsAString& aTitle) {
+ nsAutoString titleBuffer;
+ mDocument->GetTitle(titleBuffer);
+ aTitle = titleBuffer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetReferrerInfo(
+ nsIReferrerInfo** aReferrerInfo) {
+ *aReferrerInfo = mDocument->GetReferrerInfo();
+ NS_IF_ADDREF(*aReferrerInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetCookieJarSettings(
+ nsICookieJarSettings** aCookieJarSettings) {
+ *aCookieJarSettings = mDocument->CookieJarSettings();
+ NS_ADDREF(*aCookieJarSettings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetContentDisposition(nsAString& aCD) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
+ if (NS_WARN_IF(!window)) {
+ aCD.SetIsVoid(true);
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDOMWindowUtils> utils =
+ nsGlobalWindowOuter::Cast(window)->WindowUtils();
+ nsresult rv = utils->GetDocumentMetadata(u"content-disposition"_ns, aCD);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aCD.SetIsVoid(true);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetCacheKey(uint32_t* aKey) {
+ Maybe<uint32_t> cacheKey;
+
+ if (nsDocShell* docShell = nsDocShell::Cast(mDocument->GetDocShell())) {
+ cacheKey = docShell->GetCacheKeyFromCurrentEntry();
+ }
+ *aKey = cacheKey.valueOr(0);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetPostData(nsIInputStream** aStream) {
+ nsCOMPtr<nsIInputStream> postData;
+ if (nsDocShell* docShell = nsDocShell::Cast(mDocument->GetDocShell())) {
+ postData = docShell->GetPostDataFromCurrentEntry();
+ }
+
+ postData.forget(aStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetPrincipal(nsIPrincipal** aPrincipal) {
+ nsCOMPtr<nsIPrincipal> nodePrincipal = mDocument->NodePrincipal();
+ nodePrincipal.forget(aPrincipal);
+ return NS_OK;
+}
+
+already_AddRefed<nsISHEntry> WebBrowserPersistLocalDocument::GetHistory() {
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
+ if (NS_WARN_IF(!window)) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(window);
+ if (NS_WARN_IF(!webNav)) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIWebPageDescriptor> desc = do_QueryInterface(webNav);
+ if (NS_WARN_IF(!desc)) {
+ return nullptr;
+ }
+ nsCOMPtr<nsISupports> curDesc;
+ nsresult rv = desc->GetCurrentDescriptor(getter_AddRefs(curDesc));
+ // This can fail if, e.g., the document is a Print Preview.
+ if (NS_FAILED(rv) || NS_WARN_IF(!curDesc)) {
+ return nullptr;
+ }
+ nsCOMPtr<nsISHEntry> history = do_QueryInterface(curDesc);
+ return history.forget();
+}
+
+NotNull<const Encoding*> WebBrowserPersistLocalDocument::GetCharacterSet()
+ const {
+ return mDocument->GetDocumentCharacterSet();
+}
+
+uint32_t WebBrowserPersistLocalDocument::GetPersistFlags() const {
+ return mPersistFlags;
+}
+
+nsIURI* WebBrowserPersistLocalDocument::GetBaseURI() const {
+ return mDocument->GetBaseURI();
+}
+
+namespace {
+
+// Helper class for ReadResources().
+class ResourceReader final : public nsIWebBrowserPersistDocumentReceiver {
+ public:
+ ResourceReader(WebBrowserPersistLocalDocument* aParent,
+ nsIWebBrowserPersistResourceVisitor* aVisitor);
+ nsresult OnWalkDOMNode(nsINode* aNode);
+
+ // This is called both to indicate the end of the document walk
+ // and when a subdocument is (maybe asynchronously) sent to the
+ // visitor. The call to EndVisit needs to happen after both of
+ // those have finished.
+ void DocumentDone(nsresult aStatus);
+
+ NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER
+ NS_DECL_ISUPPORTS
+
+ private:
+ RefPtr<WebBrowserPersistLocalDocument> mParent;
+ nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor;
+ nsCOMPtr<nsIURI> mCurrentBaseURI;
+ uint32_t mPersistFlags;
+
+ // The number of DocumentDone calls after which EndVisit will be
+ // called on the visitor. Counts the main document if it's still
+ // being walked and any outstanding asynchronous subdocument
+ // StartPersistence calls.
+ size_t mOutstandingDocuments;
+ // Collects the status parameters to DocumentDone calls.
+ nsresult mEndStatus;
+
+ nsresult OnWalkURI(const nsACString& aURISpec,
+ nsContentPolicyType aContentPolicyType);
+ nsresult OnWalkURI(nsIURI* aURI, nsContentPolicyType aContentPolicyType);
+ nsresult OnWalkAttribute(dom::Element* aElement,
+ nsContentPolicyType aContentPolicyType,
+ const char* aAttribute,
+ const char* aNamespaceURI = "");
+ nsresult OnWalkSubframe(nsINode* aNode);
+ nsresult OnWalkSrcSet(dom::Element* aElement);
+
+ ~ResourceReader();
+
+ using IWBP = nsIWebBrowserPersist;
+};
+
+NS_IMPL_ISUPPORTS(ResourceReader, nsIWebBrowserPersistDocumentReceiver)
+
+ResourceReader::ResourceReader(WebBrowserPersistLocalDocument* aParent,
+ nsIWebBrowserPersistResourceVisitor* aVisitor)
+ : mParent(aParent),
+ mVisitor(aVisitor),
+ mCurrentBaseURI(aParent->GetBaseURI()),
+ mPersistFlags(aParent->GetPersistFlags()),
+ mOutstandingDocuments(1),
+ mEndStatus(NS_OK) {
+ MOZ_ASSERT(mCurrentBaseURI);
+}
+
+ResourceReader::~ResourceReader() { MOZ_ASSERT(mOutstandingDocuments == 0); }
+
+void ResourceReader::DocumentDone(nsresult aStatus) {
+ MOZ_ASSERT(mOutstandingDocuments > 0);
+ if (NS_SUCCEEDED(mEndStatus)) {
+ mEndStatus = aStatus;
+ }
+ if (--mOutstandingDocuments == 0) {
+ mVisitor->EndVisit(mParent, mEndStatus);
+ }
+}
+
+nsresult ResourceReader::OnWalkSubframe(nsINode* aNode) {
+ RefPtr<nsFrameLoaderOwner> loaderOwner = do_QueryObject(aNode);
+ NS_ENSURE_STATE(loaderOwner);
+ RefPtr<nsFrameLoader> loader = loaderOwner->GetFrameLoader();
+ NS_ENSURE_STATE(loader);
+
+ RefPtr<dom::BrowsingContext> context = loader->GetBrowsingContext();
+ NS_ENSURE_STATE(context);
+
+ if (loader->IsRemoteFrame()) {
+ mVisitor->VisitBrowsingContext(mParent, context);
+ return NS_OK;
+ }
+
+ ++mOutstandingDocuments;
+ ErrorResult err;
+ loader->StartPersistence(context, this, err);
+ nsresult rv = err.StealNSResult();
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_NO_CONTENT) {
+ // Just ignore frames with no content document.
+ rv = NS_OK;
+ }
+ // StartPersistence won't eventually call this if it failed,
+ // so this does so (to keep mOutstandingDocuments correct).
+ DocumentDone(rv);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+ResourceReader::OnDocumentReady(nsIWebBrowserPersistDocument* aDocument) {
+ mVisitor->VisitDocument(mParent, aDocument);
+ DocumentDone(NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ResourceReader::OnError(nsresult aFailure) {
+ DocumentDone(aFailure);
+ return NS_OK;
+}
+
+nsresult ResourceReader::OnWalkURI(nsIURI* aURI,
+ nsContentPolicyType aContentPolicyType) {
+ // Test if this URI should be persisted. By default
+ // we should assume the URI is persistable.
+ bool doNotPersistURI;
+ nsresult rv = NS_URIChainHasFlags(
+ aURI, nsIProtocolHandler::URI_NON_PERSISTABLE, &doNotPersistURI);
+ if (NS_SUCCEEDED(rv) && doNotPersistURI) {
+ return NS_OK;
+ }
+
+ nsAutoCString stringURI;
+ rv = aURI->GetSpec(stringURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mVisitor->VisitResource(mParent, stringURI, aContentPolicyType);
+}
+
+nsresult ResourceReader::OnWalkURI(const nsACString& aURISpec,
+ nsContentPolicyType aContentPolicyType) {
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+
+ rv = NS_NewURI(getter_AddRefs(uri), aURISpec, mParent->GetCharacterSet(),
+ mCurrentBaseURI);
+ if (NS_FAILED(rv)) {
+ // We don't want to break saving a page in case of a malformed URI.
+ return NS_OK;
+ }
+ return OnWalkURI(uri, aContentPolicyType);
+}
+
+static void ExtractAttribute(dom::Element* aElement, const char* aAttribute,
+ const char* aNamespaceURI, nsCString& aValue) {
+ // Find the named URI attribute on the (element) node and store
+ // a reference to the URI that maps onto a local file name
+
+ RefPtr<nsDOMAttributeMap> attrMap = aElement->Attributes();
+
+ NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI);
+ NS_ConvertASCIItoUTF16 attribute(aAttribute);
+ RefPtr<dom::Attr> attr = attrMap->GetNamedItemNS(namespaceURI, attribute);
+ if (attr) {
+ nsAutoString value;
+ attr->GetValue(value);
+ CopyUTF16toUTF8(value, aValue);
+ } else {
+ aValue.Truncate();
+ }
+}
+
+nsresult ResourceReader::OnWalkAttribute(dom::Element* aElement,
+ nsContentPolicyType aContentPolicyType,
+ const char* aAttribute,
+ const char* aNamespaceURI) {
+ nsAutoCString uriSpec;
+ ExtractAttribute(aElement, aAttribute, aNamespaceURI, uriSpec);
+ if (uriSpec.IsEmpty()) {
+ return NS_OK;
+ }
+ return OnWalkURI(uriSpec, aContentPolicyType);
+}
+
+nsresult ResourceReader::OnWalkSrcSet(dom::Element* aElement) {
+ nsAutoString srcSet;
+ if (!aElement->GetAttr(nsGkAtoms::srcset, srcSet)) {
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+ auto eachCandidate = [&](dom::ResponsiveImageCandidate&& aCandidate) {
+ if (!aCandidate.IsValid() || NS_FAILED(rv)) {
+ return;
+ }
+ rv = OnWalkURI(NS_ConvertUTF16toUTF8(aCandidate.URLString()),
+ nsIContentPolicy::TYPE_IMAGE);
+ };
+ dom::ResponsiveImageSelector::ParseSourceSet(srcSet, eachCandidate);
+ return rv;
+}
+
+static nsresult GetXMLStyleSheetLink(dom::ProcessingInstruction* aPI,
+ nsAString& aHref) {
+ nsAutoString data;
+ aPI->GetData(data);
+
+ nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::href, aHref);
+ return NS_OK;
+}
+
+nsresult ResourceReader::OnWalkDOMNode(nsINode* aNode) {
+ // Fixup xml-stylesheet processing instructions
+ if (auto nodeAsPI = dom::ProcessingInstruction::FromNode(aNode)) {
+ nsAutoString target;
+ nodeAsPI->GetTarget(target);
+ if (target.EqualsLiteral("xml-stylesheet")) {
+ nsAutoString href;
+ GetXMLStyleSheetLink(nodeAsPI, href);
+ if (!href.IsEmpty()) {
+ return OnWalkURI(NS_ConvertUTF16toUTF8(href),
+ nsIContentPolicy::TYPE_STYLESHEET);
+ }
+ }
+ return NS_OK;
+ }
+
+ // Test the node to see if it's an image, frame, iframe, css, js
+ if (auto* img = dom::HTMLImageElement::FromNode(*aNode)) {
+ MOZ_TRY(OnWalkAttribute(img, nsIContentPolicy::TYPE_IMAGE, "src"));
+ MOZ_TRY(OnWalkSrcSet(img));
+ return NS_OK;
+ }
+
+ if (aNode->IsSVGElement(nsGkAtoms::image)) {
+ MOZ_TRY(OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
+ "href"));
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
+ "href", "http://www.w3.org/1999/xlink");
+ }
+
+ if (aNode->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video)) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_MEDIA,
+ "src");
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::source)) {
+ MOZ_TRY(OnWalkSrcSet(aNode->AsElement()));
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_MEDIA,
+ "src");
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::body)) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
+ "background");
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::table)) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
+ "background");
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::tr)) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
+ "background");
+ }
+
+ if (aNode->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
+ "background");
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::script)) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_SCRIPT,
+ "src");
+ }
+
+ if (aNode->IsSVGElement(nsGkAtoms::script)) {
+ MOZ_TRY(OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_SCRIPT,
+ "href"));
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_SCRIPT,
+ "href", "http://www.w3.org/1999/xlink");
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::embed)) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_OBJECT,
+ "src");
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::object)) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_OBJECT,
+ "data");
+ }
+
+ if (auto nodeAsLink = dom::HTMLLinkElement::FromNode(aNode)) {
+ // Test if the link has a rel value indicating it to be a stylesheet
+ nsAutoString linkRel;
+ nodeAsLink->GetRel(linkRel);
+ if (!linkRel.IsEmpty()) {
+ nsReadingIterator<char16_t> start;
+ nsReadingIterator<char16_t> end;
+ nsReadingIterator<char16_t> current;
+
+ linkRel.BeginReading(start);
+ linkRel.EndReading(end);
+
+ // Walk through space delimited string looking for "stylesheet"
+ for (current = start; current != end; ++current) {
+ // Ignore whitespace
+ if (nsCRT::IsAsciiSpace(*current)) {
+ continue;
+ }
+
+ // Grab the next space delimited word
+ nsReadingIterator<char16_t> startWord = current;
+ do {
+ ++current;
+ } while (current != end && !nsCRT::IsAsciiSpace(*current));
+
+ // Store the link for fix up if it says "stylesheet"
+ if (Substring(startWord, current)
+ .LowerCaseEqualsLiteral("stylesheet")) {
+ OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_STYLESHEET,
+ "href");
+ return NS_OK;
+ }
+ if (current == end) {
+ break;
+ }
+ }
+ }
+ return NS_OK;
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::frame)) {
+ return OnWalkSubframe(aNode);
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::iframe) &&
+ !(mPersistFlags & IWBP::PERSIST_FLAGS_IGNORE_IFRAMES)) {
+ return OnWalkSubframe(aNode);
+ }
+
+ auto nodeAsInput = dom::HTMLInputElement::FromNode(aNode);
+ if (nodeAsInput) {
+ return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
+ "src");
+ }
+
+ return NS_OK;
+}
+
+// Helper class for node rewriting in writeContent().
+class PersistNodeFixup final : public nsIDocumentEncoderNodeFixup {
+ public:
+ PersistNodeFixup(WebBrowserPersistLocalDocument* aParent,
+ nsIWebBrowserPersistURIMap* aMap, nsIURI* aTargetURI);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOCUMENTENCODERNODEFIXUP
+ private:
+ virtual ~PersistNodeFixup() = default;
+ RefPtr<WebBrowserPersistLocalDocument> mParent;
+ nsClassHashtable<nsCStringHashKey, nsCString> mMap;
+ nsCOMPtr<nsIURI> mCurrentBaseURI;
+ nsCOMPtr<nsIURI> mTargetBaseURI;
+
+ bool IsFlagSet(uint32_t aFlag) const {
+ return mParent->GetPersistFlags() & aFlag;
+ }
+
+ nsresult GetNodeToFixup(nsINode* aNodeIn, nsINode** aNodeOut);
+ nsresult FixupURI(nsAString& aURI);
+ nsresult FixupAttribute(nsINode* aNode, const char* aAttribute,
+ const char* aNamespaceURI = "");
+ nsresult FixupAnchor(nsINode* aNode);
+ nsresult FixupXMLStyleSheetLink(dom::ProcessingInstruction* aPI,
+ const nsAString& aHref);
+
+ nsresult FixupSrcSet(nsINode*);
+
+ using IWBP = nsIWebBrowserPersist;
+};
+
+NS_IMPL_ISUPPORTS(PersistNodeFixup, nsIDocumentEncoderNodeFixup)
+
+PersistNodeFixup::PersistNodeFixup(WebBrowserPersistLocalDocument* aParent,
+ nsIWebBrowserPersistURIMap* aMap,
+ nsIURI* aTargetURI)
+ : mParent(aParent),
+ mCurrentBaseURI(aParent->GetBaseURI()),
+ mTargetBaseURI(aTargetURI) {
+ if (aMap) {
+ uint32_t mapSize;
+ nsresult rv = aMap->GetNumMappedURIs(&mapSize);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ for (uint32_t i = 0; i < mapSize; ++i) {
+ nsAutoCString urlFrom;
+ auto urlTo = MakeUnique<nsCString>();
+
+ rv = aMap->GetURIMapping(i, urlFrom, *urlTo);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_SUCCEEDED(rv)) {
+ mMap.InsertOrUpdate(urlFrom, std::move(urlTo));
+ }
+ }
+ }
+}
+
+nsresult PersistNodeFixup::GetNodeToFixup(nsINode* aNodeIn,
+ nsINode** aNodeOut) {
+ // Avoid mixups in FixupNode that could leak objects; this goes
+ // against the usual out parameter convention, but it's a private
+ // method so shouldn't be a problem.
+ MOZ_ASSERT(!*aNodeOut);
+
+ if (!IsFlagSet(IWBP::PERSIST_FLAGS_FIXUP_ORIGINAL_DOM)) {
+ ErrorResult rv;
+ *aNodeOut = aNodeIn->CloneNode(false, rv).take();
+ return rv.StealNSResult();
+ }
+
+ NS_ADDREF(*aNodeOut = aNodeIn);
+ return NS_OK;
+}
+
+nsresult PersistNodeFixup::FixupURI(nsAString& aURI) {
+ // get the current location of the file (absolutized)
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI, mParent->GetCharacterSet(),
+ mCurrentBaseURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString spec;
+ rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const nsCString* replacement = mMap.Get(spec);
+ if (!replacement) {
+ // Note that most callers ignore this "failure".
+ return NS_ERROR_FAILURE;
+ }
+ if (!replacement->IsEmpty()) {
+ CopyUTF8toUTF16(*replacement, aURI);
+ }
+ return NS_OK;
+}
+
+nsresult PersistNodeFixup::FixupSrcSet(nsINode* aNode) {
+ dom::Element* element = aNode->AsElement();
+ nsAutoString originalSrcSet;
+ if (!element->GetAttr(nsGkAtoms::srcset, originalSrcSet)) {
+ return NS_OK;
+ }
+ nsAutoString newSrcSet;
+ bool first = true;
+ auto eachCandidate = [&](dom::ResponsiveImageCandidate&& aCandidate) {
+ if (!aCandidate.IsValid()) {
+ return;
+ }
+ if (!first) {
+ newSrcSet.AppendLiteral(", ");
+ }
+ first = false;
+ nsAutoString uri(aCandidate.URLString());
+ FixupURI(uri);
+ newSrcSet.Append(uri);
+ aCandidate.AppendDescriptors(newSrcSet);
+ };
+ dom::ResponsiveImageSelector::ParseSourceSet(originalSrcSet, eachCandidate);
+ element->SetAttr(nsGkAtoms::srcset, newSrcSet, IgnoreErrors());
+ return NS_OK;
+}
+
+nsresult PersistNodeFixup::FixupAttribute(nsINode* aNode,
+ const char* aAttribute,
+ const char* aNamespaceURI) {
+ MOZ_ASSERT(aNode->IsElement());
+ dom::Element* element = aNode->AsElement();
+
+ RefPtr<nsDOMAttributeMap> attrMap = element->Attributes();
+
+ NS_ConvertASCIItoUTF16 attribute(aAttribute);
+ NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI);
+ RefPtr<dom::Attr> attr = attrMap->GetNamedItemNS(namespaceURI, attribute);
+ nsresult rv = NS_OK;
+ if (attr) {
+ nsString uri;
+ attr->GetValue(uri);
+ rv = FixupURI(uri);
+ if (NS_SUCCEEDED(rv)) {
+ attr->SetValue(uri, IgnoreErrors());
+ }
+ }
+
+ return rv;
+}
+
+nsresult PersistNodeFixup::FixupAnchor(nsINode* aNode) {
+ if (IsFlagSet(IWBP::PERSIST_FLAGS_DONT_FIXUP_LINKS)) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aNode->IsElement());
+ dom::Element* element = aNode->AsElement();
+
+ RefPtr<nsDOMAttributeMap> attrMap = element->Attributes();
+
+ // Make all anchor links absolute so they point off onto the Internet
+ nsString attribute(u"href"_ns);
+ RefPtr<dom::Attr> attr = attrMap->GetNamedItem(attribute);
+ if (attr) {
+ nsString oldValue;
+ attr->GetValue(oldValue);
+ NS_ConvertUTF16toUTF8 oldCValue(oldValue);
+
+ // Skip empty values and self-referencing bookmarks
+ if (oldCValue.IsEmpty() || oldCValue.CharAt(0) == '#') {
+ return NS_OK;
+ }
+
+ // if saving file to same location, we don't need to do any fixup
+ bool isEqual;
+ if (mTargetBaseURI &&
+ NS_SUCCEEDED(mCurrentBaseURI->Equals(mTargetBaseURI, &isEqual)) &&
+ isEqual) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> relativeURI;
+ relativeURI = IsFlagSet(IWBP::PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION)
+ ? mTargetBaseURI
+ : mCurrentBaseURI;
+ // Make a new URI to replace the current one
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(newURI), oldCValue,
+ mParent->GetCharacterSet(), relativeURI);
+ if (NS_SUCCEEDED(rv) && newURI) {
+ Unused << NS_MutateURI(newURI).SetUserPass(""_ns).Finalize(newURI);
+ nsAutoCString uriSpec;
+ rv = newURI->GetSpec(uriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ attr->SetValue(NS_ConvertUTF8toUTF16(uriSpec), IgnoreErrors());
+ }
+ }
+
+ return NS_OK;
+}
+
+static void AppendXMLAttr(const nsAString& key, const nsAString& aValue,
+ nsAString& aBuffer) {
+ if (!aBuffer.IsEmpty()) {
+ aBuffer.Append(' ');
+ }
+ aBuffer.Append(key);
+ aBuffer.AppendLiteral(R"(=")");
+ for (size_t i = 0; i < aValue.Length(); ++i) {
+ switch (aValue[i]) {
+ case '&':
+ aBuffer.AppendLiteral("&amp;");
+ break;
+ case '<':
+ aBuffer.AppendLiteral("&lt;");
+ break;
+ case '>':
+ aBuffer.AppendLiteral("&gt;");
+ break;
+ case '"':
+ aBuffer.AppendLiteral("&quot;");
+ break;
+ default:
+ aBuffer.Append(aValue[i]);
+ break;
+ }
+ }
+ aBuffer.Append('"');
+}
+
+nsresult PersistNodeFixup::FixupXMLStyleSheetLink(
+ dom::ProcessingInstruction* aPI, const nsAString& aHref) {
+ NS_ENSURE_ARG_POINTER(aPI);
+
+ nsAutoString data;
+ aPI->GetData(data);
+
+ nsAutoString href;
+ nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::href, href);
+
+ // Construct and set a new data value for the xml-stylesheet
+ if (!aHref.IsEmpty() && !href.IsEmpty()) {
+ nsAutoString alternate;
+ nsAutoString charset;
+ nsAutoString title;
+ nsAutoString type;
+ nsAutoString media;
+
+ nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::alternate,
+ alternate);
+ nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::charset, charset);
+ nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::title, title);
+ nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::type, type);
+ nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::media, media);
+
+ nsAutoString newData;
+ AppendXMLAttr(u"href"_ns, aHref, newData);
+ if (!title.IsEmpty()) {
+ AppendXMLAttr(u"title"_ns, title, newData);
+ }
+ if (!media.IsEmpty()) {
+ AppendXMLAttr(u"media"_ns, media, newData);
+ }
+ if (!type.IsEmpty()) {
+ AppendXMLAttr(u"type"_ns, type, newData);
+ }
+ if (!charset.IsEmpty()) {
+ AppendXMLAttr(u"charset"_ns, charset, newData);
+ }
+ if (!alternate.IsEmpty()) {
+ AppendXMLAttr(u"alternate"_ns, alternate, newData);
+ }
+ aPI->SetData(newData, IgnoreErrors());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PersistNodeFixup::FixupNode(nsINode* aNodeIn, bool* aSerializeCloneKids,
+ nsINode** aNodeOut) {
+ *aNodeOut = nullptr;
+ *aSerializeCloneKids = false;
+
+ uint16_t type = aNodeIn->NodeType();
+ if (type != nsINode::ELEMENT_NODE &&
+ type != nsINode::PROCESSING_INSTRUCTION_NODE) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aNodeIn->IsContent());
+
+ // Fixup xml-stylesheet processing instructions
+ if (auto nodeAsPI = dom::ProcessingInstruction::FromNode(aNodeIn)) {
+ nsAutoString target;
+ nodeAsPI->GetTarget(target);
+ if (target.EqualsLiteral("xml-stylesheet")) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ MOZ_ASSERT((*aNodeOut)->IsProcessingInstruction());
+ auto nodeAsPI = static_cast<dom::ProcessingInstruction*>(*aNodeOut);
+ nsAutoString href;
+ GetXMLStyleSheetLink(nodeAsPI, href);
+ if (!href.IsEmpty()) {
+ FixupURI(href);
+ FixupXMLStyleSheetLink(nodeAsPI, href);
+ }
+ }
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aNodeIn);
+ if (!content) {
+ return NS_OK;
+ }
+
+ // BASE elements are replaced by a comment so relative links are not hosed.
+ if (!IsFlagSet(IWBP::PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS) &&
+ content->IsHTMLElement(nsGkAtoms::base)) {
+ // Base uses HTMLSharedElement, which would be awkward to implement
+ // FromContent on, since it represents multiple elements. Since we've
+ // already checked IsHTMLElement here, just cast as we were doing.
+ auto* base = static_cast<dom::HTMLSharedElement*>(content.get());
+ dom::Document* ownerDoc = base->OwnerDoc();
+
+ nsAutoString href;
+ base->GetHref(href); // Doesn't matter if this fails
+ nsAutoString commentText;
+ commentText.AssignLiteral(" base ");
+ if (!href.IsEmpty()) {
+ commentText += u"href=\""_ns + href + u"\" "_ns;
+ }
+ *aNodeOut = ownerDoc->CreateComment(commentText).take();
+ return NS_OK;
+ }
+
+ // Fix up href and file links in the elements
+ RefPtr<dom::HTMLAnchorElement> nodeAsAnchor =
+ dom::HTMLAnchorElement::FromNode(content);
+ if (nodeAsAnchor) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAnchor(*aNodeOut);
+ }
+ return rv;
+ }
+
+ RefPtr<dom::HTMLAreaElement> nodeAsArea =
+ dom::HTMLAreaElement::FromNode(content);
+ if (nodeAsArea) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAnchor(*aNodeOut);
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::body)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "background");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::table)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "background");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::tr)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "background");
+ }
+ return rv;
+ }
+
+ if (content->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "background");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::img)) {
+ MOZ_TRY(GetNodeToFixup(aNodeIn, aNodeOut));
+ if (!*aNodeOut) {
+ return NS_OK;
+ }
+
+ // Disable image loads
+ nsCOMPtr<nsIImageLoadingContent> imgCon = do_QueryInterface(*aNodeOut);
+ if (imgCon) {
+ imgCon->SetLoadingEnabled(false);
+ }
+ // FIXME(emilio): Why fixing up <img href>? Looks bogus
+ FixupAnchor(*aNodeOut);
+ FixupAttribute(*aNodeOut, "src");
+ FixupSrcSet(*aNodeOut);
+ return NS_OK;
+ }
+
+ if (content->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::source)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ FixupSrcSet(*aNodeOut);
+ }
+ return rv;
+ }
+
+ if (content->IsSVGElement(nsGkAtoms::image)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ // Disable image loads
+ nsCOMPtr<nsIImageLoadingContent> imgCon = do_QueryInterface(*aNodeOut);
+ if (imgCon) imgCon->SetLoadingEnabled(false);
+
+ // FixupAnchor(*aNodeOut); // XXXjwatt: is this line needed?
+ FixupAttribute(*aNodeOut, "href");
+ FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::script)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ if (content->IsSVGElement(nsGkAtoms::script)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "href");
+ FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::embed)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::object)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "data");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::link)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ // First see if the link represents linked content
+ rv = FixupAttribute(*aNodeOut, "href");
+ if (NS_FAILED(rv)) {
+ // Perhaps this link is actually an anchor to related content
+ FixupAnchor(*aNodeOut);
+ }
+ // TODO if "type" attribute == "text/css"
+ // fixup stylesheet
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::frame)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::iframe)) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ FixupAttribute(*aNodeOut, "src");
+ }
+ return rv;
+ }
+
+ RefPtr<dom::HTMLInputElement> nodeAsInput =
+ dom::HTMLInputElement::FromNodeOrNull(content);
+ if (nodeAsInput) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ // Disable image loads
+ nsCOMPtr<nsIImageLoadingContent> imgCon = do_QueryInterface(*aNodeOut);
+ if (imgCon) {
+ imgCon->SetLoadingEnabled(false);
+ }
+
+ FixupAttribute(*aNodeOut, "src");
+
+ nsAutoString valueStr;
+ constexpr auto valueAttr = u"value"_ns;
+ // Update element node attributes with user-entered form state
+ RefPtr<dom::HTMLInputElement> outElt =
+ dom::HTMLInputElement::FromNode((*aNodeOut)->AsContent());
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(*aNodeOut);
+ switch (formControl->ControlType()) {
+ case FormControlType::InputEmail:
+ case FormControlType::InputSearch:
+ case FormControlType::InputText:
+ case FormControlType::InputTel:
+ case FormControlType::InputUrl:
+ case FormControlType::InputNumber:
+ case FormControlType::InputRange:
+ case FormControlType::InputDate:
+ case FormControlType::InputTime:
+ case FormControlType::InputColor:
+ nodeAsInput->GetValue(valueStr, dom::CallerType::System);
+ // Avoid superfluous value="" serialization
+ if (valueStr.IsEmpty()) {
+ outElt->RemoveAttribute(valueAttr, IgnoreErrors());
+ } else {
+ outElt->SetAttribute(valueAttr, valueStr, IgnoreErrors());
+ }
+ break;
+ case FormControlType::InputCheckbox:
+ case FormControlType::InputRadio:
+ outElt->SetDefaultChecked(nodeAsInput->Checked(), IgnoreErrors());
+ break;
+ default:
+ break;
+ }
+ }
+ return rv;
+ }
+
+ dom::HTMLTextAreaElement* nodeAsTextArea =
+ dom::HTMLTextAreaElement::FromNode(content);
+ if (nodeAsTextArea) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ // Tell the document encoder to serialize the text child we create below
+ *aSerializeCloneKids = true;
+
+ nsAutoString valueStr;
+ nodeAsTextArea->GetValue(valueStr);
+
+ (*aNodeOut)->SetTextContent(valueStr, IgnoreErrors());
+ }
+ return rv;
+ }
+
+ dom::HTMLOptionElement* nodeAsOption =
+ dom::HTMLOptionElement::FromNode(content);
+ if (nodeAsOption) {
+ nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
+ if (NS_SUCCEEDED(rv) && *aNodeOut) {
+ dom::HTMLOptionElement* outElt =
+ dom::HTMLOptionElement::FromNode((*aNodeOut)->AsContent());
+ bool selected = nodeAsOption->Selected();
+ outElt->SetDefaultSelected(selected, IgnoreErrors());
+ }
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+} // unnamed namespace
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::ReadResources(
+ nsIWebBrowserPersistResourceVisitor* aVisitor) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIWebBrowserPersistResourceVisitor> visitor = aVisitor;
+
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
+
+ ErrorResult err;
+ RefPtr<dom::TreeWalker> walker = mDocument->CreateTreeWalker(
+ *mDocument,
+ dom::NodeFilter_Binding::SHOW_ELEMENT |
+ dom::NodeFilter_Binding::SHOW_DOCUMENT |
+ dom::NodeFilter_Binding::SHOW_PROCESSING_INSTRUCTION,
+ nullptr, err);
+
+ if (NS_WARN_IF(err.Failed())) {
+ return err.StealNSResult();
+ }
+ MOZ_ASSERT(walker);
+
+ RefPtr<ResourceReader> reader = new ResourceReader(this, aVisitor);
+ nsCOMPtr<nsINode> currentNode = walker->CurrentNode();
+ do {
+ rv = reader->OnWalkDOMNode(currentNode);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+
+ ErrorResult err;
+ currentNode = walker->NextNode(err);
+ if (NS_WARN_IF(err.Failed())) {
+ err.SuppressException();
+ break;
+ }
+ } while (currentNode);
+ reader->DocumentDone(rv);
+ // If NS_FAILED(rv), it was / will be reported by an EndVisit call
+ // via DocumentDone. This method must return a failure if and
+ // only if visitor won't be invoked.
+ return NS_OK;
+}
+
+static uint32_t ConvertEncoderFlags(uint32_t aEncoderFlags) {
+ uint32_t encoderFlags = 0;
+
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_SELECTION_ONLY)
+ encoderFlags |= nsIDocumentEncoder::OutputSelectionOnly;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_FORMATTED)
+ encoderFlags |= nsIDocumentEncoder::OutputFormatted;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_RAW)
+ encoderFlags |= nsIDocumentEncoder::OutputRaw;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_BODY_ONLY)
+ encoderFlags |= nsIDocumentEncoder::OutputBodyOnly;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_PREFORMATTED)
+ encoderFlags |= nsIDocumentEncoder::OutputPreformatted;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_WRAP)
+ encoderFlags |= nsIDocumentEncoder::OutputWrap;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_FORMAT_FLOWED)
+ encoderFlags |= nsIDocumentEncoder::OutputFormatFlowed;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ABSOLUTE_LINKS)
+ encoderFlags |= nsIDocumentEncoder::OutputAbsoluteLinks;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_BASIC_ENTITIES)
+ encoderFlags |= nsIDocumentEncoder::OutputEncodeBasicEntities;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_CR_LINEBREAKS)
+ encoderFlags |= nsIDocumentEncoder::OutputCRLineBreak;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_LF_LINEBREAKS)
+ encoderFlags |= nsIDocumentEncoder::OutputLFLineBreak;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_NOSCRIPT_CONTENT)
+ encoderFlags |= nsIDocumentEncoder::OutputNoScriptContent;
+ if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_NOFRAMES_CONTENT)
+ encoderFlags |= nsIDocumentEncoder::OutputNoFramesContent;
+
+ return encoderFlags;
+}
+
+static bool ContentTypeEncoderExists(const nsACString& aType) {
+ return do_getDocumentTypeSupportedForEncoding(
+ PromiseFlatCString(aType).get());
+}
+
+void WebBrowserPersistLocalDocument::DecideContentType(
+ nsACString& aContentType) {
+ if (aContentType.IsEmpty()) {
+ if (NS_WARN_IF(NS_FAILED(GetContentType(aContentType)))) {
+ aContentType.Truncate();
+ }
+ }
+ if (!aContentType.IsEmpty() && !ContentTypeEncoderExists(aContentType)) {
+ aContentType.Truncate();
+ }
+ if (aContentType.IsEmpty()) {
+ aContentType.AssignLiteral("text/html");
+ }
+}
+
+nsresult WebBrowserPersistLocalDocument::GetDocEncoder(
+ const nsACString& aContentType, uint32_t aEncoderFlags,
+ nsIDocumentEncoder** aEncoder) {
+ nsCOMPtr<nsIDocumentEncoder> encoder =
+ do_createDocumentEncoder(PromiseFlatCString(aContentType).get());
+ NS_ENSURE_TRUE(encoder, NS_ERROR_FAILURE);
+
+ nsresult rv =
+ encoder->NativeInit(mDocument, NS_ConvertASCIItoUTF16(aContentType),
+ ConvertEncoderFlags(aEncoderFlags));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ nsAutoCString charSet;
+ rv = GetCharacterSet(charSet);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ rv = encoder->SetCharset(charSet);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ encoder.forget(aEncoder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::WriteContent(
+ nsIOutputStream* aStream, nsIWebBrowserPersistURIMap* aMap,
+ const nsACString& aRequestedContentType, uint32_t aEncoderFlags,
+ uint32_t aWrapColumn, nsIWebBrowserPersistWriteCompletion* aCompletion) {
+ NS_ENSURE_ARG_POINTER(aStream);
+ NS_ENSURE_ARG_POINTER(aCompletion);
+ nsAutoCString contentType(aRequestedContentType);
+ DecideContentType(contentType);
+
+ nsCOMPtr<nsIDocumentEncoder> encoder;
+ nsresult rv =
+ GetDocEncoder(contentType, aEncoderFlags, getter_AddRefs(encoder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aWrapColumn != 0 &&
+ (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_WRAP)) {
+ encoder->SetWrapColumn(aWrapColumn);
+ }
+
+ nsCOMPtr<nsIURI> targetURI;
+ if (aMap) {
+ nsAutoCString targetURISpec;
+ rv = aMap->GetTargetBaseURI(targetURISpec);
+ if (NS_SUCCEEDED(rv) && !targetURISpec.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(targetURI), targetURISpec);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+ } else if (mPersistFlags &
+ nsIWebBrowserPersist::PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ rv = encoder->SetNodeFixup(new PersistNodeFixup(this, aMap, targetURI));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ rv = encoder->EncodeToStream(aStream);
+ aCompletion->OnFinish(this, aStream, contentType, rv);
+ return NS_OK;
+}
+
+} // namespace mozilla