Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / webbrowserpersist / WebBrowserPersistLocalDocument.cpp
blob78641582ceee0dae475de5a2861d341b14ae49a2
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "WebBrowserPersistLocalDocument.h"
7 #include "WebBrowserPersistDocumentParent.h"
9 #include "mozilla/dom/Attr.h"
10 #include "mozilla/dom/BrowsingContext.h"
11 #include "mozilla/dom/Comment.h"
12 #include "mozilla/dom/Element.h"
13 #include "mozilla/dom/HTMLAnchorElement.h"
14 #include "mozilla/dom/HTMLAreaElement.h"
15 #include "mozilla/dom/HTMLImageElement.h"
16 #include "mozilla/dom/HTMLInputElement.h"
17 #include "mozilla/dom/HTMLLinkElement.h"
18 #include "mozilla/dom/HTMLObjectElement.h"
19 #include "mozilla/dom/HTMLOptionElement.h"
20 #include "mozilla/dom/HTMLSharedElement.h"
21 #include "mozilla/dom/HTMLTextAreaElement.h"
22 #include "mozilla/dom/NodeFilterBinding.h"
23 #include "mozilla/dom/ProcessingInstruction.h"
24 #include "mozilla/dom/ResponsiveImageSelector.h"
25 #include "mozilla/dom/BrowserParent.h"
26 #include "mozilla/dom/TreeWalker.h"
27 #include "mozilla/Encoding.h"
28 #include "mozilla/Try.h"
29 #include "mozilla/Unused.h"
30 #include "nsComponentManagerUtils.h"
31 #include "nsContentUtils.h"
32 #include "nsCycleCollectionParticipant.h"
33 #include "nsDOMAttributeMap.h"
34 #include "nsFrameLoader.h"
35 #include "nsGlobalWindowOuter.h"
36 #include "nsIContent.h"
37 #include "nsICookieJarSettings.h"
38 #include "nsIDOMWindowUtils.h"
39 #include "mozilla/dom/Document.h"
40 #include "nsIDocumentEncoder.h"
41 #include "nsILoadContext.h"
42 #include "nsIProtocolHandler.h"
43 #include "nsISHEntry.h"
44 #include "nsIURIMutator.h"
45 #include "nsIWebBrowserPersist.h"
46 #include "nsIWebNavigation.h"
47 #include "nsIWebPageDescriptor.h"
48 #include "nsNetUtil.h"
49 #include "nsQueryObject.h"
51 namespace mozilla {
53 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebBrowserPersistLocalDocument)
54 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebBrowserPersistLocalDocument)
56 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebBrowserPersistLocalDocument)
57 NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersistDocument)
58 NS_INTERFACE_MAP_ENTRY(nsISupports)
59 NS_INTERFACE_MAP_END
61 NS_IMPL_CYCLE_COLLECTION(WebBrowserPersistLocalDocument, mDocument)
63 WebBrowserPersistLocalDocument::WebBrowserPersistLocalDocument(
64 dom::Document* aDocument)
65 : mDocument(aDocument), mPersistFlags(0) {
66 MOZ_ASSERT(mDocument);
69 WebBrowserPersistLocalDocument::~WebBrowserPersistLocalDocument() = default;
71 NS_IMETHODIMP
72 WebBrowserPersistLocalDocument::SetPersistFlags(uint32_t aFlags) {
73 mPersistFlags = aFlags;
74 return NS_OK;
77 NS_IMETHODIMP
78 WebBrowserPersistLocalDocument::GetPersistFlags(uint32_t* aFlags) {
79 *aFlags = mPersistFlags;
80 return NS_OK;
83 NS_IMETHODIMP
84 WebBrowserPersistLocalDocument::GetIsClosed(bool* aIsClosed) {
85 *aIsClosed = false;
86 return NS_OK;
89 NS_IMETHODIMP
90 WebBrowserPersistLocalDocument::GetIsPrivate(bool* aIsPrivate) {
91 nsCOMPtr<nsILoadContext> privacyContext = mDocument->GetLoadContext();
92 *aIsPrivate = privacyContext && privacyContext->UsePrivateBrowsing();
93 return NS_OK;
96 NS_IMETHODIMP
97 WebBrowserPersistLocalDocument::GetDocumentURI(nsACString& aURISpec) {
98 nsCOMPtr<nsIURI> uri = mDocument->GetDocumentURI();
99 if (!uri) {
100 return NS_ERROR_UNEXPECTED;
102 return uri->GetSpec(aURISpec);
105 NS_IMETHODIMP
106 WebBrowserPersistLocalDocument::GetBaseURI(nsACString& aURISpec) {
107 nsCOMPtr<nsIURI> uri = GetBaseURI();
108 if (!uri) {
109 return NS_ERROR_UNEXPECTED;
111 return uri->GetSpec(aURISpec);
114 NS_IMETHODIMP
115 WebBrowserPersistLocalDocument::GetContentType(nsACString& aContentType) {
116 nsAutoString utf16Type;
117 mDocument->GetContentType(utf16Type);
118 CopyUTF16toUTF8(utf16Type, aContentType);
119 return NS_OK;
122 NS_IMETHODIMP
123 WebBrowserPersistLocalDocument::GetCharacterSet(nsACString& aCharSet) {
124 GetCharacterSet()->Name(aCharSet);
125 return NS_OK;
128 NS_IMETHODIMP
129 WebBrowserPersistLocalDocument::GetTitle(nsAString& aTitle) {
130 nsAutoString titleBuffer;
131 mDocument->GetTitle(titleBuffer);
132 aTitle = titleBuffer;
133 return NS_OK;
136 NS_IMETHODIMP
137 WebBrowserPersistLocalDocument::GetReferrerInfo(
138 nsIReferrerInfo** aReferrerInfo) {
139 *aReferrerInfo = mDocument->GetReferrerInfo();
140 NS_IF_ADDREF(*aReferrerInfo);
141 return NS_OK;
144 NS_IMETHODIMP
145 WebBrowserPersistLocalDocument::GetCookieJarSettings(
146 nsICookieJarSettings** aCookieJarSettings) {
147 *aCookieJarSettings = mDocument->CookieJarSettings();
148 NS_ADDREF(*aCookieJarSettings);
149 return NS_OK;
152 NS_IMETHODIMP
153 WebBrowserPersistLocalDocument::GetContentDisposition(nsAString& aCD) {
154 nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
155 if (NS_WARN_IF(!window)) {
156 aCD.SetIsVoid(true);
157 return NS_OK;
159 nsCOMPtr<nsIDOMWindowUtils> utils =
160 nsGlobalWindowOuter::Cast(window)->WindowUtils();
161 nsresult rv = utils->GetDocumentMetadata(u"content-disposition"_ns, aCD);
162 if (NS_WARN_IF(NS_FAILED(rv))) {
163 aCD.SetIsVoid(true);
165 return NS_OK;
168 NS_IMETHODIMP
169 WebBrowserPersistLocalDocument::GetCacheKey(uint32_t* aKey) {
170 Maybe<uint32_t> cacheKey;
172 if (nsDocShell* docShell = nsDocShell::Cast(mDocument->GetDocShell())) {
173 cacheKey = docShell->GetCacheKeyFromCurrentEntry();
175 *aKey = cacheKey.valueOr(0);
177 return NS_OK;
180 NS_IMETHODIMP
181 WebBrowserPersistLocalDocument::GetPostData(nsIInputStream** aStream) {
182 nsCOMPtr<nsIInputStream> postData;
183 if (nsDocShell* docShell = nsDocShell::Cast(mDocument->GetDocShell())) {
184 postData = docShell->GetPostDataFromCurrentEntry();
187 postData.forget(aStream);
188 return NS_OK;
191 NS_IMETHODIMP
192 WebBrowserPersistLocalDocument::GetPrincipal(nsIPrincipal** aPrincipal) {
193 nsCOMPtr<nsIPrincipal> nodePrincipal = mDocument->NodePrincipal();
194 nodePrincipal.forget(aPrincipal);
195 return NS_OK;
198 already_AddRefed<nsISHEntry> WebBrowserPersistLocalDocument::GetHistory() {
199 nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
200 if (NS_WARN_IF(!window)) {
201 return nullptr;
203 nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(window);
204 if (NS_WARN_IF(!webNav)) {
205 return nullptr;
207 nsCOMPtr<nsIWebPageDescriptor> desc = do_QueryInterface(webNav);
208 if (NS_WARN_IF(!desc)) {
209 return nullptr;
211 nsCOMPtr<nsISupports> curDesc;
212 nsresult rv = desc->GetCurrentDescriptor(getter_AddRefs(curDesc));
213 // This can fail if, e.g., the document is a Print Preview.
214 if (NS_FAILED(rv) || NS_WARN_IF(!curDesc)) {
215 return nullptr;
217 nsCOMPtr<nsISHEntry> history = do_QueryInterface(curDesc);
218 return history.forget();
221 NotNull<const Encoding*> WebBrowserPersistLocalDocument::GetCharacterSet()
222 const {
223 return mDocument->GetDocumentCharacterSet();
226 uint32_t WebBrowserPersistLocalDocument::GetPersistFlags() const {
227 return mPersistFlags;
230 nsIURI* WebBrowserPersistLocalDocument::GetBaseURI() const {
231 return mDocument->GetBaseURI();
234 namespace {
236 // Helper class for ReadResources().
237 class ResourceReader final : public nsIWebBrowserPersistDocumentReceiver {
238 public:
239 ResourceReader(WebBrowserPersistLocalDocument* aParent,
240 nsIWebBrowserPersistResourceVisitor* aVisitor);
241 nsresult OnWalkDOMNode(nsINode* aNode);
243 // This is called both to indicate the end of the document walk
244 // and when a subdocument is (maybe asynchronously) sent to the
245 // visitor. The call to EndVisit needs to happen after both of
246 // those have finished.
247 void DocumentDone(nsresult aStatus);
249 NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER
250 NS_DECL_ISUPPORTS
252 private:
253 RefPtr<WebBrowserPersistLocalDocument> mParent;
254 nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor;
255 nsCOMPtr<nsIURI> mCurrentBaseURI;
256 uint32_t mPersistFlags;
258 // The number of DocumentDone calls after which EndVisit will be
259 // called on the visitor. Counts the main document if it's still
260 // being walked and any outstanding asynchronous subdocument
261 // StartPersistence calls.
262 size_t mOutstandingDocuments;
263 // Collects the status parameters to DocumentDone calls.
264 nsresult mEndStatus;
266 nsresult OnWalkURI(const nsACString& aURISpec,
267 nsContentPolicyType aContentPolicyType);
268 nsresult OnWalkURI(nsIURI* aURI, nsContentPolicyType aContentPolicyType);
269 nsresult OnWalkAttribute(dom::Element* aElement,
270 nsContentPolicyType aContentPolicyType,
271 const char* aAttribute,
272 const char* aNamespaceURI = "");
273 nsresult OnWalkSubframe(nsINode* aNode);
274 nsresult OnWalkSrcSet(dom::Element* aElement);
276 ~ResourceReader();
278 using IWBP = nsIWebBrowserPersist;
281 NS_IMPL_ISUPPORTS(ResourceReader, nsIWebBrowserPersistDocumentReceiver)
283 ResourceReader::ResourceReader(WebBrowserPersistLocalDocument* aParent,
284 nsIWebBrowserPersistResourceVisitor* aVisitor)
285 : mParent(aParent),
286 mVisitor(aVisitor),
287 mCurrentBaseURI(aParent->GetBaseURI()),
288 mPersistFlags(aParent->GetPersistFlags()),
289 mOutstandingDocuments(1),
290 mEndStatus(NS_OK) {
291 MOZ_ASSERT(mCurrentBaseURI);
294 ResourceReader::~ResourceReader() { MOZ_ASSERT(mOutstandingDocuments == 0); }
296 void ResourceReader::DocumentDone(nsresult aStatus) {
297 MOZ_ASSERT(mOutstandingDocuments > 0);
298 if (NS_SUCCEEDED(mEndStatus)) {
299 mEndStatus = aStatus;
301 if (--mOutstandingDocuments == 0) {
302 mVisitor->EndVisit(mParent, mEndStatus);
306 nsresult ResourceReader::OnWalkSubframe(nsINode* aNode) {
307 RefPtr<nsFrameLoaderOwner> loaderOwner = do_QueryObject(aNode);
308 NS_ENSURE_STATE(loaderOwner);
309 RefPtr<nsFrameLoader> loader = loaderOwner->GetFrameLoader();
310 NS_ENSURE_STATE(loader);
312 RefPtr<dom::BrowsingContext> context = loader->GetBrowsingContext();
313 NS_ENSURE_STATE(context);
315 if (loader->IsRemoteFrame()) {
316 mVisitor->VisitBrowsingContext(mParent, context);
317 return NS_OK;
320 ++mOutstandingDocuments;
321 ErrorResult err;
322 loader->StartPersistence(context, this, err);
323 nsresult rv = err.StealNSResult();
324 if (NS_FAILED(rv)) {
325 if (rv == NS_ERROR_NO_CONTENT) {
326 // Just ignore frames with no content document.
327 rv = NS_OK;
329 // StartPersistence won't eventually call this if it failed,
330 // so this does so (to keep mOutstandingDocuments correct).
331 DocumentDone(rv);
333 return rv;
336 NS_IMETHODIMP
337 ResourceReader::OnDocumentReady(nsIWebBrowserPersistDocument* aDocument) {
338 mVisitor->VisitDocument(mParent, aDocument);
339 DocumentDone(NS_OK);
340 return NS_OK;
343 NS_IMETHODIMP
344 ResourceReader::OnError(nsresult aFailure) {
345 DocumentDone(aFailure);
346 return NS_OK;
349 nsresult ResourceReader::OnWalkURI(nsIURI* aURI,
350 nsContentPolicyType aContentPolicyType) {
351 // Test if this URI should be persisted. By default
352 // we should assume the URI is persistable.
353 bool doNotPersistURI;
354 nsresult rv = NS_URIChainHasFlags(
355 aURI, nsIProtocolHandler::URI_NON_PERSISTABLE, &doNotPersistURI);
356 if (NS_SUCCEEDED(rv) && doNotPersistURI) {
357 return NS_OK;
360 nsAutoCString stringURI;
361 rv = aURI->GetSpec(stringURI);
362 NS_ENSURE_SUCCESS(rv, rv);
363 return mVisitor->VisitResource(mParent, stringURI, aContentPolicyType);
366 nsresult ResourceReader::OnWalkURI(const nsACString& aURISpec,
367 nsContentPolicyType aContentPolicyType) {
368 nsresult rv;
369 nsCOMPtr<nsIURI> uri;
371 rv = NS_NewURI(getter_AddRefs(uri), aURISpec, mParent->GetCharacterSet(),
372 mCurrentBaseURI);
373 if (NS_FAILED(rv)) {
374 // We don't want to break saving a page in case of a malformed URI.
375 return NS_OK;
377 return OnWalkURI(uri, aContentPolicyType);
380 static void ExtractAttribute(dom::Element* aElement, const char* aAttribute,
381 const char* aNamespaceURI, nsCString& aValue) {
382 // Find the named URI attribute on the (element) node and store
383 // a reference to the URI that maps onto a local file name
385 RefPtr<nsDOMAttributeMap> attrMap = aElement->Attributes();
387 NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI);
388 NS_ConvertASCIItoUTF16 attribute(aAttribute);
389 RefPtr<dom::Attr> attr = attrMap->GetNamedItemNS(namespaceURI, attribute);
390 if (attr) {
391 nsAutoString value;
392 attr->GetValue(value);
393 CopyUTF16toUTF8(value, aValue);
394 } else {
395 aValue.Truncate();
399 nsresult ResourceReader::OnWalkAttribute(dom::Element* aElement,
400 nsContentPolicyType aContentPolicyType,
401 const char* aAttribute,
402 const char* aNamespaceURI) {
403 nsAutoCString uriSpec;
404 ExtractAttribute(aElement, aAttribute, aNamespaceURI, uriSpec);
405 if (uriSpec.IsEmpty()) {
406 return NS_OK;
408 return OnWalkURI(uriSpec, aContentPolicyType);
411 nsresult ResourceReader::OnWalkSrcSet(dom::Element* aElement) {
412 nsAutoString srcSet;
413 if (!aElement->GetAttr(nsGkAtoms::srcset, srcSet)) {
414 return NS_OK;
417 nsresult rv = NS_OK;
418 auto eachCandidate = [&](dom::ResponsiveImageCandidate&& aCandidate) {
419 if (!aCandidate.IsValid() || NS_FAILED(rv)) {
420 return;
422 rv = OnWalkURI(NS_ConvertUTF16toUTF8(aCandidate.URLString()),
423 nsIContentPolicy::TYPE_IMAGE);
425 dom::ResponsiveImageSelector::ParseSourceSet(srcSet, eachCandidate);
426 return rv;
429 static nsresult GetXMLStyleSheetLink(dom::ProcessingInstruction* aPI,
430 nsAString& aHref) {
431 nsAutoString data;
432 aPI->GetData(data);
434 nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::href, aHref);
435 return NS_OK;
438 nsresult ResourceReader::OnWalkDOMNode(nsINode* aNode) {
439 // Fixup xml-stylesheet processing instructions
440 if (auto nodeAsPI = dom::ProcessingInstruction::FromNode(aNode)) {
441 nsAutoString target;
442 nodeAsPI->GetTarget(target);
443 if (target.EqualsLiteral("xml-stylesheet")) {
444 nsAutoString href;
445 GetXMLStyleSheetLink(nodeAsPI, href);
446 if (!href.IsEmpty()) {
447 return OnWalkURI(NS_ConvertUTF16toUTF8(href),
448 nsIContentPolicy::TYPE_STYLESHEET);
451 return NS_OK;
454 // Test the node to see if it's an image, frame, iframe, css, js
455 if (auto* img = dom::HTMLImageElement::FromNode(*aNode)) {
456 MOZ_TRY(OnWalkAttribute(img, nsIContentPolicy::TYPE_IMAGE, "src"));
457 MOZ_TRY(OnWalkSrcSet(img));
458 return NS_OK;
461 if (aNode->IsSVGElement(nsGkAtoms::image)) {
462 MOZ_TRY(OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
463 "href"));
464 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
465 "href", "http://www.w3.org/1999/xlink");
468 if (aNode->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video)) {
469 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_MEDIA,
470 "src");
473 if (aNode->IsHTMLElement(nsGkAtoms::source)) {
474 MOZ_TRY(OnWalkSrcSet(aNode->AsElement()));
475 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_MEDIA,
476 "src");
479 if (aNode->IsHTMLElement(nsGkAtoms::body)) {
480 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
481 "background");
484 if (aNode->IsHTMLElement(nsGkAtoms::table)) {
485 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
486 "background");
489 if (aNode->IsHTMLElement(nsGkAtoms::tr)) {
490 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
491 "background");
494 if (aNode->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) {
495 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
496 "background");
499 if (aNode->IsHTMLElement(nsGkAtoms::script)) {
500 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_SCRIPT,
501 "src");
504 if (aNode->IsSVGElement(nsGkAtoms::script)) {
505 MOZ_TRY(OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_SCRIPT,
506 "href"));
507 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_SCRIPT,
508 "href", "http://www.w3.org/1999/xlink");
511 if (aNode->IsHTMLElement(nsGkAtoms::embed)) {
512 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_OBJECT,
513 "src");
516 if (aNode->IsHTMLElement(nsGkAtoms::object)) {
517 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_OBJECT,
518 "data");
521 if (auto nodeAsLink = dom::HTMLLinkElement::FromNode(aNode)) {
522 // Test if the link has a rel value indicating it to be a stylesheet
523 nsAutoString linkRel;
524 nodeAsLink->GetRel(linkRel);
525 if (!linkRel.IsEmpty()) {
526 nsReadingIterator<char16_t> start;
527 nsReadingIterator<char16_t> end;
528 nsReadingIterator<char16_t> current;
530 linkRel.BeginReading(start);
531 linkRel.EndReading(end);
533 // Walk through space delimited string looking for "stylesheet"
534 for (current = start; current != end; ++current) {
535 // Ignore whitespace
536 if (nsCRT::IsAsciiSpace(*current)) {
537 continue;
540 // Grab the next space delimited word
541 nsReadingIterator<char16_t> startWord = current;
542 do {
543 ++current;
544 } while (current != end && !nsCRT::IsAsciiSpace(*current));
546 // Store the link for fix up if it says "stylesheet"
547 if (Substring(startWord, current)
548 .LowerCaseEqualsLiteral("stylesheet")) {
549 OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_STYLESHEET,
550 "href");
551 return NS_OK;
553 if (current == end) {
554 break;
558 return NS_OK;
561 if (aNode->IsHTMLElement(nsGkAtoms::frame)) {
562 return OnWalkSubframe(aNode);
565 if (aNode->IsHTMLElement(nsGkAtoms::iframe) &&
566 !(mPersistFlags & IWBP::PERSIST_FLAGS_IGNORE_IFRAMES)) {
567 return OnWalkSubframe(aNode);
570 auto nodeAsInput = dom::HTMLInputElement::FromNode(aNode);
571 if (nodeAsInput) {
572 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
573 "src");
576 return NS_OK;
579 // Helper class for node rewriting in writeContent().
580 class PersistNodeFixup final : public nsIDocumentEncoderNodeFixup {
581 public:
582 PersistNodeFixup(WebBrowserPersistLocalDocument* aParent,
583 nsIWebBrowserPersistURIMap* aMap, nsIURI* aTargetURI);
585 NS_DECL_ISUPPORTS
586 NS_DECL_NSIDOCUMENTENCODERNODEFIXUP
587 private:
588 virtual ~PersistNodeFixup() = default;
589 RefPtr<WebBrowserPersistLocalDocument> mParent;
590 nsClassHashtable<nsCStringHashKey, nsCString> mMap;
591 nsCOMPtr<nsIURI> mCurrentBaseURI;
592 nsCOMPtr<nsIURI> mTargetBaseURI;
594 bool IsFlagSet(uint32_t aFlag) const {
595 return mParent->GetPersistFlags() & aFlag;
598 nsresult GetNodeToFixup(nsINode* aNodeIn, nsINode** aNodeOut);
599 nsresult FixupURI(nsAString& aURI);
600 nsresult FixupAttribute(nsINode* aNode, const char* aAttribute,
601 const char* aNamespaceURI = "");
602 nsresult FixupAnchor(nsINode* aNode);
603 nsresult FixupXMLStyleSheetLink(dom::ProcessingInstruction* aPI,
604 const nsAString& aHref);
606 nsresult FixupSrcSet(nsINode*);
608 using IWBP = nsIWebBrowserPersist;
611 NS_IMPL_ISUPPORTS(PersistNodeFixup, nsIDocumentEncoderNodeFixup)
613 PersistNodeFixup::PersistNodeFixup(WebBrowserPersistLocalDocument* aParent,
614 nsIWebBrowserPersistURIMap* aMap,
615 nsIURI* aTargetURI)
616 : mParent(aParent),
617 mCurrentBaseURI(aParent->GetBaseURI()),
618 mTargetBaseURI(aTargetURI) {
619 if (aMap) {
620 uint32_t mapSize;
621 nsresult rv = aMap->GetNumMappedURIs(&mapSize);
622 MOZ_ASSERT(NS_SUCCEEDED(rv));
623 NS_ENSURE_SUCCESS_VOID(rv);
624 for (uint32_t i = 0; i < mapSize; ++i) {
625 nsAutoCString urlFrom;
626 auto urlTo = MakeUnique<nsCString>();
628 rv = aMap->GetURIMapping(i, urlFrom, *urlTo);
629 MOZ_ASSERT(NS_SUCCEEDED(rv));
630 if (NS_SUCCEEDED(rv)) {
631 mMap.InsertOrUpdate(urlFrom, std::move(urlTo));
637 nsresult PersistNodeFixup::GetNodeToFixup(nsINode* aNodeIn,
638 nsINode** aNodeOut) {
639 // Avoid mixups in FixupNode that could leak objects; this goes
640 // against the usual out parameter convention, but it's a private
641 // method so shouldn't be a problem.
642 MOZ_ASSERT(!*aNodeOut);
644 if (!IsFlagSet(IWBP::PERSIST_FLAGS_FIXUP_ORIGINAL_DOM)) {
645 ErrorResult rv;
646 *aNodeOut = aNodeIn->CloneNode(false, rv).take();
647 return rv.StealNSResult();
650 NS_ADDREF(*aNodeOut = aNodeIn);
651 return NS_OK;
654 nsresult PersistNodeFixup::FixupURI(nsAString& aURI) {
655 // get the current location of the file (absolutized)
656 nsCOMPtr<nsIURI> uri;
657 nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI, mParent->GetCharacterSet(),
658 mCurrentBaseURI);
659 NS_ENSURE_SUCCESS(rv, rv);
660 nsAutoCString spec;
661 rv = uri->GetSpec(spec);
662 NS_ENSURE_SUCCESS(rv, rv);
664 const nsCString* replacement = mMap.Get(spec);
665 if (!replacement) {
666 // Note that most callers ignore this "failure".
667 return NS_ERROR_FAILURE;
669 if (!replacement->IsEmpty()) {
670 CopyUTF8toUTF16(*replacement, aURI);
672 return NS_OK;
675 nsresult PersistNodeFixup::FixupSrcSet(nsINode* aNode) {
676 dom::Element* element = aNode->AsElement();
677 nsAutoString originalSrcSet;
678 if (!element->GetAttr(nsGkAtoms::srcset, originalSrcSet)) {
679 return NS_OK;
681 nsAutoString newSrcSet;
682 bool first = true;
683 auto eachCandidate = [&](dom::ResponsiveImageCandidate&& aCandidate) {
684 if (!aCandidate.IsValid()) {
685 return;
687 if (!first) {
688 newSrcSet.AppendLiteral(", ");
690 first = false;
691 nsAutoString uri(aCandidate.URLString());
692 FixupURI(uri);
693 newSrcSet.Append(uri);
694 aCandidate.AppendDescriptors(newSrcSet);
696 dom::ResponsiveImageSelector::ParseSourceSet(originalSrcSet, eachCandidate);
697 element->SetAttr(nsGkAtoms::srcset, newSrcSet, IgnoreErrors());
698 return NS_OK;
701 nsresult PersistNodeFixup::FixupAttribute(nsINode* aNode,
702 const char* aAttribute,
703 const char* aNamespaceURI) {
704 MOZ_ASSERT(aNode->IsElement());
705 dom::Element* element = aNode->AsElement();
707 RefPtr<nsDOMAttributeMap> attrMap = element->Attributes();
709 NS_ConvertASCIItoUTF16 attribute(aAttribute);
710 NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI);
711 RefPtr<dom::Attr> attr = attrMap->GetNamedItemNS(namespaceURI, attribute);
712 nsresult rv = NS_OK;
713 if (attr) {
714 nsString uri;
715 attr->GetValue(uri);
716 rv = FixupURI(uri);
717 if (NS_SUCCEEDED(rv)) {
718 attr->SetValue(uri, IgnoreErrors());
722 return rv;
725 nsresult PersistNodeFixup::FixupAnchor(nsINode* aNode) {
726 if (IsFlagSet(IWBP::PERSIST_FLAGS_DONT_FIXUP_LINKS)) {
727 return NS_OK;
730 MOZ_ASSERT(aNode->IsElement());
731 dom::Element* element = aNode->AsElement();
733 RefPtr<nsDOMAttributeMap> attrMap = element->Attributes();
735 // Make all anchor links absolute so they point off onto the Internet
736 nsString attribute(u"href"_ns);
737 RefPtr<dom::Attr> attr = attrMap->GetNamedItem(attribute);
738 if (attr) {
739 nsString oldValue;
740 attr->GetValue(oldValue);
741 NS_ConvertUTF16toUTF8 oldCValue(oldValue);
743 // Skip empty values and self-referencing bookmarks
744 if (oldCValue.IsEmpty() || oldCValue.CharAt(0) == '#') {
745 return NS_OK;
748 // if saving file to same location, we don't need to do any fixup
749 bool isEqual;
750 if (mTargetBaseURI &&
751 NS_SUCCEEDED(mCurrentBaseURI->Equals(mTargetBaseURI, &isEqual)) &&
752 isEqual) {
753 return NS_OK;
756 nsCOMPtr<nsIURI> relativeURI;
757 relativeURI = IsFlagSet(IWBP::PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION)
758 ? mTargetBaseURI
759 : mCurrentBaseURI;
760 // Make a new URI to replace the current one
761 nsCOMPtr<nsIURI> newURI;
762 nsresult rv = NS_NewURI(getter_AddRefs(newURI), oldCValue,
763 mParent->GetCharacterSet(), relativeURI);
764 if (NS_SUCCEEDED(rv) && newURI) {
765 Unused << NS_MutateURI(newURI).SetUserPass(""_ns).Finalize(newURI);
766 nsAutoCString uriSpec;
767 rv = newURI->GetSpec(uriSpec);
768 NS_ENSURE_SUCCESS(rv, rv);
769 attr->SetValue(NS_ConvertUTF8toUTF16(uriSpec), IgnoreErrors());
773 return NS_OK;
776 static void AppendXMLAttr(const nsAString& key, const nsAString& aValue,
777 nsAString& aBuffer) {
778 if (!aBuffer.IsEmpty()) {
779 aBuffer.Append(' ');
781 aBuffer.Append(key);
782 aBuffer.AppendLiteral(R"(=")");
783 for (size_t i = 0; i < aValue.Length(); ++i) {
784 switch (aValue[i]) {
785 case '&':
786 aBuffer.AppendLiteral("&amp;");
787 break;
788 case '<':
789 aBuffer.AppendLiteral("&lt;");
790 break;
791 case '>':
792 aBuffer.AppendLiteral("&gt;");
793 break;
794 case '"':
795 aBuffer.AppendLiteral("&quot;");
796 break;
797 default:
798 aBuffer.Append(aValue[i]);
799 break;
802 aBuffer.Append('"');
805 nsresult PersistNodeFixup::FixupXMLStyleSheetLink(
806 dom::ProcessingInstruction* aPI, const nsAString& aHref) {
807 NS_ENSURE_ARG_POINTER(aPI);
809 nsAutoString data;
810 aPI->GetData(data);
812 nsAutoString href;
813 nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::href, href);
815 // Construct and set a new data value for the xml-stylesheet
816 if (!aHref.IsEmpty() && !href.IsEmpty()) {
817 nsAutoString alternate;
818 nsAutoString charset;
819 nsAutoString title;
820 nsAutoString type;
821 nsAutoString media;
823 nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::alternate,
824 alternate);
825 nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::charset, charset);
826 nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::title, title);
827 nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::type, type);
828 nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::media, media);
830 nsAutoString newData;
831 AppendXMLAttr(u"href"_ns, aHref, newData);
832 if (!title.IsEmpty()) {
833 AppendXMLAttr(u"title"_ns, title, newData);
835 if (!media.IsEmpty()) {
836 AppendXMLAttr(u"media"_ns, media, newData);
838 if (!type.IsEmpty()) {
839 AppendXMLAttr(u"type"_ns, type, newData);
841 if (!charset.IsEmpty()) {
842 AppendXMLAttr(u"charset"_ns, charset, newData);
844 if (!alternate.IsEmpty()) {
845 AppendXMLAttr(u"alternate"_ns, alternate, newData);
847 aPI->SetData(newData, IgnoreErrors());
850 return NS_OK;
853 NS_IMETHODIMP
854 PersistNodeFixup::FixupNode(nsINode* aNodeIn, bool* aSerializeCloneKids,
855 nsINode** aNodeOut) {
856 *aNodeOut = nullptr;
857 *aSerializeCloneKids = false;
859 uint16_t type = aNodeIn->NodeType();
860 if (type != nsINode::ELEMENT_NODE &&
861 type != nsINode::PROCESSING_INSTRUCTION_NODE) {
862 return NS_OK;
865 MOZ_ASSERT(aNodeIn->IsContent());
867 // Fixup xml-stylesheet processing instructions
868 if (auto nodeAsPI = dom::ProcessingInstruction::FromNode(aNodeIn)) {
869 nsAutoString target;
870 nodeAsPI->GetTarget(target);
871 if (target.EqualsLiteral("xml-stylesheet")) {
872 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
873 if (NS_SUCCEEDED(rv) && *aNodeOut) {
874 MOZ_ASSERT((*aNodeOut)->IsProcessingInstruction());
875 auto nodeAsPI = static_cast<dom::ProcessingInstruction*>(*aNodeOut);
876 nsAutoString href;
877 GetXMLStyleSheetLink(nodeAsPI, href);
878 if (!href.IsEmpty()) {
879 FixupURI(href);
880 FixupXMLStyleSheetLink(nodeAsPI, href);
884 return NS_OK;
887 nsCOMPtr<nsIContent> content = do_QueryInterface(aNodeIn);
888 if (!content) {
889 return NS_OK;
892 // BASE elements are replaced by a comment so relative links are not hosed.
893 if (!IsFlagSet(IWBP::PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS) &&
894 content->IsHTMLElement(nsGkAtoms::base)) {
895 // Base uses HTMLSharedElement, which would be awkward to implement
896 // FromContent on, since it represents multiple elements. Since we've
897 // already checked IsHTMLElement here, just cast as we were doing.
898 auto* base = static_cast<dom::HTMLSharedElement*>(content.get());
899 dom::Document* ownerDoc = base->OwnerDoc();
901 nsAutoString href;
902 base->GetHref(href); // Doesn't matter if this fails
903 nsAutoString commentText;
904 commentText.AssignLiteral(" base ");
905 if (!href.IsEmpty()) {
906 commentText += u"href=\""_ns + href + u"\" "_ns;
908 *aNodeOut = ownerDoc->CreateComment(commentText).take();
909 return NS_OK;
912 // Fix up href and file links in the elements
913 RefPtr<dom::HTMLAnchorElement> nodeAsAnchor =
914 dom::HTMLAnchorElement::FromNode(content);
915 if (nodeAsAnchor) {
916 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
917 if (NS_SUCCEEDED(rv) && *aNodeOut) {
918 FixupAnchor(*aNodeOut);
920 return rv;
923 RefPtr<dom::HTMLAreaElement> nodeAsArea =
924 dom::HTMLAreaElement::FromNode(content);
925 if (nodeAsArea) {
926 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
927 if (NS_SUCCEEDED(rv) && *aNodeOut) {
928 FixupAnchor(*aNodeOut);
930 return rv;
933 if (content->IsHTMLElement(nsGkAtoms::body)) {
934 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
935 if (NS_SUCCEEDED(rv) && *aNodeOut) {
936 FixupAttribute(*aNodeOut, "background");
938 return rv;
941 if (content->IsHTMLElement(nsGkAtoms::table)) {
942 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
943 if (NS_SUCCEEDED(rv) && *aNodeOut) {
944 FixupAttribute(*aNodeOut, "background");
946 return rv;
949 if (content->IsHTMLElement(nsGkAtoms::tr)) {
950 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
951 if (NS_SUCCEEDED(rv) && *aNodeOut) {
952 FixupAttribute(*aNodeOut, "background");
954 return rv;
957 if (content->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) {
958 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
959 if (NS_SUCCEEDED(rv) && *aNodeOut) {
960 FixupAttribute(*aNodeOut, "background");
962 return rv;
965 if (content->IsHTMLElement(nsGkAtoms::img)) {
966 MOZ_TRY(GetNodeToFixup(aNodeIn, aNodeOut));
967 if (!*aNodeOut) {
968 return NS_OK;
971 // Disable image loads
972 nsCOMPtr<nsIImageLoadingContent> imgCon = do_QueryInterface(*aNodeOut);
973 if (imgCon) {
974 imgCon->SetLoadingEnabled(false);
976 // FIXME(emilio): Why fixing up <img href>? Looks bogus
977 FixupAnchor(*aNodeOut);
978 FixupAttribute(*aNodeOut, "src");
979 FixupSrcSet(*aNodeOut);
980 return NS_OK;
983 if (content->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video)) {
984 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
985 if (NS_SUCCEEDED(rv) && *aNodeOut) {
986 FixupAttribute(*aNodeOut, "src");
988 return rv;
991 if (content->IsHTMLElement(nsGkAtoms::source)) {
992 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
993 if (NS_SUCCEEDED(rv) && *aNodeOut) {
994 FixupAttribute(*aNodeOut, "src");
995 FixupSrcSet(*aNodeOut);
997 return rv;
1000 if (content->IsSVGElement(nsGkAtoms::image)) {
1001 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1002 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1003 // Disable image loads
1004 nsCOMPtr<nsIImageLoadingContent> imgCon = do_QueryInterface(*aNodeOut);
1005 if (imgCon) imgCon->SetLoadingEnabled(false);
1007 // FixupAnchor(*aNodeOut); // XXXjwatt: is this line needed?
1008 FixupAttribute(*aNodeOut, "href");
1009 FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink");
1011 return rv;
1014 if (content->IsHTMLElement(nsGkAtoms::script)) {
1015 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1016 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1017 FixupAttribute(*aNodeOut, "src");
1019 return rv;
1022 if (content->IsSVGElement(nsGkAtoms::script)) {
1023 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1024 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1025 FixupAttribute(*aNodeOut, "href");
1026 FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink");
1028 return rv;
1031 if (content->IsHTMLElement(nsGkAtoms::embed)) {
1032 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1033 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1034 FixupAttribute(*aNodeOut, "src");
1036 return rv;
1039 if (content->IsHTMLElement(nsGkAtoms::object)) {
1040 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1041 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1042 FixupAttribute(*aNodeOut, "data");
1044 return rv;
1047 if (content->IsHTMLElement(nsGkAtoms::link)) {
1048 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1049 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1050 // First see if the link represents linked content
1051 rv = FixupAttribute(*aNodeOut, "href");
1052 if (NS_FAILED(rv)) {
1053 // Perhaps this link is actually an anchor to related content
1054 FixupAnchor(*aNodeOut);
1056 // TODO if "type" attribute == "text/css"
1057 // fixup stylesheet
1059 return rv;
1062 if (content->IsHTMLElement(nsGkAtoms::frame)) {
1063 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1064 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1065 FixupAttribute(*aNodeOut, "src");
1067 return rv;
1070 if (content->IsHTMLElement(nsGkAtoms::iframe)) {
1071 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1072 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1073 FixupAttribute(*aNodeOut, "src");
1075 return rv;
1078 RefPtr<dom::HTMLInputElement> nodeAsInput =
1079 dom::HTMLInputElement::FromNodeOrNull(content);
1080 if (nodeAsInput) {
1081 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1082 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1083 // Disable image loads
1084 nsCOMPtr<nsIImageLoadingContent> imgCon = do_QueryInterface(*aNodeOut);
1085 if (imgCon) {
1086 imgCon->SetLoadingEnabled(false);
1089 FixupAttribute(*aNodeOut, "src");
1091 nsAutoString valueStr;
1092 constexpr auto valueAttr = u"value"_ns;
1093 // Update element node attributes with user-entered form state
1094 RefPtr<dom::HTMLInputElement> outElt =
1095 dom::HTMLInputElement::FromNode((*aNodeOut)->AsContent());
1096 nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(*aNodeOut);
1097 switch (formControl->ControlType()) {
1098 case FormControlType::InputEmail:
1099 case FormControlType::InputSearch:
1100 case FormControlType::InputText:
1101 case FormControlType::InputTel:
1102 case FormControlType::InputUrl:
1103 case FormControlType::InputNumber:
1104 case FormControlType::InputRange:
1105 case FormControlType::InputDate:
1106 case FormControlType::InputTime:
1107 case FormControlType::InputColor:
1108 nodeAsInput->GetValue(valueStr, dom::CallerType::System);
1109 // Avoid superfluous value="" serialization
1110 if (valueStr.IsEmpty()) {
1111 outElt->RemoveAttribute(valueAttr, IgnoreErrors());
1112 } else {
1113 outElt->SetAttribute(valueAttr, valueStr, IgnoreErrors());
1115 break;
1116 case FormControlType::InputCheckbox:
1117 case FormControlType::InputRadio:
1118 outElt->SetDefaultChecked(nodeAsInput->Checked(), IgnoreErrors());
1119 break;
1120 default:
1121 break;
1124 return rv;
1127 dom::HTMLTextAreaElement* nodeAsTextArea =
1128 dom::HTMLTextAreaElement::FromNode(content);
1129 if (nodeAsTextArea) {
1130 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1131 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1132 // Tell the document encoder to serialize the text child we create below
1133 *aSerializeCloneKids = true;
1135 nsAutoString valueStr;
1136 nodeAsTextArea->GetValue(valueStr);
1138 (*aNodeOut)->SetTextContent(valueStr, IgnoreErrors());
1140 return rv;
1143 dom::HTMLOptionElement* nodeAsOption =
1144 dom::HTMLOptionElement::FromNode(content);
1145 if (nodeAsOption) {
1146 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1147 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1148 dom::HTMLOptionElement* outElt =
1149 dom::HTMLOptionElement::FromNode((*aNodeOut)->AsContent());
1150 bool selected = nodeAsOption->Selected();
1151 outElt->SetDefaultSelected(selected, IgnoreErrors());
1153 return rv;
1156 return NS_OK;
1159 } // unnamed namespace
1161 NS_IMETHODIMP
1162 WebBrowserPersistLocalDocument::ReadResources(
1163 nsIWebBrowserPersistResourceVisitor* aVisitor) {
1164 nsresult rv = NS_OK;
1165 nsCOMPtr<nsIWebBrowserPersistResourceVisitor> visitor = aVisitor;
1167 NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
1169 ErrorResult err;
1170 RefPtr<dom::TreeWalker> walker = mDocument->CreateTreeWalker(
1171 *mDocument,
1172 dom::NodeFilter_Binding::SHOW_ELEMENT |
1173 dom::NodeFilter_Binding::SHOW_DOCUMENT |
1174 dom::NodeFilter_Binding::SHOW_PROCESSING_INSTRUCTION,
1175 nullptr, err);
1177 if (NS_WARN_IF(err.Failed())) {
1178 return err.StealNSResult();
1180 MOZ_ASSERT(walker);
1182 RefPtr<ResourceReader> reader = new ResourceReader(this, aVisitor);
1183 nsCOMPtr<nsINode> currentNode = walker->CurrentNode();
1184 do {
1185 rv = reader->OnWalkDOMNode(currentNode);
1186 if (NS_WARN_IF(NS_FAILED(rv))) {
1187 break;
1190 ErrorResult err;
1191 currentNode = walker->NextNode(err);
1192 if (NS_WARN_IF(err.Failed())) {
1193 err.SuppressException();
1194 break;
1196 } while (currentNode);
1197 reader->DocumentDone(rv);
1198 // If NS_FAILED(rv), it was / will be reported by an EndVisit call
1199 // via DocumentDone. This method must return a failure if and
1200 // only if visitor won't be invoked.
1201 return NS_OK;
1204 static uint32_t ConvertEncoderFlags(uint32_t aEncoderFlags) {
1205 uint32_t encoderFlags = 0;
1207 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_SELECTION_ONLY)
1208 encoderFlags |= nsIDocumentEncoder::OutputSelectionOnly;
1209 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_FORMATTED)
1210 encoderFlags |= nsIDocumentEncoder::OutputFormatted;
1211 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_RAW)
1212 encoderFlags |= nsIDocumentEncoder::OutputRaw;
1213 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_BODY_ONLY)
1214 encoderFlags |= nsIDocumentEncoder::OutputBodyOnly;
1215 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_PREFORMATTED)
1216 encoderFlags |= nsIDocumentEncoder::OutputPreformatted;
1217 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_WRAP)
1218 encoderFlags |= nsIDocumentEncoder::OutputWrap;
1219 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_FORMAT_FLOWED)
1220 encoderFlags |= nsIDocumentEncoder::OutputFormatFlowed;
1221 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ABSOLUTE_LINKS)
1222 encoderFlags |= nsIDocumentEncoder::OutputAbsoluteLinks;
1223 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_BASIC_ENTITIES)
1224 encoderFlags |= nsIDocumentEncoder::OutputEncodeBasicEntities;
1225 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_CR_LINEBREAKS)
1226 encoderFlags |= nsIDocumentEncoder::OutputCRLineBreak;
1227 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_LF_LINEBREAKS)
1228 encoderFlags |= nsIDocumentEncoder::OutputLFLineBreak;
1229 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_NOSCRIPT_CONTENT)
1230 encoderFlags |= nsIDocumentEncoder::OutputNoScriptContent;
1231 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_NOFRAMES_CONTENT)
1232 encoderFlags |= nsIDocumentEncoder::OutputNoFramesContent;
1234 return encoderFlags;
1237 static bool ContentTypeEncoderExists(const nsACString& aType) {
1238 return do_getDocumentTypeSupportedForEncoding(
1239 PromiseFlatCString(aType).get());
1242 void WebBrowserPersistLocalDocument::DecideContentType(
1243 nsACString& aContentType) {
1244 if (aContentType.IsEmpty()) {
1245 if (NS_WARN_IF(NS_FAILED(GetContentType(aContentType)))) {
1246 aContentType.Truncate();
1249 if (!aContentType.IsEmpty() && !ContentTypeEncoderExists(aContentType)) {
1250 aContentType.Truncate();
1252 if (aContentType.IsEmpty()) {
1253 aContentType.AssignLiteral("text/html");
1257 nsresult WebBrowserPersistLocalDocument::GetDocEncoder(
1258 const nsACString& aContentType, uint32_t aEncoderFlags,
1259 nsIDocumentEncoder** aEncoder) {
1260 nsCOMPtr<nsIDocumentEncoder> encoder =
1261 do_createDocumentEncoder(PromiseFlatCString(aContentType).get());
1262 NS_ENSURE_TRUE(encoder, NS_ERROR_FAILURE);
1264 nsresult rv =
1265 encoder->NativeInit(mDocument, NS_ConvertASCIItoUTF16(aContentType),
1266 ConvertEncoderFlags(aEncoderFlags));
1267 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
1269 nsAutoCString charSet;
1270 rv = GetCharacterSet(charSet);
1271 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
1272 rv = encoder->SetCharset(charSet);
1273 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
1275 encoder.forget(aEncoder);
1276 return NS_OK;
1279 NS_IMETHODIMP
1280 WebBrowserPersistLocalDocument::WriteContent(
1281 nsIOutputStream* aStream, nsIWebBrowserPersistURIMap* aMap,
1282 const nsACString& aRequestedContentType, uint32_t aEncoderFlags,
1283 uint32_t aWrapColumn, nsIWebBrowserPersistWriteCompletion* aCompletion) {
1284 NS_ENSURE_ARG_POINTER(aStream);
1285 NS_ENSURE_ARG_POINTER(aCompletion);
1286 nsAutoCString contentType(aRequestedContentType);
1287 DecideContentType(contentType);
1289 nsCOMPtr<nsIDocumentEncoder> encoder;
1290 nsresult rv =
1291 GetDocEncoder(contentType, aEncoderFlags, getter_AddRefs(encoder));
1292 NS_ENSURE_SUCCESS(rv, rv);
1294 if (aWrapColumn != 0 &&
1295 (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_WRAP)) {
1296 encoder->SetWrapColumn(aWrapColumn);
1299 nsCOMPtr<nsIURI> targetURI;
1300 if (aMap) {
1301 nsAutoCString targetURISpec;
1302 rv = aMap->GetTargetBaseURI(targetURISpec);
1303 if (NS_SUCCEEDED(rv) && !targetURISpec.IsEmpty()) {
1304 rv = NS_NewURI(getter_AddRefs(targetURI), targetURISpec);
1305 NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
1306 } else if (mPersistFlags &
1307 nsIWebBrowserPersist::PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION) {
1308 return NS_ERROR_UNEXPECTED;
1311 rv = encoder->SetNodeFixup(new PersistNodeFixup(this, aMap, targetURI));
1312 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
1314 rv = encoder->EncodeToStream(aStream);
1315 aCompletion->OnFinish(this, aStream, contentType, rv);
1316 return NS_OK;
1319 } // namespace mozilla