no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / webbrowserpersist / WebBrowserPersistLocalDocument.cpp
blobdea0b98023bab1f06fedd60847211423801c384d
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 "nsContentCID.h"
33 #include "nsCycleCollectionParticipant.h"
34 #include "nsDOMAttributeMap.h"
35 #include "nsFrameLoader.h"
36 #include "nsGlobalWindowOuter.h"
37 #include "nsIContent.h"
38 #include "nsICookieJarSettings.h"
39 #include "nsIDOMWindowUtils.h"
40 #include "mozilla/dom/Document.h"
41 #include "nsIDocumentEncoder.h"
42 #include "nsILoadContext.h"
43 #include "nsIProtocolHandler.h"
44 #include "nsISHEntry.h"
45 #include "nsIURIMutator.h"
46 #include "nsIWebBrowserPersist.h"
47 #include "nsIWebNavigation.h"
48 #include "nsIWebPageDescriptor.h"
49 #include "nsNetUtil.h"
50 #include "nsQueryObject.h"
52 namespace mozilla {
54 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebBrowserPersistLocalDocument)
55 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebBrowserPersistLocalDocument)
57 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebBrowserPersistLocalDocument)
58 NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersistDocument)
59 NS_INTERFACE_MAP_ENTRY(nsISupports)
60 NS_INTERFACE_MAP_END
62 NS_IMPL_CYCLE_COLLECTION(WebBrowserPersistLocalDocument, mDocument)
64 WebBrowserPersistLocalDocument::WebBrowserPersistLocalDocument(
65 dom::Document* aDocument)
66 : mDocument(aDocument), mPersistFlags(0) {
67 MOZ_ASSERT(mDocument);
70 WebBrowserPersistLocalDocument::~WebBrowserPersistLocalDocument() = default;
72 NS_IMETHODIMP
73 WebBrowserPersistLocalDocument::SetPersistFlags(uint32_t aFlags) {
74 mPersistFlags = aFlags;
75 return NS_OK;
78 NS_IMETHODIMP
79 WebBrowserPersistLocalDocument::GetPersistFlags(uint32_t* aFlags) {
80 *aFlags = mPersistFlags;
81 return NS_OK;
84 NS_IMETHODIMP
85 WebBrowserPersistLocalDocument::GetIsClosed(bool* aIsClosed) {
86 *aIsClosed = false;
87 return NS_OK;
90 NS_IMETHODIMP
91 WebBrowserPersistLocalDocument::GetIsPrivate(bool* aIsPrivate) {
92 nsCOMPtr<nsILoadContext> privacyContext = mDocument->GetLoadContext();
93 *aIsPrivate = privacyContext && privacyContext->UsePrivateBrowsing();
94 return NS_OK;
97 NS_IMETHODIMP
98 WebBrowserPersistLocalDocument::GetDocumentURI(nsACString& aURISpec) {
99 nsCOMPtr<nsIURI> uri = mDocument->GetDocumentURI();
100 if (!uri) {
101 return NS_ERROR_UNEXPECTED;
103 return uri->GetSpec(aURISpec);
106 NS_IMETHODIMP
107 WebBrowserPersistLocalDocument::GetBaseURI(nsACString& aURISpec) {
108 nsCOMPtr<nsIURI> uri = GetBaseURI();
109 if (!uri) {
110 return NS_ERROR_UNEXPECTED;
112 return uri->GetSpec(aURISpec);
115 NS_IMETHODIMP
116 WebBrowserPersistLocalDocument::GetContentType(nsACString& aContentType) {
117 nsAutoString utf16Type;
118 mDocument->GetContentType(utf16Type);
119 CopyUTF16toUTF8(utf16Type, aContentType);
120 return NS_OK;
123 NS_IMETHODIMP
124 WebBrowserPersistLocalDocument::GetCharacterSet(nsACString& aCharSet) {
125 GetCharacterSet()->Name(aCharSet);
126 return NS_OK;
129 NS_IMETHODIMP
130 WebBrowserPersistLocalDocument::GetTitle(nsAString& aTitle) {
131 nsAutoString titleBuffer;
132 mDocument->GetTitle(titleBuffer);
133 aTitle = titleBuffer;
134 return NS_OK;
137 NS_IMETHODIMP
138 WebBrowserPersistLocalDocument::GetReferrerInfo(
139 nsIReferrerInfo** aReferrerInfo) {
140 *aReferrerInfo = mDocument->GetReferrerInfo();
141 NS_IF_ADDREF(*aReferrerInfo);
142 return NS_OK;
145 NS_IMETHODIMP
146 WebBrowserPersistLocalDocument::GetCookieJarSettings(
147 nsICookieJarSettings** aCookieJarSettings) {
148 *aCookieJarSettings = mDocument->CookieJarSettings();
149 NS_ADDREF(*aCookieJarSettings);
150 return NS_OK;
153 NS_IMETHODIMP
154 WebBrowserPersistLocalDocument::GetContentDisposition(nsAString& aCD) {
155 nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
156 if (NS_WARN_IF(!window)) {
157 aCD.SetIsVoid(true);
158 return NS_OK;
160 nsCOMPtr<nsIDOMWindowUtils> utils =
161 nsGlobalWindowOuter::Cast(window)->WindowUtils();
162 nsresult rv = utils->GetDocumentMetadata(u"content-disposition"_ns, aCD);
163 if (NS_WARN_IF(NS_FAILED(rv))) {
164 aCD.SetIsVoid(true);
166 return NS_OK;
169 NS_IMETHODIMP
170 WebBrowserPersistLocalDocument::GetCacheKey(uint32_t* aKey) {
171 Maybe<uint32_t> cacheKey;
173 if (nsDocShell* docShell = nsDocShell::Cast(mDocument->GetDocShell())) {
174 cacheKey = docShell->GetCacheKeyFromCurrentEntry();
176 *aKey = cacheKey.valueOr(0);
178 return NS_OK;
181 NS_IMETHODIMP
182 WebBrowserPersistLocalDocument::GetPostData(nsIInputStream** aStream) {
183 nsCOMPtr<nsIInputStream> postData;
184 if (nsDocShell* docShell = nsDocShell::Cast(mDocument->GetDocShell())) {
185 postData = docShell->GetPostDataFromCurrentEntry();
188 postData.forget(aStream);
189 return NS_OK;
192 NS_IMETHODIMP
193 WebBrowserPersistLocalDocument::GetPrincipal(nsIPrincipal** aPrincipal) {
194 nsCOMPtr<nsIPrincipal> nodePrincipal = mDocument->NodePrincipal();
195 nodePrincipal.forget(aPrincipal);
196 return NS_OK;
199 already_AddRefed<nsISHEntry> WebBrowserPersistLocalDocument::GetHistory() {
200 nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
201 if (NS_WARN_IF(!window)) {
202 return nullptr;
204 nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(window);
205 if (NS_WARN_IF(!webNav)) {
206 return nullptr;
208 nsCOMPtr<nsIWebPageDescriptor> desc = do_QueryInterface(webNav);
209 if (NS_WARN_IF(!desc)) {
210 return nullptr;
212 nsCOMPtr<nsISupports> curDesc;
213 nsresult rv = desc->GetCurrentDescriptor(getter_AddRefs(curDesc));
214 // This can fail if, e.g., the document is a Print Preview.
215 if (NS_FAILED(rv) || NS_WARN_IF(!curDesc)) {
216 return nullptr;
218 nsCOMPtr<nsISHEntry> history = do_QueryInterface(curDesc);
219 return history.forget();
222 NotNull<const Encoding*> WebBrowserPersistLocalDocument::GetCharacterSet()
223 const {
224 return mDocument->GetDocumentCharacterSet();
227 uint32_t WebBrowserPersistLocalDocument::GetPersistFlags() const {
228 return mPersistFlags;
231 nsIURI* WebBrowserPersistLocalDocument::GetBaseURI() const {
232 return mDocument->GetBaseURI();
235 namespace {
237 // Helper class for ReadResources().
238 class ResourceReader final : public nsIWebBrowserPersistDocumentReceiver {
239 public:
240 ResourceReader(WebBrowserPersistLocalDocument* aParent,
241 nsIWebBrowserPersistResourceVisitor* aVisitor);
242 nsresult OnWalkDOMNode(nsINode* aNode);
244 // This is called both to indicate the end of the document walk
245 // and when a subdocument is (maybe asynchronously) sent to the
246 // visitor. The call to EndVisit needs to happen after both of
247 // those have finished.
248 void DocumentDone(nsresult aStatus);
250 NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER
251 NS_DECL_ISUPPORTS
253 private:
254 RefPtr<WebBrowserPersistLocalDocument> mParent;
255 nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor;
256 nsCOMPtr<nsIURI> mCurrentBaseURI;
257 uint32_t mPersistFlags;
259 // The number of DocumentDone calls after which EndVisit will be
260 // called on the visitor. Counts the main document if it's still
261 // being walked and any outstanding asynchronous subdocument
262 // StartPersistence calls.
263 size_t mOutstandingDocuments;
264 // Collects the status parameters to DocumentDone calls.
265 nsresult mEndStatus;
267 nsresult OnWalkURI(const nsACString& aURISpec,
268 nsContentPolicyType aContentPolicyType);
269 nsresult OnWalkURI(nsIURI* aURI, nsContentPolicyType aContentPolicyType);
270 nsresult OnWalkAttribute(dom::Element* aElement,
271 nsContentPolicyType aContentPolicyType,
272 const char* aAttribute,
273 const char* aNamespaceURI = "");
274 nsresult OnWalkSubframe(nsINode* aNode);
275 nsresult OnWalkSrcSet(dom::Element* aElement);
277 ~ResourceReader();
279 using IWBP = nsIWebBrowserPersist;
282 NS_IMPL_ISUPPORTS(ResourceReader, nsIWebBrowserPersistDocumentReceiver)
284 ResourceReader::ResourceReader(WebBrowserPersistLocalDocument* aParent,
285 nsIWebBrowserPersistResourceVisitor* aVisitor)
286 : mParent(aParent),
287 mVisitor(aVisitor),
288 mCurrentBaseURI(aParent->GetBaseURI()),
289 mPersistFlags(aParent->GetPersistFlags()),
290 mOutstandingDocuments(1),
291 mEndStatus(NS_OK) {
292 MOZ_ASSERT(mCurrentBaseURI);
295 ResourceReader::~ResourceReader() { MOZ_ASSERT(mOutstandingDocuments == 0); }
297 void ResourceReader::DocumentDone(nsresult aStatus) {
298 MOZ_ASSERT(mOutstandingDocuments > 0);
299 if (NS_SUCCEEDED(mEndStatus)) {
300 mEndStatus = aStatus;
302 if (--mOutstandingDocuments == 0) {
303 mVisitor->EndVisit(mParent, mEndStatus);
307 nsresult ResourceReader::OnWalkSubframe(nsINode* aNode) {
308 RefPtr<nsFrameLoaderOwner> loaderOwner = do_QueryObject(aNode);
309 NS_ENSURE_STATE(loaderOwner);
310 RefPtr<nsFrameLoader> loader = loaderOwner->GetFrameLoader();
311 NS_ENSURE_STATE(loader);
313 RefPtr<dom::BrowsingContext> context = loader->GetBrowsingContext();
314 NS_ENSURE_STATE(context);
316 if (loader->IsRemoteFrame()) {
317 mVisitor->VisitBrowsingContext(mParent, context);
318 return NS_OK;
321 ++mOutstandingDocuments;
322 ErrorResult err;
323 loader->StartPersistence(context, this, err);
324 nsresult rv = err.StealNSResult();
325 if (NS_FAILED(rv)) {
326 if (rv == NS_ERROR_NO_CONTENT) {
327 // Just ignore frames with no content document.
328 rv = NS_OK;
330 // StartPersistence won't eventually call this if it failed,
331 // so this does so (to keep mOutstandingDocuments correct).
332 DocumentDone(rv);
334 return rv;
337 NS_IMETHODIMP
338 ResourceReader::OnDocumentReady(nsIWebBrowserPersistDocument* aDocument) {
339 mVisitor->VisitDocument(mParent, aDocument);
340 DocumentDone(NS_OK);
341 return NS_OK;
344 NS_IMETHODIMP
345 ResourceReader::OnError(nsresult aFailure) {
346 DocumentDone(aFailure);
347 return NS_OK;
350 nsresult ResourceReader::OnWalkURI(nsIURI* aURI,
351 nsContentPolicyType aContentPolicyType) {
352 // Test if this URI should be persisted. By default
353 // we should assume the URI is persistable.
354 bool doNotPersistURI;
355 nsresult rv = NS_URIChainHasFlags(
356 aURI, nsIProtocolHandler::URI_NON_PERSISTABLE, &doNotPersistURI);
357 if (NS_SUCCEEDED(rv) && doNotPersistURI) {
358 return NS_OK;
361 nsAutoCString stringURI;
362 rv = aURI->GetSpec(stringURI);
363 NS_ENSURE_SUCCESS(rv, rv);
364 return mVisitor->VisitResource(mParent, stringURI, aContentPolicyType);
367 nsresult ResourceReader::OnWalkURI(const nsACString& aURISpec,
368 nsContentPolicyType aContentPolicyType) {
369 nsresult rv;
370 nsCOMPtr<nsIURI> uri;
372 rv = NS_NewURI(getter_AddRefs(uri), aURISpec, mParent->GetCharacterSet(),
373 mCurrentBaseURI);
374 if (NS_FAILED(rv)) {
375 // We don't want to break saving a page in case of a malformed URI.
376 return NS_OK;
378 return OnWalkURI(uri, aContentPolicyType);
381 static void ExtractAttribute(dom::Element* aElement, const char* aAttribute,
382 const char* aNamespaceURI, nsCString& aValue) {
383 // Find the named URI attribute on the (element) node and store
384 // a reference to the URI that maps onto a local file name
386 RefPtr<nsDOMAttributeMap> attrMap = aElement->Attributes();
388 NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI);
389 NS_ConvertASCIItoUTF16 attribute(aAttribute);
390 RefPtr<dom::Attr> attr = attrMap->GetNamedItemNS(namespaceURI, attribute);
391 if (attr) {
392 nsAutoString value;
393 attr->GetValue(value);
394 CopyUTF16toUTF8(value, aValue);
395 } else {
396 aValue.Truncate();
400 nsresult ResourceReader::OnWalkAttribute(dom::Element* aElement,
401 nsContentPolicyType aContentPolicyType,
402 const char* aAttribute,
403 const char* aNamespaceURI) {
404 nsAutoCString uriSpec;
405 ExtractAttribute(aElement, aAttribute, aNamespaceURI, uriSpec);
406 if (uriSpec.IsEmpty()) {
407 return NS_OK;
409 return OnWalkURI(uriSpec, aContentPolicyType);
412 nsresult ResourceReader::OnWalkSrcSet(dom::Element* aElement) {
413 nsAutoString srcSet;
414 if (!aElement->GetAttr(nsGkAtoms::srcset, srcSet)) {
415 return NS_OK;
418 nsresult rv = NS_OK;
419 auto eachCandidate = [&](dom::ResponsiveImageCandidate&& aCandidate) {
420 if (!aCandidate.IsValid() || NS_FAILED(rv)) {
421 return;
423 rv = OnWalkURI(NS_ConvertUTF16toUTF8(aCandidate.URLString()),
424 nsIContentPolicy::TYPE_IMAGE);
426 dom::ResponsiveImageSelector::ParseSourceSet(srcSet, eachCandidate);
427 return rv;
430 static nsresult GetXMLStyleSheetLink(dom::ProcessingInstruction* aPI,
431 nsAString& aHref) {
432 nsAutoString data;
433 aPI->GetData(data);
435 nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::href, aHref);
436 return NS_OK;
439 nsresult ResourceReader::OnWalkDOMNode(nsINode* aNode) {
440 // Fixup xml-stylesheet processing instructions
441 if (auto nodeAsPI = dom::ProcessingInstruction::FromNode(aNode)) {
442 nsAutoString target;
443 nodeAsPI->GetTarget(target);
444 if (target.EqualsLiteral("xml-stylesheet")) {
445 nsAutoString href;
446 GetXMLStyleSheetLink(nodeAsPI, href);
447 if (!href.IsEmpty()) {
448 return OnWalkURI(NS_ConvertUTF16toUTF8(href),
449 nsIContentPolicy::TYPE_STYLESHEET);
452 return NS_OK;
455 // Test the node to see if it's an image, frame, iframe, css, js
456 if (auto* img = dom::HTMLImageElement::FromNode(*aNode)) {
457 MOZ_TRY(OnWalkAttribute(img, nsIContentPolicy::TYPE_IMAGE, "src"));
458 MOZ_TRY(OnWalkSrcSet(img));
459 return NS_OK;
462 if (aNode->IsSVGElement(nsGkAtoms::image)) {
463 MOZ_TRY(OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
464 "href"));
465 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
466 "href", "http://www.w3.org/1999/xlink");
469 if (aNode->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video)) {
470 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_MEDIA,
471 "src");
474 if (aNode->IsHTMLElement(nsGkAtoms::source)) {
475 MOZ_TRY(OnWalkSrcSet(aNode->AsElement()));
476 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_MEDIA,
477 "src");
480 if (aNode->IsHTMLElement(nsGkAtoms::body)) {
481 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
482 "background");
485 if (aNode->IsHTMLElement(nsGkAtoms::table)) {
486 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
487 "background");
490 if (aNode->IsHTMLElement(nsGkAtoms::tr)) {
491 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
492 "background");
495 if (aNode->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) {
496 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
497 "background");
500 if (aNode->IsHTMLElement(nsGkAtoms::script)) {
501 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_SCRIPT,
502 "src");
505 if (aNode->IsSVGElement(nsGkAtoms::script)) {
506 MOZ_TRY(OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_SCRIPT,
507 "href"));
508 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_SCRIPT,
509 "href", "http://www.w3.org/1999/xlink");
512 if (aNode->IsHTMLElement(nsGkAtoms::embed)) {
513 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_OBJECT,
514 "src");
517 if (aNode->IsHTMLElement(nsGkAtoms::object)) {
518 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_OBJECT,
519 "data");
522 if (auto nodeAsLink = dom::HTMLLinkElement::FromNode(aNode)) {
523 // Test if the link has a rel value indicating it to be a stylesheet
524 nsAutoString linkRel;
525 nodeAsLink->GetRel(linkRel);
526 if (!linkRel.IsEmpty()) {
527 nsReadingIterator<char16_t> start;
528 nsReadingIterator<char16_t> end;
529 nsReadingIterator<char16_t> current;
531 linkRel.BeginReading(start);
532 linkRel.EndReading(end);
534 // Walk through space delimited string looking for "stylesheet"
535 for (current = start; current != end; ++current) {
536 // Ignore whitespace
537 if (nsCRT::IsAsciiSpace(*current)) {
538 continue;
541 // Grab the next space delimited word
542 nsReadingIterator<char16_t> startWord = current;
543 do {
544 ++current;
545 } while (current != end && !nsCRT::IsAsciiSpace(*current));
547 // Store the link for fix up if it says "stylesheet"
548 if (Substring(startWord, current)
549 .LowerCaseEqualsLiteral("stylesheet")) {
550 OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_STYLESHEET,
551 "href");
552 return NS_OK;
554 if (current == end) {
555 break;
559 return NS_OK;
562 if (aNode->IsHTMLElement(nsGkAtoms::frame)) {
563 return OnWalkSubframe(aNode);
566 if (aNode->IsHTMLElement(nsGkAtoms::iframe) &&
567 !(mPersistFlags & IWBP::PERSIST_FLAGS_IGNORE_IFRAMES)) {
568 return OnWalkSubframe(aNode);
571 auto nodeAsInput = dom::HTMLInputElement::FromNode(aNode);
572 if (nodeAsInput) {
573 return OnWalkAttribute(aNode->AsElement(), nsIContentPolicy::TYPE_IMAGE,
574 "src");
577 return NS_OK;
580 // Helper class for node rewriting in writeContent().
581 class PersistNodeFixup final : public nsIDocumentEncoderNodeFixup {
582 public:
583 PersistNodeFixup(WebBrowserPersistLocalDocument* aParent,
584 nsIWebBrowserPersistURIMap* aMap, nsIURI* aTargetURI);
586 NS_DECL_ISUPPORTS
587 NS_DECL_NSIDOCUMENTENCODERNODEFIXUP
588 private:
589 virtual ~PersistNodeFixup() = default;
590 RefPtr<WebBrowserPersistLocalDocument> mParent;
591 nsClassHashtable<nsCStringHashKey, nsCString> mMap;
592 nsCOMPtr<nsIURI> mCurrentBaseURI;
593 nsCOMPtr<nsIURI> mTargetBaseURI;
595 bool IsFlagSet(uint32_t aFlag) const {
596 return mParent->GetPersistFlags() & aFlag;
599 nsresult GetNodeToFixup(nsINode* aNodeIn, nsINode** aNodeOut);
600 nsresult FixupURI(nsAString& aURI);
601 nsresult FixupAttribute(nsINode* aNode, const char* aAttribute,
602 const char* aNamespaceURI = "");
603 nsresult FixupAnchor(nsINode* aNode);
604 nsresult FixupXMLStyleSheetLink(dom::ProcessingInstruction* aPI,
605 const nsAString& aHref);
607 nsresult FixupSrcSet(nsINode*);
609 using IWBP = nsIWebBrowserPersist;
612 NS_IMPL_ISUPPORTS(PersistNodeFixup, nsIDocumentEncoderNodeFixup)
614 PersistNodeFixup::PersistNodeFixup(WebBrowserPersistLocalDocument* aParent,
615 nsIWebBrowserPersistURIMap* aMap,
616 nsIURI* aTargetURI)
617 : mParent(aParent),
618 mCurrentBaseURI(aParent->GetBaseURI()),
619 mTargetBaseURI(aTargetURI) {
620 if (aMap) {
621 uint32_t mapSize;
622 nsresult rv = aMap->GetNumMappedURIs(&mapSize);
623 MOZ_ASSERT(NS_SUCCEEDED(rv));
624 NS_ENSURE_SUCCESS_VOID(rv);
625 for (uint32_t i = 0; i < mapSize; ++i) {
626 nsAutoCString urlFrom;
627 auto urlTo = MakeUnique<nsCString>();
629 rv = aMap->GetURIMapping(i, urlFrom, *urlTo);
630 MOZ_ASSERT(NS_SUCCEEDED(rv));
631 if (NS_SUCCEEDED(rv)) {
632 mMap.InsertOrUpdate(urlFrom, std::move(urlTo));
638 nsresult PersistNodeFixup::GetNodeToFixup(nsINode* aNodeIn,
639 nsINode** aNodeOut) {
640 // Avoid mixups in FixupNode that could leak objects; this goes
641 // against the usual out parameter convention, but it's a private
642 // method so shouldn't be a problem.
643 MOZ_ASSERT(!*aNodeOut);
645 if (!IsFlagSet(IWBP::PERSIST_FLAGS_FIXUP_ORIGINAL_DOM)) {
646 ErrorResult rv;
647 *aNodeOut = aNodeIn->CloneNode(false, rv).take();
648 return rv.StealNSResult();
651 NS_ADDREF(*aNodeOut = aNodeIn);
652 return NS_OK;
655 nsresult PersistNodeFixup::FixupURI(nsAString& aURI) {
656 // get the current location of the file (absolutized)
657 nsCOMPtr<nsIURI> uri;
658 nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI, mParent->GetCharacterSet(),
659 mCurrentBaseURI);
660 NS_ENSURE_SUCCESS(rv, rv);
661 nsAutoCString spec;
662 rv = uri->GetSpec(spec);
663 NS_ENSURE_SUCCESS(rv, rv);
665 const nsCString* replacement = mMap.Get(spec);
666 if (!replacement) {
667 // Note that most callers ignore this "failure".
668 return NS_ERROR_FAILURE;
670 if (!replacement->IsEmpty()) {
671 CopyUTF8toUTF16(*replacement, aURI);
673 return NS_OK;
676 nsresult PersistNodeFixup::FixupSrcSet(nsINode* aNode) {
677 dom::Element* element = aNode->AsElement();
678 nsAutoString originalSrcSet;
679 if (!element->GetAttr(nsGkAtoms::srcset, originalSrcSet)) {
680 return NS_OK;
682 nsAutoString newSrcSet;
683 bool first = true;
684 auto eachCandidate = [&](dom::ResponsiveImageCandidate&& aCandidate) {
685 if (!aCandidate.IsValid()) {
686 return;
688 if (!first) {
689 newSrcSet.AppendLiteral(", ");
691 first = false;
692 nsAutoString uri(aCandidate.URLString());
693 FixupURI(uri);
694 newSrcSet.Append(uri);
695 aCandidate.AppendDescriptors(newSrcSet);
697 dom::ResponsiveImageSelector::ParseSourceSet(originalSrcSet, eachCandidate);
698 element->SetAttr(nsGkAtoms::srcset, newSrcSet, IgnoreErrors());
699 return NS_OK;
702 nsresult PersistNodeFixup::FixupAttribute(nsINode* aNode,
703 const char* aAttribute,
704 const char* aNamespaceURI) {
705 MOZ_ASSERT(aNode->IsElement());
706 dom::Element* element = aNode->AsElement();
708 RefPtr<nsDOMAttributeMap> attrMap = element->Attributes();
710 NS_ConvertASCIItoUTF16 attribute(aAttribute);
711 NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI);
712 RefPtr<dom::Attr> attr = attrMap->GetNamedItemNS(namespaceURI, attribute);
713 nsresult rv = NS_OK;
714 if (attr) {
715 nsString uri;
716 attr->GetValue(uri);
717 rv = FixupURI(uri);
718 if (NS_SUCCEEDED(rv)) {
719 attr->SetValue(uri, IgnoreErrors());
723 return rv;
726 nsresult PersistNodeFixup::FixupAnchor(nsINode* aNode) {
727 if (IsFlagSet(IWBP::PERSIST_FLAGS_DONT_FIXUP_LINKS)) {
728 return NS_OK;
731 MOZ_ASSERT(aNode->IsElement());
732 dom::Element* element = aNode->AsElement();
734 RefPtr<nsDOMAttributeMap> attrMap = element->Attributes();
736 // Make all anchor links absolute so they point off onto the Internet
737 nsString attribute(u"href"_ns);
738 RefPtr<dom::Attr> attr = attrMap->GetNamedItem(attribute);
739 if (attr) {
740 nsString oldValue;
741 attr->GetValue(oldValue);
742 NS_ConvertUTF16toUTF8 oldCValue(oldValue);
744 // Skip empty values and self-referencing bookmarks
745 if (oldCValue.IsEmpty() || oldCValue.CharAt(0) == '#') {
746 return NS_OK;
749 // if saving file to same location, we don't need to do any fixup
750 bool isEqual;
751 if (mTargetBaseURI &&
752 NS_SUCCEEDED(mCurrentBaseURI->Equals(mTargetBaseURI, &isEqual)) &&
753 isEqual) {
754 return NS_OK;
757 nsCOMPtr<nsIURI> relativeURI;
758 relativeURI = IsFlagSet(IWBP::PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION)
759 ? mTargetBaseURI
760 : mCurrentBaseURI;
761 // Make a new URI to replace the current one
762 nsCOMPtr<nsIURI> newURI;
763 nsresult rv = NS_NewURI(getter_AddRefs(newURI), oldCValue,
764 mParent->GetCharacterSet(), relativeURI);
765 if (NS_SUCCEEDED(rv) && newURI) {
766 Unused << NS_MutateURI(newURI).SetUserPass(""_ns).Finalize(newURI);
767 nsAutoCString uriSpec;
768 rv = newURI->GetSpec(uriSpec);
769 NS_ENSURE_SUCCESS(rv, rv);
770 attr->SetValue(NS_ConvertUTF8toUTF16(uriSpec), IgnoreErrors());
774 return NS_OK;
777 static void AppendXMLAttr(const nsAString& key, const nsAString& aValue,
778 nsAString& aBuffer) {
779 if (!aBuffer.IsEmpty()) {
780 aBuffer.Append(' ');
782 aBuffer.Append(key);
783 aBuffer.AppendLiteral(R"(=")");
784 for (size_t i = 0; i < aValue.Length(); ++i) {
785 switch (aValue[i]) {
786 case '&':
787 aBuffer.AppendLiteral("&amp;");
788 break;
789 case '<':
790 aBuffer.AppendLiteral("&lt;");
791 break;
792 case '>':
793 aBuffer.AppendLiteral("&gt;");
794 break;
795 case '"':
796 aBuffer.AppendLiteral("&quot;");
797 break;
798 default:
799 aBuffer.Append(aValue[i]);
800 break;
803 aBuffer.Append('"');
806 nsresult PersistNodeFixup::FixupXMLStyleSheetLink(
807 dom::ProcessingInstruction* aPI, const nsAString& aHref) {
808 NS_ENSURE_ARG_POINTER(aPI);
810 nsAutoString data;
811 aPI->GetData(data);
813 nsAutoString href;
814 nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::href, href);
816 // Construct and set a new data value for the xml-stylesheet
817 if (!aHref.IsEmpty() && !href.IsEmpty()) {
818 nsAutoString alternate;
819 nsAutoString charset;
820 nsAutoString title;
821 nsAutoString type;
822 nsAutoString media;
824 nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::alternate,
825 alternate);
826 nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::charset, charset);
827 nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::title, title);
828 nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::type, type);
829 nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::media, media);
831 nsAutoString newData;
832 AppendXMLAttr(u"href"_ns, aHref, newData);
833 if (!title.IsEmpty()) {
834 AppendXMLAttr(u"title"_ns, title, newData);
836 if (!media.IsEmpty()) {
837 AppendXMLAttr(u"media"_ns, media, newData);
839 if (!type.IsEmpty()) {
840 AppendXMLAttr(u"type"_ns, type, newData);
842 if (!charset.IsEmpty()) {
843 AppendXMLAttr(u"charset"_ns, charset, newData);
845 if (!alternate.IsEmpty()) {
846 AppendXMLAttr(u"alternate"_ns, alternate, newData);
848 aPI->SetData(newData, IgnoreErrors());
851 return NS_OK;
854 NS_IMETHODIMP
855 PersistNodeFixup::FixupNode(nsINode* aNodeIn, bool* aSerializeCloneKids,
856 nsINode** aNodeOut) {
857 *aNodeOut = nullptr;
858 *aSerializeCloneKids = false;
860 uint16_t type = aNodeIn->NodeType();
861 if (type != nsINode::ELEMENT_NODE &&
862 type != nsINode::PROCESSING_INSTRUCTION_NODE) {
863 return NS_OK;
866 MOZ_ASSERT(aNodeIn->IsContent());
868 // Fixup xml-stylesheet processing instructions
869 if (auto nodeAsPI = dom::ProcessingInstruction::FromNode(aNodeIn)) {
870 nsAutoString target;
871 nodeAsPI->GetTarget(target);
872 if (target.EqualsLiteral("xml-stylesheet")) {
873 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
874 if (NS_SUCCEEDED(rv) && *aNodeOut) {
875 MOZ_ASSERT((*aNodeOut)->IsProcessingInstruction());
876 auto nodeAsPI = static_cast<dom::ProcessingInstruction*>(*aNodeOut);
877 nsAutoString href;
878 GetXMLStyleSheetLink(nodeAsPI, href);
879 if (!href.IsEmpty()) {
880 FixupURI(href);
881 FixupXMLStyleSheetLink(nodeAsPI, href);
885 return NS_OK;
888 nsCOMPtr<nsIContent> content = do_QueryInterface(aNodeIn);
889 if (!content) {
890 return NS_OK;
893 // BASE elements are replaced by a comment so relative links are not hosed.
894 if (!IsFlagSet(IWBP::PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS) &&
895 content->IsHTMLElement(nsGkAtoms::base)) {
896 // Base uses HTMLSharedElement, which would be awkward to implement
897 // FromContent on, since it represents multiple elements. Since we've
898 // already checked IsHTMLElement here, just cast as we were doing.
899 auto* base = static_cast<dom::HTMLSharedElement*>(content.get());
900 dom::Document* ownerDoc = base->OwnerDoc();
902 nsAutoString href;
903 base->GetHref(href); // Doesn't matter if this fails
904 nsAutoString commentText;
905 commentText.AssignLiteral(" base ");
906 if (!href.IsEmpty()) {
907 commentText += u"href=\""_ns + href + u"\" "_ns;
909 *aNodeOut = ownerDoc->CreateComment(commentText).take();
910 return NS_OK;
913 // Fix up href and file links in the elements
914 RefPtr<dom::HTMLAnchorElement> nodeAsAnchor =
915 dom::HTMLAnchorElement::FromNode(content);
916 if (nodeAsAnchor) {
917 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
918 if (NS_SUCCEEDED(rv) && *aNodeOut) {
919 FixupAnchor(*aNodeOut);
921 return rv;
924 RefPtr<dom::HTMLAreaElement> nodeAsArea =
925 dom::HTMLAreaElement::FromNode(content);
926 if (nodeAsArea) {
927 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
928 if (NS_SUCCEEDED(rv) && *aNodeOut) {
929 FixupAnchor(*aNodeOut);
931 return rv;
934 if (content->IsHTMLElement(nsGkAtoms::body)) {
935 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
936 if (NS_SUCCEEDED(rv) && *aNodeOut) {
937 FixupAttribute(*aNodeOut, "background");
939 return rv;
942 if (content->IsHTMLElement(nsGkAtoms::table)) {
943 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
944 if (NS_SUCCEEDED(rv) && *aNodeOut) {
945 FixupAttribute(*aNodeOut, "background");
947 return rv;
950 if (content->IsHTMLElement(nsGkAtoms::tr)) {
951 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
952 if (NS_SUCCEEDED(rv) && *aNodeOut) {
953 FixupAttribute(*aNodeOut, "background");
955 return rv;
958 if (content->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) {
959 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
960 if (NS_SUCCEEDED(rv) && *aNodeOut) {
961 FixupAttribute(*aNodeOut, "background");
963 return rv;
966 if (content->IsHTMLElement(nsGkAtoms::img)) {
967 MOZ_TRY(GetNodeToFixup(aNodeIn, aNodeOut));
968 if (!*aNodeOut) {
969 return NS_OK;
972 // Disable image loads
973 nsCOMPtr<nsIImageLoadingContent> imgCon = do_QueryInterface(*aNodeOut);
974 if (imgCon) {
975 imgCon->SetLoadingEnabled(false);
977 // FIXME(emilio): Why fixing up <img href>? Looks bogus
978 FixupAnchor(*aNodeOut);
979 FixupAttribute(*aNodeOut, "src");
980 FixupSrcSet(*aNodeOut);
981 return NS_OK;
984 if (content->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video)) {
985 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
986 if (NS_SUCCEEDED(rv) && *aNodeOut) {
987 FixupAttribute(*aNodeOut, "src");
989 return rv;
992 if (content->IsHTMLElement(nsGkAtoms::source)) {
993 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
994 if (NS_SUCCEEDED(rv) && *aNodeOut) {
995 FixupAttribute(*aNodeOut, "src");
996 FixupSrcSet(*aNodeOut);
998 return rv;
1001 if (content->IsSVGElement(nsGkAtoms::image)) {
1002 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1003 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1004 // Disable image loads
1005 nsCOMPtr<nsIImageLoadingContent> imgCon = do_QueryInterface(*aNodeOut);
1006 if (imgCon) imgCon->SetLoadingEnabled(false);
1008 // FixupAnchor(*aNodeOut); // XXXjwatt: is this line needed?
1009 FixupAttribute(*aNodeOut, "href");
1010 FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink");
1012 return rv;
1015 if (content->IsHTMLElement(nsGkAtoms::script)) {
1016 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1017 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1018 FixupAttribute(*aNodeOut, "src");
1020 return rv;
1023 if (content->IsSVGElement(nsGkAtoms::script)) {
1024 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1025 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1026 FixupAttribute(*aNodeOut, "href");
1027 FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink");
1029 return rv;
1032 if (content->IsHTMLElement(nsGkAtoms::embed)) {
1033 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1034 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1035 FixupAttribute(*aNodeOut, "src");
1037 return rv;
1040 if (content->IsHTMLElement(nsGkAtoms::object)) {
1041 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1042 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1043 FixupAttribute(*aNodeOut, "data");
1045 return rv;
1048 if (content->IsHTMLElement(nsGkAtoms::link)) {
1049 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1050 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1051 // First see if the link represents linked content
1052 rv = FixupAttribute(*aNodeOut, "href");
1053 if (NS_FAILED(rv)) {
1054 // Perhaps this link is actually an anchor to related content
1055 FixupAnchor(*aNodeOut);
1057 // TODO if "type" attribute == "text/css"
1058 // fixup stylesheet
1060 return rv;
1063 if (content->IsHTMLElement(nsGkAtoms::frame)) {
1064 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1065 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1066 FixupAttribute(*aNodeOut, "src");
1068 return rv;
1071 if (content->IsHTMLElement(nsGkAtoms::iframe)) {
1072 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1073 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1074 FixupAttribute(*aNodeOut, "src");
1076 return rv;
1079 RefPtr<dom::HTMLInputElement> nodeAsInput =
1080 dom::HTMLInputElement::FromNodeOrNull(content);
1081 if (nodeAsInput) {
1082 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1083 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1084 // Disable image loads
1085 nsCOMPtr<nsIImageLoadingContent> imgCon = do_QueryInterface(*aNodeOut);
1086 if (imgCon) {
1087 imgCon->SetLoadingEnabled(false);
1090 FixupAttribute(*aNodeOut, "src");
1092 nsAutoString valueStr;
1093 constexpr auto valueAttr = u"value"_ns;
1094 // Update element node attributes with user-entered form state
1095 RefPtr<dom::HTMLInputElement> outElt =
1096 dom::HTMLInputElement::FromNode((*aNodeOut)->AsContent());
1097 nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(*aNodeOut);
1098 switch (formControl->ControlType()) {
1099 case FormControlType::InputEmail:
1100 case FormControlType::InputSearch:
1101 case FormControlType::InputText:
1102 case FormControlType::InputTel:
1103 case FormControlType::InputUrl:
1104 case FormControlType::InputNumber:
1105 case FormControlType::InputRange:
1106 case FormControlType::InputDate:
1107 case FormControlType::InputTime:
1108 case FormControlType::InputColor:
1109 nodeAsInput->GetValue(valueStr, dom::CallerType::System);
1110 // Avoid superfluous value="" serialization
1111 if (valueStr.IsEmpty()) {
1112 outElt->RemoveAttribute(valueAttr, IgnoreErrors());
1113 } else {
1114 outElt->SetAttribute(valueAttr, valueStr, IgnoreErrors());
1116 break;
1117 case FormControlType::InputCheckbox:
1118 case FormControlType::InputRadio:
1119 outElt->SetDefaultChecked(nodeAsInput->Checked(), IgnoreErrors());
1120 break;
1121 default:
1122 break;
1125 return rv;
1128 dom::HTMLTextAreaElement* nodeAsTextArea =
1129 dom::HTMLTextAreaElement::FromNode(content);
1130 if (nodeAsTextArea) {
1131 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1132 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1133 // Tell the document encoder to serialize the text child we create below
1134 *aSerializeCloneKids = true;
1136 nsAutoString valueStr;
1137 nodeAsTextArea->GetValue(valueStr);
1139 (*aNodeOut)->SetTextContent(valueStr, IgnoreErrors());
1141 return rv;
1144 dom::HTMLOptionElement* nodeAsOption =
1145 dom::HTMLOptionElement::FromNode(content);
1146 if (nodeAsOption) {
1147 nsresult rv = GetNodeToFixup(aNodeIn, aNodeOut);
1148 if (NS_SUCCEEDED(rv) && *aNodeOut) {
1149 dom::HTMLOptionElement* outElt =
1150 dom::HTMLOptionElement::FromNode((*aNodeOut)->AsContent());
1151 bool selected = nodeAsOption->Selected();
1152 outElt->SetDefaultSelected(selected, IgnoreErrors());
1154 return rv;
1157 return NS_OK;
1160 } // unnamed namespace
1162 NS_IMETHODIMP
1163 WebBrowserPersistLocalDocument::ReadResources(
1164 nsIWebBrowserPersistResourceVisitor* aVisitor) {
1165 nsresult rv = NS_OK;
1166 nsCOMPtr<nsIWebBrowserPersistResourceVisitor> visitor = aVisitor;
1168 NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
1170 ErrorResult err;
1171 RefPtr<dom::TreeWalker> walker = mDocument->CreateTreeWalker(
1172 *mDocument,
1173 dom::NodeFilter_Binding::SHOW_ELEMENT |
1174 dom::NodeFilter_Binding::SHOW_DOCUMENT |
1175 dom::NodeFilter_Binding::SHOW_PROCESSING_INSTRUCTION,
1176 nullptr, err);
1178 if (NS_WARN_IF(err.Failed())) {
1179 return err.StealNSResult();
1181 MOZ_ASSERT(walker);
1183 RefPtr<ResourceReader> reader = new ResourceReader(this, aVisitor);
1184 nsCOMPtr<nsINode> currentNode = walker->CurrentNode();
1185 do {
1186 rv = reader->OnWalkDOMNode(currentNode);
1187 if (NS_WARN_IF(NS_FAILED(rv))) {
1188 break;
1191 ErrorResult err;
1192 currentNode = walker->NextNode(err);
1193 if (NS_WARN_IF(err.Failed())) {
1194 err.SuppressException();
1195 break;
1197 } while (currentNode);
1198 reader->DocumentDone(rv);
1199 // If NS_FAILED(rv), it was / will be reported by an EndVisit call
1200 // via DocumentDone. This method must return a failure if and
1201 // only if visitor won't be invoked.
1202 return NS_OK;
1205 static uint32_t ConvertEncoderFlags(uint32_t aEncoderFlags) {
1206 uint32_t encoderFlags = 0;
1208 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_SELECTION_ONLY)
1209 encoderFlags |= nsIDocumentEncoder::OutputSelectionOnly;
1210 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_FORMATTED)
1211 encoderFlags |= nsIDocumentEncoder::OutputFormatted;
1212 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_RAW)
1213 encoderFlags |= nsIDocumentEncoder::OutputRaw;
1214 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_BODY_ONLY)
1215 encoderFlags |= nsIDocumentEncoder::OutputBodyOnly;
1216 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_PREFORMATTED)
1217 encoderFlags |= nsIDocumentEncoder::OutputPreformatted;
1218 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_WRAP)
1219 encoderFlags |= nsIDocumentEncoder::OutputWrap;
1220 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_FORMAT_FLOWED)
1221 encoderFlags |= nsIDocumentEncoder::OutputFormatFlowed;
1222 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ABSOLUTE_LINKS)
1223 encoderFlags |= nsIDocumentEncoder::OutputAbsoluteLinks;
1224 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_BASIC_ENTITIES)
1225 encoderFlags |= nsIDocumentEncoder::OutputEncodeBasicEntities;
1226 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_CR_LINEBREAKS)
1227 encoderFlags |= nsIDocumentEncoder::OutputCRLineBreak;
1228 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_LF_LINEBREAKS)
1229 encoderFlags |= nsIDocumentEncoder::OutputLFLineBreak;
1230 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_NOSCRIPT_CONTENT)
1231 encoderFlags |= nsIDocumentEncoder::OutputNoScriptContent;
1232 if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_NOFRAMES_CONTENT)
1233 encoderFlags |= nsIDocumentEncoder::OutputNoFramesContent;
1235 return encoderFlags;
1238 static bool ContentTypeEncoderExists(const nsACString& aType) {
1239 return do_getDocumentTypeSupportedForEncoding(
1240 PromiseFlatCString(aType).get());
1243 void WebBrowserPersistLocalDocument::DecideContentType(
1244 nsACString& aContentType) {
1245 if (aContentType.IsEmpty()) {
1246 if (NS_WARN_IF(NS_FAILED(GetContentType(aContentType)))) {
1247 aContentType.Truncate();
1250 if (!aContentType.IsEmpty() && !ContentTypeEncoderExists(aContentType)) {
1251 aContentType.Truncate();
1253 if (aContentType.IsEmpty()) {
1254 aContentType.AssignLiteral("text/html");
1258 nsresult WebBrowserPersistLocalDocument::GetDocEncoder(
1259 const nsACString& aContentType, uint32_t aEncoderFlags,
1260 nsIDocumentEncoder** aEncoder) {
1261 nsCOMPtr<nsIDocumentEncoder> encoder =
1262 do_createDocumentEncoder(PromiseFlatCString(aContentType).get());
1263 NS_ENSURE_TRUE(encoder, NS_ERROR_FAILURE);
1265 nsresult rv =
1266 encoder->NativeInit(mDocument, NS_ConvertASCIItoUTF16(aContentType),
1267 ConvertEncoderFlags(aEncoderFlags));
1268 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
1270 nsAutoCString charSet;
1271 rv = GetCharacterSet(charSet);
1272 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
1273 rv = encoder->SetCharset(charSet);
1274 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
1276 encoder.forget(aEncoder);
1277 return NS_OK;
1280 NS_IMETHODIMP
1281 WebBrowserPersistLocalDocument::WriteContent(
1282 nsIOutputStream* aStream, nsIWebBrowserPersistURIMap* aMap,
1283 const nsACString& aRequestedContentType, uint32_t aEncoderFlags,
1284 uint32_t aWrapColumn, nsIWebBrowserPersistWriteCompletion* aCompletion) {
1285 NS_ENSURE_ARG_POINTER(aStream);
1286 NS_ENSURE_ARG_POINTER(aCompletion);
1287 nsAutoCString contentType(aRequestedContentType);
1288 DecideContentType(contentType);
1290 nsCOMPtr<nsIDocumentEncoder> encoder;
1291 nsresult rv =
1292 GetDocEncoder(contentType, aEncoderFlags, getter_AddRefs(encoder));
1293 NS_ENSURE_SUCCESS(rv, rv);
1295 if (aWrapColumn != 0 &&
1296 (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_WRAP)) {
1297 encoder->SetWrapColumn(aWrapColumn);
1300 nsCOMPtr<nsIURI> targetURI;
1301 if (aMap) {
1302 nsAutoCString targetURISpec;
1303 rv = aMap->GetTargetBaseURI(targetURISpec);
1304 if (NS_SUCCEEDED(rv) && !targetURISpec.IsEmpty()) {
1305 rv = NS_NewURI(getter_AddRefs(targetURI), targetURISpec);
1306 NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
1307 } else if (mPersistFlags &
1308 nsIWebBrowserPersist::PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION) {
1309 return NS_ERROR_UNEXPECTED;
1312 rv = encoder->SetNodeFixup(new PersistNodeFixup(this, aMap, targetURI));
1313 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
1315 rv = encoder->EncodeToStream(aStream);
1316 aCompletion->OnFinish(this, aStream, contentType, rv);
1317 return NS_OK;
1320 } // namespace mozilla