tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

WebBrowserPersistLocalDocument.cpp (42102B)


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