Bug 1101100 - Multiprocess-enable nsWebBrowserPersist. r=billm
authorJed Davis <jld@mozilla.com>
Wed, 05 Aug 2015 14:25:39 -0700
changeset 288109 d19a0d92455b5606149d90df5ce80278c73d5309
parent 288108 45ea852a8f61a301a4e698bbd32d05b53415b588
child 288110 331e489c753420ba3aeb537fe6af196f1a0ffee2
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs1101100
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1101100 - Multiprocess-enable nsWebBrowserPersist. r=billm The high-level overview is that the parts of nsWebBrowserPersist which access the DOM have been factored out (as WebBrowserPersistLocalDocument) and abstracted (nsIWebBrowserPersistDocument) such that they can be implemented in the cross-process case using IPC.
browser/base/content/browser-sets.inc
browser/base/content/browser.js
browser/base/content/nsContextMenu.js
browser/components/customizableui/CustomizableWidgets.jsm
dom/base/nsFrameLoader.cpp
dom/base/nsFrameLoader.h
dom/ipc/PBrowser.ipdl
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
embedding/browser/nsWebBrowser.cpp
embedding/components/webbrowserpersist/PWebBrowserPersistDocument.ipdl
embedding/components/webbrowserpersist/PWebBrowserPersistResources.ipdl
embedding/components/webbrowserpersist/PWebBrowserPersistSerialize.ipdl
embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.cpp
embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.h
embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.cpp
embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.h
embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp
embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h
embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp
embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.h
embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp
embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.h
embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.cpp
embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.h
embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.cpp
embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.h
embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.cpp
embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.h
embedding/components/webbrowserpersist/moz.build
embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl
embedding/components/webbrowserpersist/nsIWebBrowserPersistDocument.idl
embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl
embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp
embedding/components/webbrowserpersist/nsWebBrowserPersist.h
toolkit/content/contentAreaUtils.js
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -17,17 +17,17 @@
 
   <commandset id="mainCommandSet">
     <command id="cmd_newNavigator" oncommand="OpenBrowserWindow()" reserved="true"/>
     <command id="cmd_handleBackspace" oncommand="BrowserHandleBackspace();" />
     <command id="cmd_handleShiftBackspace" oncommand="BrowserHandleShiftBackspace();" />
 
     <command id="cmd_newNavigatorTab" oncommand="BrowserOpenNewTabOrWindow(event);" reserved="true"/>
     <command id="Browser:OpenFile"  oncommand="BrowserOpenFileWindow();"/>
-    <command id="Browser:SavePage" oncommand="saveDocument(gBrowser.selectedBrowser.contentDocumentAsCPOW);"/>
+    <command id="Browser:SavePage" oncommand="saveBrowser(gBrowser.selectedBrowser);"/>
 
     <command id="Browser:SendLink"
              oncommand="MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser);"/>
 
     <command id="cmd_pageSetup" oncommand="PrintUtils.showPageSetup();"/>
     <command id="cmd_print" oncommand="PrintUtils.printWindow(window.gBrowser.selectedBrowser.outerWindowID, window.gBrowser.selectedBrowser);"/>
     <command id="cmd_printPreview" oncommand="PrintUtils.printPreview(PrintPreviewListener);"/>
     <command id="cmd_close" oncommand="BrowserCloseTabOrWindow()" reserved="true"/>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1791,17 +1791,17 @@ function HandleAppCommandEvent(evt) {
   case "Open":
     BrowserOpenFileWindow();
     break;
   case "Print":
     PrintUtils.printWindow(gBrowser.selectedBrowser.outerWindowID,
                            gBrowser.selectedBrowser);
     break;
   case "Save":
-    saveDocument(gBrowser.selectedBrowser.contentDocumentAsCPOW);
+    saveBrowser(gBrowser.selectedBrowser);
     break;
   case "SendMail":
     MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser);
     break;
   default:
     return;
   }
   evt.stopPropagation();
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1665,17 +1665,17 @@ nsContextMenu.prototype = {
     SocialShare.sharePage(null, { url: this.mediaURL, source: this.mediaURL }, this.target);
   },
 
   shareSelect: function CM_shareSelect() {
     SocialShare.sharePage(null, { url: this.browser.currentURI.spec, text: this.textSelected }, this.target);
   },
 
   savePageAs: function CM_savePageAs() {
-    saveDocument(this.browser.contentDocumentAsCPOW);
+    saveBrowser(this.browser);
   },
 
   saveLinkToPocket: function CM_saveLinkToPocket() {
     Pocket.savePage(this.browser, this.linkURL);
   },
 
   savePageToPocket: function CM_saveToPocket() {
     Pocket.savePage(this.browser, this.browser.currentURI.spec, this.browser.contentTitle);
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -310,18 +310,18 @@ const CustomizableWidgets = [
     id: "save-page-button",
     shortcutId: "key_savePage",
     tooltiptext: "save-page-button.tooltiptext3",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(aEvent) {
       let win = aEvent.target &&
                 aEvent.target.ownerDocument &&
                 aEvent.target.ownerDocument.defaultView;
-      if (win && typeof win.saveDocument == "function") {
-        win.saveDocument(win.gBrowser.selectedBrowser.contentDocumentAsCPOW);
+      if (win && typeof win.saveBrowser == "function") {
+        win.saveBrowser(win.gBrowser.selectedBrowser);
       }
     }
   }, {
     id: "find-button",
     shortcutId: "key_find",
     tooltiptext: "find-button.tooltiptext3",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(aEvent) {
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -88,16 +88,17 @@
 #include "GeckoProfiler.h"
 
 #include "jsapi.h"
 #include "mozilla/dom/HTMLIFrameElement.h"
 #include "nsSandboxFlags.h"
 #include "mozilla/layers/CompositorChild.h"
 
 #include "mozilla/dom/StructuredCloneUtils.h"
+#include "mozilla/WebBrowserPersistLocalDocument.h"
 
 #ifdef MOZ_XUL
 #include "nsXULPopupManager.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::hal;
 using namespace mozilla::dom;
@@ -125,16 +126,17 @@ typedef FrameMetrics::ViewID ViewID;
 
 NS_IMPL_CYCLE_COLLECTION(nsFrameLoader, mDocShell, mMessageManager, mChildMessageManager)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameLoader)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameLoader)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameLoader)
   NS_INTERFACE_MAP_ENTRY(nsIFrameLoader)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFrameLoader)
+  NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersistable)
 NS_INTERFACE_MAP_END
 
 nsFrameLoader::nsFrameLoader(Element* aOwner, bool aNetworkCreated)
   : mOwnerContent(aOwner)
   , mAppIdSentToPermissionManager(nsIScriptSecurityManager::NO_APP_ID)
   , mDetachedSubdocViews(nullptr)
   , mRemoteBrowser(nullptr)
   , mChildID(0)
@@ -2842,8 +2844,25 @@ nsFrameLoader::InitializeBrowserAPI()
       }
     }
     nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
     if (browserFrame) {
       browserFrame->InitializeBrowserAPI();
     }
   }
 }
+
+NS_IMETHODIMP
+nsFrameLoader::StartPersistence(nsIWebBrowserPersistDocumentReceiver* aRecv)
+{
+  if (mRemoteBrowser) {
+    return mRemoteBrowser->StartPersistence(aRecv);
+  }
+  if (mDocShell) {
+    nsCOMPtr<nsIDocument> doc = do_GetInterface(mDocShell);
+    NS_ENSURE_STATE(doc);
+    nsCOMPtr<nsIWebBrowserPersistDocument> pdoc =
+      new mozilla::WebBrowserPersistLocalDocument(doc);
+    aRecv->OnDocumentReady(pdoc);
+    return NS_OK;
+  }
+  return NS_ERROR_NO_CONTENT;
+}
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -19,16 +19,17 @@
 #include "nsSize.h"
 #include "nsIURI.h"
 #include "nsAutoPtr.h"
 #include "nsFrameMessageManager.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/Attributes.h"
 #include "nsStubMutationObserver.h"
 #include "Units.h"
+#include "nsIWebBrowserPersistable.h"
 
 class nsIURI;
 class nsSubDocumentFrame;
 class nsView;
 class nsIInProcessContentFrameMessageManager;
 class AutoResetInShow;
 class nsITabParent;
 class nsIDocShellTreeItem;
@@ -48,32 +49,34 @@ class RenderFrameParent;
 } // namespace layout
 } // namespace mozilla
 
 #if defined(MOZ_WIDGET_GTK)
 typedef struct _GtkWidget GtkWidget;
 #endif
 
 class nsFrameLoader final : public nsIFrameLoader,
+                            public nsIWebBrowserPersistable,
                             public nsStubMutationObserver,
                             public mozilla::dom::ipc::MessageManagerCallback
 {
   friend class AutoResetInShow;
   typedef mozilla::dom::PBrowserParent PBrowserParent;
   typedef mozilla::dom::TabParent TabParent;
   typedef mozilla::layout::RenderFrameParent RenderFrameParent;
 
 public:
   static nsFrameLoader* Create(mozilla::dom::Element* aOwner,
                                bool aNetworkCreated);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsFrameLoader, nsIFrameLoader)
   NS_DECL_NSIFRAMELOADER
   NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+  NS_DECL_NSIWEBBROWSERPERSISTABLE
   nsresult CheckForRecursiveLoad(nsIURI* aURI);
   nsresult ReallyStartLoading();
   void StartDestroy();
   void DestroyDocShell();
   void DestroyComplete();
   nsIDocShell* GetExistingDocShell() { return mDocShell; }
   mozilla::dom::EventTarget* GetTabChildGlobalAsEventTarget();
   nsresult CreateStaticClone(nsIFrameLoader* aDest);
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -10,16 +10,17 @@ include protocol PColorPicker;
 include protocol PContent;
 include protocol PContentBridge;
 include protocol PDocAccessible;
 include protocol PDocumentRenderer;
 include protocol PFilePicker;
 include protocol PIndexedDBPermissionRequest;
 include protocol PRenderFrame;
 include protocol PPluginWidget;
+include protocol PWebBrowserPersistDocument;
 include DOMTypes;
 include JavaScriptTypes;
 include URIParams;
 include BrowserConfiguration;
 
 
 using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
 using class mozilla::gfx::Matrix from "mozilla/gfx/Matrix.h";
@@ -103,27 +104,30 @@ prio(normal upto urgent) sync protocol P
 
     manages PColorPicker;
     manages PDocAccessible;
     manages PDocumentRenderer;
     manages PFilePicker;
     manages PIndexedDBPermissionRequest;
     manages PRenderFrame;
     manages PPluginWidget;
+    manages PWebBrowserPersistDocument;
 
 both:
     AsyncMessage(nsString aMessage, ClonedMessageData aData, CpowEntry[] aCpows,
                  Principal aPrincipal);
 
     /**
      * Create a layout frame (encapsulating a remote layer tree) for
      * the page that is currently loaded in the <browser>.
      */
     PRenderFrame();
 
+    PWebBrowserPersistDocument();
+
 parent:
     /**
      * Tell the parent process a new accessible document has been created.
      * aParentDoc is the accessible document it was created in if any, and
      * aParentAcc is the id of the accessible in that document the new document
      * is a child of.
      */
     PDocAccessible(nullable PDocAccessible aParentDoc, uint64_t aParentAcc);
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -30,16 +30,17 @@
 #include "mozilla/layers/APZEventState.h"
 #include "mozilla/layers/CompositorChild.h"
 #include "mozilla/layers/DoubleTapToZoom.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/ShadowLayers.h"
 #include "mozilla/layout/RenderFrameChild.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/MouseEvents.h"
+#include "mozilla/PWebBrowserPersistDocumentChild.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/unused.h"
 #include "mozIApplication.h"
 #include "nsContentUtils.h"
 #include "nsDocShell.h"
@@ -93,16 +94,17 @@
 #include "nsColorPickerProxy.h"
 #include "nsPresShell.h"
 #include "nsIAppsService.h"
 #include "nsNetUtil.h"
 #include "nsIPermissionManager.h"
 #include "nsIScriptError.h"
 #include "mozilla/EventForwards.h"
 #include "nsDeviceContext.h"
+#include "mozilla/WebBrowserPersistDocumentChild.h"
 
 #define BROWSER_ELEMENT_CHILD_SCRIPT \
     NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js")
 
 #define TABC_LOG(...)
 // #define TABC_LOG(...) printf_stderr("TABC: " __VA_ARGS__)
 
 using namespace mozilla;
@@ -3101,8 +3103,28 @@ JSObject*
 TabChildGlobal::GetGlobalJSObject()
 {
   NS_ENSURE_TRUE(mTabChild, nullptr);
   nsCOMPtr<nsIXPConnectJSObjectHolder> ref = mTabChild->GetGlobal();
   NS_ENSURE_TRUE(ref, nullptr);
   return ref->GetJSObject();
 }
 
+PWebBrowserPersistDocumentChild*
+TabChild::AllocPWebBrowserPersistDocumentChild()
+{
+  return new WebBrowserPersistDocumentChild();
+}
+
+bool
+TabChild::RecvPWebBrowserPersistDocumentConstructor(PWebBrowserPersistDocumentChild *aActor)
+{
+  nsCOMPtr<nsIDocument> doc = GetDocument();
+  static_cast<WebBrowserPersistDocumentChild*>(aActor)->Start(doc);
+  return true;
+}
+
+bool
+TabChild::DeallocPWebBrowserPersistDocumentChild(PWebBrowserPersistDocumentChild* aActor)
+{
+  delete aActor;
+  return true;
+}
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -489,16 +489,20 @@ public:
     bool ParentIsActive()
     {
       return mParentIsActive;
     }
     bool AsyncPanZoomEnabled() { return mAsyncPanZoomEnabled; }
 
     virtual ScreenIntSize GetInnerSize() override;
 
+    virtual PWebBrowserPersistDocumentChild* AllocPWebBrowserPersistDocumentChild() override;
+    virtual bool RecvPWebBrowserPersistDocumentConstructor(PWebBrowserPersistDocumentChild *aActor) override;
+    virtual bool DeallocPWebBrowserPersistDocumentChild(PWebBrowserPersistDocumentChild* aActor) override;
+
 protected:
     virtual ~TabChild();
 
     virtual PRenderFrameChild* AllocPRenderFrameChild() override;
     virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) override;
     virtual bool RecvDestroy() override;
     virtual bool RecvSetUpdateHitRegion(const bool& aEnabled) override;
     virtual bool RecvSetIsDocShellActive(const bool& aIsActive) override;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -90,16 +90,17 @@
 #include "gfxPrefs.h"
 #include "nsILoginManagerPrompter.h"
 #include "nsPIWindowRoot.h"
 #include "nsIAuthPrompt2.h"
 #include "gfxDrawable.h"
 #include "ImageOps.h"
 #include "UnitTransforms.h"
 #include <algorithm>
+#include "mozilla/WebBrowserPersistDocumentParent.h"
 
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 using namespace mozilla::services;
 using namespace mozilla::widget;
 using namespace mozilla::jsipc;
@@ -251,17 +252,18 @@ namespace mozilla {
 namespace dom {
 
 TabParent::LayerToTabParentTable* TabParent::sLayerToTabParentTable = nullptr;
 
 NS_IMPL_ISUPPORTS(TabParent,
                   nsITabParent,
                   nsIAuthPromptProvider,
                   nsISecureBrowserUI,
-                  nsISupportsWeakReference)
+                  nsISupportsWeakReference,
+                  nsIWebBrowserPersistable)
 
 TabParent::TabParent(nsIContentParent* aManager,
                      const TabId& aTabId,
                      const TabContext& aContext,
                      uint32_t aChromeFlags)
   : TabContext(aContext)
   , mFrameElement(nullptr)
   , mRect(0, 0, 0, 0)
@@ -3340,16 +3342,39 @@ TabParent::TakeDragVisualization(RefPtr<
 
 bool
 TabParent::AsyncPanZoomEnabled() const
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   return widget && widget->AsyncPanZoomEnabled();
 }
 
+PWebBrowserPersistDocumentParent*
+TabParent::AllocPWebBrowserPersistDocumentParent()
+{
+  return new WebBrowserPersistDocumentParent();
+}
+
+bool
+TabParent::DeallocPWebBrowserPersistDocumentParent(PWebBrowserPersistDocumentParent* aActor)
+{
+  delete aActor;
+  return true;
+}
+
+NS_IMETHODIMP
+TabParent::StartPersistence(nsIWebBrowserPersistDocumentReceiver* aRecv)
+{
+  auto* actor = new WebBrowserPersistDocumentParent();
+  actor->SetOnReady(aRecv);
+  return SendPWebBrowserPersistDocumentConstructor(actor)
+    ? NS_OK : NS_ERROR_FAILURE;
+  // (The actor will be destroyed on constructor failure.)
+}
+
 NS_IMETHODIMP
 FakeChannel::OnAuthAvailable(nsISupports *aContext, nsIAuthInformation *aAuthInfo)
 {
   nsAuthInformationHolder* holder =
     static_cast<nsAuthInformationHolder*>(aAuthInfo);
 
   if (!net::gNeckoChild->SendOnAuthAvailable(mCallbackId,
                                              holder->User(),
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -17,16 +17,17 @@
 #include "mozilla/dom/File.h"
 #include "mozilla/RefPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIAuthPromptProvider.h"
 #include "nsIBrowserDOMWindow.h"
 #include "nsIDOMEventListener.h"
 #include "nsISecureBrowserUI.h"
 #include "nsITabParent.h"
+#include "nsIWebBrowserPersistable.h"
 #include "nsIXULBrowserWindow.h"
 #include "nsRefreshDriver.h"
 #include "nsWeakReference.h"
 #include "Units.h"
 #include "nsIWidget.h"
 
 class nsFrameLoader;
 class nsIFrameLoader;
@@ -71,16 +72,17 @@ struct StructuredCloneData;
 class TabParent final : public PBrowserParent
                       , public nsIDOMEventListener
                       , public nsITabParent
                       , public nsIAuthPromptProvider
                       , public nsISecureBrowserUI
                       , public nsSupportsWeakReference
                       , public TabContext
                       , public nsAPostRefreshObserver
+                      , public nsIWebBrowserPersistable
 {
     typedef mozilla::dom::ClonedMessageData ClonedMessageData;
     typedef mozilla::OwningSerializedStructuredCloneBuffer OwningSerializedStructuredCloneBuffer;
 
     virtual ~TabParent();
 
 public:
     // nsITabParent
@@ -363,16 +365,17 @@ public:
                                       PIndexedDBPermissionRequestParent* aActor)
                                       override;
 
     bool GetGlobalJSObject(JSContext* cx, JSObject** globalp);
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIAUTHPROMPTPROVIDER
     NS_DECL_NSISECUREBROWSERUI
+    NS_DECL_NSIWEBBROWSERPERSISTABLE
 
     bool HandleQueryContentEvent(mozilla::WidgetQueryContentEvent& aEvent);
     bool SendCompositionEvent(mozilla::WidgetCompositionEvent& event);
     bool SendSelectionEvent(mozilla::WidgetSelectionEvent& event);
 
     static TabParent* GetFrom(nsFrameLoader* aFrameLoader);
     static TabParent* GetFrom(nsIFrameLoader* aFrameLoader);
     static TabParent* GetFrom(nsITabParent* aTabParent);
@@ -425,16 +428,20 @@ public:
                           const uint32_t& aStride, const uint8_t& aFormat,
                           const int32_t& aDragAreaX, const int32_t& aDragAreaY) override;
 
     void AddInitialDnDDataTo(DataTransfer* aDataTransfer);
 
     void TakeDragVisualization(RefPtr<mozilla::gfx::SourceSurface>& aSurface,
                                int32_t& aDragAreaX, int32_t& aDragAreaY);
     layout::RenderFrameParent* GetRenderFrame();
+
+    virtual PWebBrowserPersistDocumentParent* AllocPWebBrowserPersistDocumentParent() override;
+    virtual bool DeallocPWebBrowserPersistDocumentParent(PWebBrowserPersistDocumentParent* aActor) override;
+
 protected:
     bool ReceiveMessage(const nsString& aMessage,
                         bool aSync,
                         const StructuredCloneData* aCloneData,
                         mozilla::jsipc::CpowHolder* aCpows,
                         nsIPrincipal* aPrincipal,
                         nsTArray<OwningSerializedStructuredCloneBuffer>* aJSONRetVal = nullptr);
 
--- a/embedding/browser/nsWebBrowser.cpp
+++ b/embedding/browser/nsWebBrowser.cpp
@@ -1060,17 +1060,17 @@ nsWebBrowser::SaveChannel(nsIChannel* aC
   rv = mPersist->SaveChannel(aChannel, aFile);
   if (NS_FAILED(rv)) {
     mPersist = nullptr;
   }
   return rv;
 }
 
 NS_IMETHODIMP
-nsWebBrowser::SaveDocument(nsIDOMDocument* aDocument,
+nsWebBrowser::SaveDocument(nsISupports* aDocumentish,
                            nsISupports* aFile,
                            nsISupports* aDataPath,
                            const char* aOutputContentType,
                            uint32_t aEncodingFlags,
                            uint32_t aWrapColumn)
 {
   if (mPersist) {
     uint32_t currentState;
@@ -1081,21 +1081,23 @@ nsWebBrowser::SaveDocument(nsIDOMDocumen
       // You can't save again until the last save has completed
       return NS_ERROR_FAILURE;
     }
   }
 
   // Use the specified DOM document, or if none is specified, the one
   // attached to the web browser.
 
-  nsCOMPtr<nsIDOMDocument> doc;
-  if (aDocument) {
-    doc = do_QueryInterface(aDocument);
+  nsCOMPtr<nsISupports> doc;
+  if (aDocumentish) {
+    doc = aDocumentish;
   } else {
-    GetDocument(getter_AddRefs(doc));
+    nsCOMPtr<nsIDOMDocument> domDoc;
+    GetDocument(getter_AddRefs(domDoc));
+    doc = domDoc.forget();
   }
   if (!doc) {
     return NS_ERROR_FAILURE;
   }
 
   // Create a throwaway persistence object to do the work
   nsresult rv;
   mPersist = do_CreateInstance(NS_WEBBROWSERPERSIST_CONTRACTID, &rv);
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/PWebBrowserPersistDocument.ipdl
@@ -0,0 +1,90 @@
+/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PBrowser;
+include protocol PWebBrowserPersistResources;
+include protocol PWebBrowserPersistSerialize;
+
+include InputStreamParams;
+
+namespace mozilla {
+
+// nsIWebBrowserPersistDocument has attributes which can be read
+// synchronously.  To avoid using sync IPC for them, the actor sends
+// this structure from the child to the parent before the parent actor
+// is exposed to XPCOM.
+struct WebBrowserPersistDocumentAttrs {
+  bool isPrivate;
+  nsCString documentURI;
+  nsCString baseURI;
+  nsCString contentType;
+  nsCString characterSet;
+  nsString title;
+  nsString referrer;
+  nsString contentDisposition;
+  uint32_t cacheKey;
+  uint32_t persistFlags;
+};
+
+// IPDL doesn't have tuples, so this gives the pair of strings from
+// nsIWebBrowserPersistURIMap::getURIMapping a name.
+struct WebBrowserPersistURIMapEntry {
+  nsCString mapFrom;
+  nsCString mapTo;
+};
+
+// nsIWebBrowserPersistURIMap is just copied over IPC as one of these,
+// not proxied, to simplify the protocol.
+struct WebBrowserPersistURIMap {
+  WebBrowserPersistURIMapEntry[] mapURIs;
+  nsCString targetBaseURI;
+};
+
+// This remotes nsIWebBrowserPersistDocument and its visitors.  The
+// lifecycle is a little complicated: the initial document is
+// constructed parent->child, but subdocuments are constructed
+// child->parent and then passed back.  Subdocuments aren't subactors,
+// because that would impose a lifetime relationship that doesn't
+// exist in the XPIDL; instead they're all managed by the enclosing
+// PBrowser (== TabParent/TabChild).
+protocol PWebBrowserPersistDocument {
+  manager PBrowser;
+  manages PWebBrowserPersistResources;
+  manages PWebBrowserPersistSerialize;
+
+parent:
+  // The actor isn't exposed to XPCOM until after it gets one of these
+  // two messages; see also the state transition rules.  The message
+  // is either a response to the constructor (if it was parent->child)
+  // or sent after it (if it was child->parent).
+  Attributes(WebBrowserPersistDocumentAttrs aAttrs,
+             OptionalInputStreamParams postData,
+             FileDescriptor[] postFiles);
+  InitFailure(nsresult aStatus);
+
+child:
+  SetPersistFlags(uint32_t aNewFlags);
+  PWebBrowserPersistResources();
+  PWebBrowserPersistSerialize(WebBrowserPersistURIMap aMap,
+                              nsCString aRequestedContentType,
+                              uint32_t aEncoderFlags,
+                              uint32_t aWrapColumn);
+  __delete__();
+
+state START:
+  recv Attributes goto MAIN;
+  recv InitFailure goto FAILED;
+
+state MAIN:
+  send SetPersistFlags goto MAIN;
+  send PWebBrowserPersistResources goto MAIN;
+  send PWebBrowserPersistSerialize goto MAIN;
+  send __delete__;
+
+state FAILED:
+  send __delete__;
+};
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/PWebBrowserPersistResources.ipdl
@@ -0,0 +1,26 @@
+/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PWebBrowserPersistDocument;
+
+namespace mozilla {
+
+// == nsIWebBrowserPersistResourceVisitor
+protocol PWebBrowserPersistResources {
+  manager PWebBrowserPersistDocument;
+
+parent:
+  VisitResource(nsCString aURI);
+
+  // The actor sent here is in the START state; the parent-side
+  // receiver will have to wait for it to enter the MAIN state
+  // before exposing it with a visitDocument call.
+  VisitDocument(PWebBrowserPersistDocument aSubDocument);
+
+  // This reflects the endVisit method.
+  __delete__(nsresult aStatus);
+};
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/PWebBrowserPersistSerialize.ipdl
@@ -0,0 +1,29 @@
+/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PWebBrowserPersistDocument;
+
+namespace mozilla {
+
+// This actor represents both an nsIWebBrowserPersistWriteCompletion
+// and the nsIOutputStream passed with it to the writeContent method.
+protocol PWebBrowserPersistSerialize {
+  manager PWebBrowserPersistDocument;
+
+parent:
+  // This sends the data with no flow control, so the parent could
+  // wind up buffering an arbitrarily large amount of data...  but
+  // it's a serialized DOM that's already in memory as DOM nodes, so
+  // this is at worst just a constant-factor increase in memory usage.
+  // Also, Chromium does the same thing; see
+  // content::RenderViewImpl::didSerializeDataForFrame.
+  WriteData(uint8_t[] aData);
+
+  // This is the onFinish method.
+  __delete__(nsCString aContentType,
+             nsresult aStatus);
+};
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.cpp
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistDocumentChild.h"
+
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "nsIDocument.h"
+#include "nsIInputStream.h"
+#include "WebBrowserPersistLocalDocument.h"
+#include "WebBrowserPersistResourcesChild.h"
+#include "WebBrowserPersistSerializeChild.h"
+
+namespace mozilla {
+
+WebBrowserPersistDocumentChild::WebBrowserPersistDocumentChild()
+{
+}
+
+WebBrowserPersistDocumentChild::~WebBrowserPersistDocumentChild()
+{
+}
+
+void
+WebBrowserPersistDocumentChild::Start(nsIDocument* aDocument)
+{
+    nsRefPtr<WebBrowserPersistLocalDocument> doc;
+    if (aDocument) {
+        doc = new WebBrowserPersistLocalDocument(aDocument);
+    }
+    Start(doc);
+}
+
+void
+WebBrowserPersistDocumentChild::Start(nsIWebBrowserPersistDocument* aDocument)
+{
+    MOZ_ASSERT(!mDocument);
+    if (!aDocument) {
+        SendInitFailure(NS_ERROR_FAILURE);
+        return;
+    }
+
+    WebBrowserPersistDocumentAttrs attrs;
+    nsCOMPtr<nsIInputStream> postDataStream;
+    OptionalInputStreamParams postData;
+    nsTArray<FileDescriptor> postFiles;
+#define ENSURE(e) do {           \
+        nsresult rv = (e);       \
+        if (NS_FAILED(rv)) {     \
+            SendInitFailure(rv); \
+            return;              \
+        }                        \
+    } while(0)
+    ENSURE(aDocument->GetIsPrivate(&(attrs.isPrivate())));
+    ENSURE(aDocument->GetDocumentURI(attrs.documentURI()));
+    ENSURE(aDocument->GetBaseURI(attrs.baseURI()));
+    ENSURE(aDocument->GetContentType(attrs.contentType()));
+    ENSURE(aDocument->GetCharacterSet(attrs.characterSet()));
+    ENSURE(aDocument->GetTitle(attrs.title()));
+    ENSURE(aDocument->GetReferrer(attrs.referrer()));
+    ENSURE(aDocument->GetContentDisposition(attrs.contentDisposition()));
+    ENSURE(aDocument->GetCacheKey(&(attrs.cacheKey())));
+    ENSURE(aDocument->GetPersistFlags(&(attrs.persistFlags())));
+    ENSURE(aDocument->GetPostData(getter_AddRefs(postDataStream)));
+    ipc::SerializeInputStream(postDataStream,
+                              postData,
+                              postFiles);
+#undef ENSURE
+    mDocument = aDocument;
+    SendAttributes(attrs, postData, postFiles);
+}
+
+bool
+WebBrowserPersistDocumentChild::RecvSetPersistFlags(const uint32_t& aNewFlags)
+{
+    mDocument->SetPersistFlags(aNewFlags);
+    return true;
+}
+
+PWebBrowserPersistResourcesChild*
+WebBrowserPersistDocumentChild::AllocPWebBrowserPersistResourcesChild()
+{
+    auto* actor = new WebBrowserPersistResourcesChild();
+    NS_ADDREF(actor);
+    return actor;
+}
+
+bool
+WebBrowserPersistDocumentChild::RecvPWebBrowserPersistResourcesConstructor(PWebBrowserPersistResourcesChild* aActor)
+{
+    nsRefPtr<WebBrowserPersistResourcesChild> visitor =
+        static_cast<WebBrowserPersistResourcesChild*>(aActor);
+    nsresult rv = mDocument->ReadResources(visitor);
+    if (NS_FAILED(rv)) {
+        visitor->EndVisit(mDocument, rv);
+    }
+    return true;
+}
+
+bool
+WebBrowserPersistDocumentChild::DeallocPWebBrowserPersistResourcesChild(PWebBrowserPersistResourcesChild* aActor)
+{
+    auto* castActor =
+        static_cast<WebBrowserPersistResourcesChild*>(aActor);
+    NS_RELEASE(castActor);
+    return true;
+}
+
+PWebBrowserPersistSerializeChild*
+WebBrowserPersistDocumentChild::AllocPWebBrowserPersistSerializeChild(
+            const WebBrowserPersistURIMap& aMap,
+            const nsCString& aRequestedContentType,
+            const uint32_t& aEncoderFlags,
+            const uint32_t& aWrapColumn)
+{
+    auto* actor = new WebBrowserPersistSerializeChild(aMap);
+    NS_ADDREF(actor);
+    return actor;
+}
+
+bool
+WebBrowserPersistDocumentChild::RecvPWebBrowserPersistSerializeConstructor(
+            PWebBrowserPersistSerializeChild* aActor,
+            const WebBrowserPersistURIMap& aMap,
+            const nsCString& aRequestedContentType,
+            const uint32_t& aEncoderFlags,
+            const uint32_t& aWrapColumn)
+{
+    auto* castActor =
+        static_cast<WebBrowserPersistSerializeChild*>(aActor);
+    // This actor performs the roles of: completion, URI map, and output stream.
+    nsresult rv = mDocument->WriteContent(castActor,
+                                          castActor,
+                                          aRequestedContentType,
+                                          aEncoderFlags,
+                                          aWrapColumn,
+                                          castActor);
+    if (NS_FAILED(rv)) {
+        castActor->OnFinish(mDocument, castActor, aRequestedContentType, rv);
+    }
+    return true;
+}
+
+bool
+WebBrowserPersistDocumentChild::DeallocPWebBrowserPersistSerializeChild(PWebBrowserPersistSerializeChild* aActor)
+{
+    auto* castActor =
+        static_cast<WebBrowserPersistSerializeChild*>(aActor);
+    NS_RELEASE(castActor);
+    return true;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistDocumentChild_h__
+#define WebBrowserPersistDocumentChild_h__
+
+#include "mozilla/PWebBrowserPersistDocumentChild.h"
+#include "nsCOMPtr.h"
+#include "nsIWebBrowserPersistDocument.h"
+
+class nsIDocument;
+
+namespace mozilla {
+
+class WebBrowserPersistDocumentChild final
+    : public PWebBrowserPersistDocumentChild
+{
+public:
+    WebBrowserPersistDocumentChild();
+    ~WebBrowserPersistDocumentChild();
+
+    // This sends either Attributes or InitFailure and thereby causes
+    // the actor to leave the START state.
+    void Start(nsIWebBrowserPersistDocument* aDocument);
+    void Start(nsIDocument* aDocument);
+
+    virtual bool
+    RecvSetPersistFlags(const uint32_t& aNewFlags) override;
+
+    virtual PWebBrowserPersistResourcesChild*
+    AllocPWebBrowserPersistResourcesChild() override;
+    virtual bool
+    RecvPWebBrowserPersistResourcesConstructor(PWebBrowserPersistResourcesChild* aActor) override;
+    virtual bool
+    DeallocPWebBrowserPersistResourcesChild(PWebBrowserPersistResourcesChild* aActor) override;
+
+    virtual PWebBrowserPersistSerializeChild*
+    AllocPWebBrowserPersistSerializeChild(
+            const WebBrowserPersistURIMap& aMap,
+            const nsCString& aRequestedContentType,
+            const uint32_t& aEncoderFlags,
+            const uint32_t& aWrapColumn) override;
+    virtual bool
+    RecvPWebBrowserPersistSerializeConstructor(
+            PWebBrowserPersistSerializeChild* aActor,
+            const WebBrowserPersistURIMap& aMap,
+            const nsCString& aRequestedContentType,
+            const uint32_t& aEncoderFlags,
+            const uint32_t& aWrapColumn) override;
+    virtual bool
+    DeallocPWebBrowserPersistSerializeChild(PWebBrowserPersistSerializeChild* aActor) override;
+
+private:
+    nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistDocumentChild_h__
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.cpp
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistDocumentParent.h"
+
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "nsIInputStream.h"
+#include "nsThreadUtils.h"
+#include "WebBrowserPersistResourcesParent.h"
+#include "WebBrowserPersistSerializeParent.h"
+#include "WebBrowserPersistRemoteDocument.h"
+
+namespace mozilla {
+
+WebBrowserPersistDocumentParent::WebBrowserPersistDocumentParent()
+: mReflection(nullptr)
+{
+}
+
+void
+WebBrowserPersistDocumentParent::SetOnReady(nsIWebBrowserPersistDocumentReceiver* aOnReady)
+{
+    MOZ_ASSERT(aOnReady);
+    MOZ_ASSERT(!mOnReady);
+    MOZ_ASSERT(!mReflection);
+    mOnReady = aOnReady;
+}
+
+void
+WebBrowserPersistDocumentParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+    if (mReflection) {
+        mReflection->ActorDestroy();
+        mReflection = nullptr;
+    }
+    if (mOnReady) {
+        mOnReady->OnError(NS_ERROR_FAILURE);
+        mOnReady = nullptr;
+    }
+}
+
+WebBrowserPersistDocumentParent::~WebBrowserPersistDocumentParent()
+{
+    MOZ_RELEASE_ASSERT(!mReflection);
+    MOZ_ASSERT(!mOnReady);
+}
+
+bool
+WebBrowserPersistDocumentParent::RecvAttributes(const Attrs& aAttrs,
+                                                const OptionalInputStreamParams& aPostData,
+                                                nsTArray<FileDescriptor>&& aPostFiles)
+{
+    // Deserialize the postData unconditionally so that fds aren't leaked.
+    nsCOMPtr<nsIInputStream> postData =
+        ipc::DeserializeInputStream(aPostData, aPostFiles);
+    if (!mOnReady || mReflection) {
+        return false;
+    }
+    mReflection = new WebBrowserPersistRemoteDocument(this, aAttrs, postData);
+    nsRefPtr<WebBrowserPersistRemoteDocument> reflection = mReflection;
+    mOnReady->OnDocumentReady(reflection);
+    mOnReady = nullptr;
+    return true;
+}
+
+bool
+WebBrowserPersistDocumentParent::RecvInitFailure(const nsresult& aFailure)
+{
+    if (!mOnReady || mReflection) {
+        return false;
+    }
+    mOnReady->OnError(aFailure);
+    mOnReady = nullptr;
+    // Warning: Send__delete__ deallocates this object.
+    return Send__delete__(this);
+}
+
+PWebBrowserPersistResourcesParent*
+WebBrowserPersistDocumentParent::AllocPWebBrowserPersistResourcesParent()
+{
+    MOZ_CRASH("Don't use this; construct the actor directly and AddRef.");
+    return nullptr;
+}
+
+bool
+WebBrowserPersistDocumentParent::DeallocPWebBrowserPersistResourcesParent(PWebBrowserPersistResourcesParent* aActor)
+{
+    // Turn the ref held by IPC back into an nsRefPtr.
+    nsRefPtr<WebBrowserPersistResourcesParent> actor =
+        already_AddRefed<WebBrowserPersistResourcesParent>(
+            static_cast<WebBrowserPersistResourcesParent*>(aActor));
+    return true;
+}
+
+PWebBrowserPersistSerializeParent*
+WebBrowserPersistDocumentParent::AllocPWebBrowserPersistSerializeParent(
+        const WebBrowserPersistURIMap& aMap,
+        const nsCString& aRequestedContentType,
+        const uint32_t& aEncoderFlags,
+        const uint32_t& aWrapColumn)
+{
+    MOZ_CRASH("Don't use this; construct the actor directly.");
+    return nullptr;
+}
+
+bool
+WebBrowserPersistDocumentParent::DeallocPWebBrowserPersistSerializeParent(PWebBrowserPersistSerializeParent* aActor)
+{
+    delete aActor;
+    return true;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistDocumentParent_h__
+#define WebBrowserPersistDocumentParent_h__
+
+#include "mozilla/Maybe.h"
+#include "mozilla/PWebBrowserPersistDocumentParent.h"
+#include "nsCOMPtr.h"
+#include "nsIWebBrowserPersistDocument.h"
+
+// This class is the IPC half of the glue between the
+// nsIWebBrowserPersistDocument interface and a remote document.  When
+// (and if) it receives the Attributes message it constructs an
+// WebBrowserPersistRemoteDocument and releases it into the XPCOM
+// universe; otherwise, it invokes the document receiver's error
+// callback.
+//
+// This object's lifetime is the normal IPC lifetime; on destruction,
+// it calls its XPCOM reflection (if it exists yet) to remove that
+// reference.  Normal deletion occurs when the XPCOM object is being
+// destroyed or after an InitFailure is received and handled.
+//
+// See also: TabParent::StartPersistence.
+
+namespace mozilla {
+
+class WebBrowserPersistRemoteDocument;
+
+class WebBrowserPersistDocumentParent final
+    : public PWebBrowserPersistDocumentParent
+{
+public:
+    WebBrowserPersistDocumentParent();
+    virtual ~WebBrowserPersistDocumentParent();
+
+    // Set a callback to be invoked when the actor leaves the START
+    // state.  This method must be called exactly once while the actor
+    // is still in the START state (or is unconstructed).
+    void SetOnReady(nsIWebBrowserPersistDocumentReceiver* aOnReady);
+
+    using Attrs = WebBrowserPersistDocumentAttrs;
+
+    // IPDL methods:
+    virtual bool
+    RecvAttributes(const Attrs& aAttrs,
+                   const OptionalInputStreamParams& aPostData,
+                   nsTArray<FileDescriptor>&& aPostFiles) override;
+    virtual bool
+    RecvInitFailure(const nsresult& aFailure) override;
+
+    virtual PWebBrowserPersistResourcesParent*
+    AllocPWebBrowserPersistResourcesParent() override;
+    virtual bool
+    DeallocPWebBrowserPersistResourcesParent(PWebBrowserPersistResourcesParent* aActor) override;
+
+    virtual PWebBrowserPersistSerializeParent*
+    AllocPWebBrowserPersistSerializeParent(
+            const WebBrowserPersistURIMap& aMap,
+            const nsCString& aRequestedContentType,
+            const uint32_t& aEncoderFlags,
+            const uint32_t& aWrapColumn) override;
+    virtual bool
+    DeallocPWebBrowserPersistSerializeParent(PWebBrowserPersistSerializeParent* aActor) override;
+
+    virtual void
+    ActorDestroy(ActorDestroyReason aWhy) override;
+private:
+    // This is reset to nullptr when the callback is invoked.
+    nsCOMPtr<nsIWebBrowserPersistDocumentReceiver> mOnReady;
+    WebBrowserPersistRemoteDocument* mReflection;
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistDocumentParent_h__
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp
@@ -0,0 +1,1451 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistLocalDocument.h"
+#include "WebBrowserPersistDocumentParent.h"
+
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/HTMLSharedElement.h"
+#include "mozilla/dom/HTMLSharedObjectElement.h"
+#include "mozilla/dom/TabParent.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsContentCID.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsFrameLoader.h"
+#include "nsIComponentRegistrar.h"
+#include "nsIContent.h"
+#include "nsIDOMAttr.h"
+#include "nsIDOMComment.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMHTMLAnchorElement.h"
+#include "nsIDOMHTMLAppletElement.h"
+#include "nsIDOMHTMLAreaElement.h"
+#include "nsIDOMHTMLBaseElement.h"
+#include "nsIDOMHTMLCollection.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIDOMHTMLEmbedElement.h"
+#include "nsIDOMHTMLFrameElement.h"
+#include "nsIDOMHTMLIFrameElement.h"
+#include "nsIDOMHTMLImageElement.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsIDOMHTMLLinkElement.h"
+#include "nsIDOMHTMLMediaElement.h"
+#include "nsIDOMHTMLObjectElement.h"
+#include "nsIDOMHTMLOptionElement.h"
+#include "nsIDOMHTMLScriptElement.h"
+#include "nsIDOMHTMLSourceElement.h"
+#include "nsIDOMHTMLTextAreaElement.h"
+#include "nsIDOMMozNamedAttrMap.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMNodeFilter.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMProcessingInstruction.h"
+#include "nsIDOMTreeWalker.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.h"
+#include "nsIDocumentEncoder.h"
+#include "nsILoadContext.h"
+#include "nsIProtocolHandler.h"
+#include "nsISHEntry.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITabParent.h"
+#include "nsIWebBrowserPersist.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebPageDescriptor.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebBrowserPersistLocalDocument)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebBrowserPersistLocalDocument)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebBrowserPersistLocalDocument)
+  NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersistDocument)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(WebBrowserPersistLocalDocument, mDocument)
+
+
+WebBrowserPersistLocalDocument::WebBrowserPersistLocalDocument(nsIDocument* aDocument)
+: mDocument(aDocument)
+, mPersistFlags(0)
+{
+    MOZ_ASSERT(mDocument);
+}
+
+WebBrowserPersistLocalDocument::~WebBrowserPersistLocalDocument()
+{
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::SetPersistFlags(uint32_t aFlags)
+{
+    mPersistFlags = aFlags;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetPersistFlags(uint32_t* aFlags)
+{
+    *aFlags = mPersistFlags;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetIsPrivate(bool* aIsPrivate)
+{
+    nsCOMPtr<nsILoadContext> privacyContext = mDocument->GetLoadContext();
+    *aIsPrivate = privacyContext && privacyContext->UsePrivateBrowsing();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetDocumentURI(nsACString& aURISpec)
+{
+    nsCOMPtr<nsIURI> uri = mDocument->GetDocumentURI();
+    if (!uri) {
+        return NS_ERROR_UNEXPECTED;
+    }
+    return uri->GetSpec(aURISpec);
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetBaseURI(nsACString& aURISpec)
+{
+    nsCOMPtr<nsIURI> uri = GetBaseURI();
+    if (!uri) {
+        return NS_ERROR_UNEXPECTED;
+    }
+    return uri->GetSpec(aURISpec);
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetContentType(nsACString& aContentType)
+{
+    nsAutoString utf16Type;
+    nsresult rv;
+
+    rv = mDocument->GetContentType(utf16Type);
+    NS_ENSURE_SUCCESS(rv, rv);
+    aContentType = NS_ConvertUTF16toUTF8(utf16Type);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetCharacterSet(nsACString& aCharSet)
+{
+    aCharSet = GetCharacterSet();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetTitle(nsAString& aTitle)
+{
+    nsAutoString titleBuffer;
+    mDocument->GetTitle(titleBuffer);
+    aTitle = titleBuffer;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetReferrer(nsAString& aReferrer)
+{
+    mDocument->GetReferrer(aReferrer);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetContentDisposition(nsAString& aCD)
+{
+    nsCOMPtr<nsIDOMWindow> window = mDocument->GetDefaultView();
+    NS_ENSURE_STATE(window);
+    nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window);
+    NS_ENSURE_STATE(utils);
+    return utils->GetDocumentMetadata(
+        NS_LITERAL_STRING("content-disposition"), aCD);
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetCacheKey(uint32_t* aKey)
+{
+    nsCOMPtr<nsISHEntry> history;
+    nsresult rv = GetHistory(getter_AddRefs(history));
+    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ENSURE_STATE(history);
+    nsCOMPtr<nsISupports> abstractKey;
+    rv = history->GetCacheKey(getter_AddRefs(abstractKey));
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (!abstractKey) {
+        *aKey = 0;
+        return NS_OK;
+    }
+    nsCOMPtr<nsISupportsPRUint32> u32 = do_QueryInterface(abstractKey);
+    if (NS_WARN_IF(!u32)) {
+        *aKey = 0;
+        return NS_OK;
+    }
+    return u32->GetData(aKey);
+}
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::GetPostData(nsIInputStream** aStream)
+{
+    nsCOMPtr<nsISHEntry> history;
+    nsresult rv = GetHistory(getter_AddRefs(history));
+    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ENSURE_STATE(history);
+    return history->GetPostData(aStream);
+}
+
+nsresult
+WebBrowserPersistLocalDocument::GetHistory(nsISHEntry** aHistory)
+{
+    nsCOMPtr<nsIDOMWindow> window = mDocument->GetDefaultView();
+    NS_ENSURE_STATE(window);
+    nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(window);
+    NS_ENSURE_STATE(webNav);
+    nsCOMPtr<nsIWebPageDescriptor> desc = do_QueryInterface(webNav);
+    NS_ENSURE_STATE(desc);
+    nsCOMPtr<nsISupports> curDesc;
+    nsresult rv = desc->GetCurrentDescriptor(getter_AddRefs(curDesc));
+    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ENSURE_STATE(curDesc);
+    nsCOMPtr<nsISHEntry> history = do_QueryInterface(curDesc);
+    history.forget(aHistory);
+    return NS_OK;
+}
+
+const nsCString&
+WebBrowserPersistLocalDocument::GetCharacterSet() const
+{
+    return mDocument->GetDocumentCharacterSet();
+}
+
+uint32_t
+WebBrowserPersistLocalDocument::GetPersistFlags() const
+{
+    return mPersistFlags;
+}
+
+
+already_AddRefed<nsIURI>
+WebBrowserPersistLocalDocument::GetBaseURI() const
+{
+    return mDocument->GetBaseURI();
+}
+
+
+namespace {
+
+// Helper class for ReadResources().
+class ResourceReader final : public nsIWebBrowserPersistDocumentReceiver {
+public:
+    ResourceReader(WebBrowserPersistLocalDocument* aParent,
+                   nsIWebBrowserPersistResourceVisitor* aVisitor);
+    nsresult OnWalkDOMNode(nsIDOMNode* aNode);
+
+    // This is called both to indicate the end of the document walk
+    // and when a subdocument is (maybe asynchronously) sent to the
+    // visitor.  The call to EndVisit needs to happen after both of
+    // those have finished.
+    void DocumentDone(nsresult aStatus);
+
+    NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER
+    NS_DECL_ISUPPORTS
+
+private:
+    nsRefPtr<WebBrowserPersistLocalDocument> mParent;
+    nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor;
+    nsCOMPtr<nsIURI> mCurrentBaseURI;
+    uint32_t mPersistFlags;
+
+    // The number of DocumentDone calls after which EndVisit will be
+    // called on the visitor.  Counts the main document if it's still
+    // being walked and any outstanding asynchronous subdocument
+    // StartPersistence calls.
+    size_t mOutstandingDocuments;
+    // Collects the status parameters to DocumentDone calls.
+    nsresult mEndStatus;
+
+    nsresult OnWalkURI(const nsACString& aURISpec);
+    nsresult OnWalkURI(nsIURI* aURI);
+    nsresult OnWalkAttribute(nsIDOMNode* aNode,
+                             const char* aAttribute,
+                             const char* aNamespaceURI = "");
+    nsresult OnWalkSubframe(nsIDOMNode* aNode);
+
+    bool IsFlagSet(uint32_t aFlag) const {
+        return mParent->GetPersistFlags() & aFlag;
+    }
+
+    ~ResourceReader();
+
+    using IWBP = nsIWebBrowserPersist;
+};
+
+NS_IMPL_ISUPPORTS(ResourceReader, nsIWebBrowserPersistDocumentReceiver)
+
+ResourceReader::ResourceReader(WebBrowserPersistLocalDocument* aParent,
+                               nsIWebBrowserPersistResourceVisitor* aVisitor)
+: mParent(aParent)
+, mVisitor(aVisitor)
+, mCurrentBaseURI(aParent->GetBaseURI())
+, mPersistFlags(aParent->GetPersistFlags())
+, mOutstandingDocuments(1)
+, mEndStatus(NS_OK)
+{
+    MOZ_ASSERT(mCurrentBaseURI);
+}
+
+ResourceReader::~ResourceReader()
+{
+    MOZ_ASSERT(mOutstandingDocuments == 0);
+}
+
+void
+ResourceReader::DocumentDone(nsresult aStatus)
+{
+    MOZ_ASSERT(mOutstandingDocuments > 0);
+    if (NS_SUCCEEDED(mEndStatus)) {
+        mEndStatus = aStatus;
+    }
+    if (--mOutstandingDocuments == 0) {
+        mVisitor->EndVisit(mParent, mEndStatus);
+    }
+}
+
+nsresult
+ResourceReader::OnWalkSubframe(nsIDOMNode* aNode)
+{
+    nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(aNode);
+    NS_ENSURE_STATE(loaderOwner);
+    nsRefPtr<nsFrameLoader> loader = loaderOwner->GetFrameLoader();
+    NS_ENSURE_STATE(loader);
+
+    ++mOutstandingDocuments;
+    nsresult rv = loader->StartPersistence(this);
+    if (NS_FAILED(rv)) {
+        if (rv == NS_ERROR_NO_CONTENT) {
+            // Just ignore frames with no content document.
+            rv = NS_OK;
+        }
+        // StartPersistence won't eventually call this if it failed,
+        // so this does so (to keep mOutstandingDocuments correct).
+        DocumentDone(rv);
+    }
+    return rv;
+}
+
+NS_IMETHODIMP
+ResourceReader::OnDocumentReady(nsIWebBrowserPersistDocument* aDocument)
+{
+    mVisitor->VisitDocument(mParent, aDocument);
+    DocumentDone(NS_OK);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+ResourceReader::OnError(nsresult aFailure)
+{
+    DocumentDone(aFailure);
+    return NS_OK;
+}
+
+nsresult
+ResourceReader::OnWalkURI(nsIURI* aURI)
+{
+    // Test if this URI should be persisted. By default
+    // we should assume the URI  is persistable.
+    bool doNotPersistURI;
+    nsresult rv = NS_URIChainHasFlags(aURI,
+                                      nsIProtocolHandler::URI_NON_PERSISTABLE,
+                                      &doNotPersistURI);
+    if (NS_SUCCEEDED(rv) && doNotPersistURI) {
+        return NS_OK;
+    }
+
+    nsAutoCString stringURI;
+    rv = aURI->GetSpec(stringURI);
+    NS_ENSURE_SUCCESS(rv, rv);
+    return mVisitor->VisitResource(mParent, stringURI);
+}
+
+nsresult
+ResourceReader::OnWalkURI(const nsACString& aURISpec)
+{
+    nsresult rv;
+    nsCOMPtr<nsIURI> uri;
+
+    rv = NS_NewURI(getter_AddRefs(uri),
+                   aURISpec,
+                   mParent->GetCharacterSet().get(),
+                   mCurrentBaseURI);
+    NS_ENSURE_SUCCESS(rv, rv);
+    return OnWalkURI(uri);
+}
+
+static nsresult
+ExtractAttribute(nsIDOMNode* aNode,
+                 const char* aAttribute,
+                 const char* aNamespaceURI,
+                 nsCString&  aValue)
+{
+    nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode);
+    MOZ_ASSERT(element);
+
+    // Find the named URI attribute on the (element) node and store
+    // a reference to the URI that maps onto a local file name
+
+    nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap;
+    nsresult rv = element->GetAttributes(getter_AddRefs(attrMap));
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+    NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI);
+    NS_ConvertASCIItoUTF16 attribute(aAttribute);
+    nsCOMPtr<nsIDOMAttr> attr;
+    rv = attrMap->GetNamedItemNS(namespaceURI, attribute, getter_AddRefs(attr));
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (attr) {
+        nsAutoString value;
+        rv = attr->GetValue(value);
+        NS_ENSURE_SUCCESS(rv, rv);
+        aValue = NS_ConvertUTF16toUTF8(value);
+    } else {
+        aValue.Truncate();
+    }
+    return NS_OK;
+}
+
+nsresult
+ResourceReader::OnWalkAttribute(nsIDOMNode* aNode,
+                                const char* aAttribute,
+                                const char* aNamespaceURI)
+{
+    nsAutoCString uriSpec;
+    nsresult rv = ExtractAttribute(aNode, aAttribute, aNamespaceURI, uriSpec);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (uriSpec.IsEmpty()) {
+        return NS_OK;
+    }
+    return OnWalkURI(uriSpec);
+}
+
+static nsresult
+GetXMLStyleSheetLink(nsIDOMProcessingInstruction *aPI, nsAString &aHref)
+{
+    nsresult rv;
+    nsAutoString data;
+    rv = aPI->GetData(data);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+    nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::href, aHref);
+    return NS_OK;
+}
+
+nsresult
+ResourceReader::OnWalkDOMNode(nsIDOMNode* aNode)
+{
+    nsresult rv;
+
+    // Fixup xml-stylesheet processing instructions
+    nsCOMPtr<nsIDOMProcessingInstruction> nodeAsPI = do_QueryInterface(aNode);
+    if (nodeAsPI) {
+        nsAutoString target;
+        rv = nodeAsPI->GetTarget(target);
+        NS_ENSURE_SUCCESS(rv, rv);
+        if (target.EqualsLiteral("xml-stylesheet")) {
+            nsAutoString href;
+            GetXMLStyleSheetLink(nodeAsPI, href);
+            if (!href.IsEmpty()) {
+                return OnWalkURI(NS_ConvertUTF16toUTF8(href));
+            }
+        }
+        return NS_OK;
+    }
+
+    nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
+    if (!content) {
+        return NS_OK;
+    }
+
+    // Test the node to see if it's an image, frame, iframe, css, js
+    nsCOMPtr<nsIDOMHTMLImageElement> nodeAsImage = do_QueryInterface(aNode);
+    if (nodeAsImage) {
+        return OnWalkAttribute(aNode, "src");
+    }
+
+    if (content->IsSVGElement(nsGkAtoms::img)) {
+        return OnWalkAttribute(aNode, "href", "http://www.w3.org/1999/xlink");
+    }
+
+    nsCOMPtr<nsIDOMHTMLMediaElement> nodeAsMedia = do_QueryInterface(aNode);
+    if (nodeAsMedia) {
+        return OnWalkAttribute(aNode, "src");
+    }
+    nsCOMPtr<nsIDOMHTMLSourceElement> nodeAsSource = do_QueryInterface(aNode);
+    if (nodeAsSource) {
+        return OnWalkAttribute(aNode, "src");
+    }
+
+    if (content->IsHTMLElement(nsGkAtoms::body)) {
+        return OnWalkAttribute(aNode, "background");
+    }
+
+    if (content->IsHTMLElement(nsGkAtoms::table)) {
+        return OnWalkAttribute(aNode, "background");
+    }
+
+    if (content->IsHTMLElement(nsGkAtoms::tr)) {
+        return OnWalkAttribute(aNode, "background");
+    }
+
+    if (content->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) {
+        return OnWalkAttribute(aNode, "background");
+    }
+
+    nsCOMPtr<nsIDOMHTMLScriptElement> nodeAsScript = do_QueryInterface(aNode);
+    if (nodeAsScript) {
+        return OnWalkAttribute(aNode, "src");
+    }
+
+    if (content->IsSVGElement(nsGkAtoms::script)) {
+        return OnWalkAttribute(aNode, "href", "http://www.w3.org/1999/xlink");
+    }
+
+    nsCOMPtr<nsIDOMHTMLEmbedElement> nodeAsEmbed = do_QueryInterface(aNode);
+    if (nodeAsEmbed) {
+        return OnWalkAttribute(aNode, "src");
+    }
+
+    nsCOMPtr<nsIDOMHTMLObjectElement> nodeAsObject = do_QueryInterface(aNode);
+    if (nodeAsObject) {
+        return OnWalkAttribute(aNode, "data");
+    }
+
+    nsCOMPtr<nsIDOMHTMLAppletElement> nodeAsApplet = do_QueryInterface(aNode);
+    if (nodeAsApplet) {
+        // For an applet, relative URIs are resolved relative to the
+        // codebase (which is resolved relative to the base URI).
+        nsCOMPtr<nsIURI> oldBase = mCurrentBaseURI;
+        nsAutoString codebase;
+        rv = nodeAsApplet->GetCodeBase(codebase);
+        NS_ENSURE_SUCCESS(rv, rv);
+        if (!codebase.IsEmpty()) {
+            nsCOMPtr<nsIURI> baseURI;
+            rv = NS_NewURI(getter_AddRefs(baseURI), codebase,
+                           mParent->GetCharacterSet().get(), mCurrentBaseURI);
+            NS_ENSURE_SUCCESS(rv, rv);
+            if (baseURI) {
+                mCurrentBaseURI = baseURI;
+                // Must restore this before returning (or ENSURE'ing).
+            }
+        }
+
+        // We only store 'code' locally if there is no 'archive',
+        // otherwise we assume the archive file(s) contains it (bug 430283).
+        nsAutoCString archiveAttr;
+        rv = ExtractAttribute(aNode, "archive", "", archiveAttr);
+        if (NS_SUCCEEDED(rv)) {
+            if (!archiveAttr.IsEmpty()) {
+                rv = OnWalkURI(archiveAttr);
+            } else {
+                rv = OnWalkAttribute(aNode, "core");
+            }
+        }
+
+        // restore the base URI we really want to have
+        mCurrentBaseURI = oldBase;
+        return rv;
+    }
+
+    nsCOMPtr<nsIDOMHTMLLinkElement> nodeAsLink = do_QueryInterface(aNode);
+    if (nodeAsLink) {
+        // Test if the link has a rel value indicating it to be a stylesheet
+        nsAutoString linkRel;
+        if (NS_SUCCEEDED(nodeAsLink->GetRel(linkRel)) && !linkRel.IsEmpty()) {
+            nsReadingIterator<char16_t> start;
+            nsReadingIterator<char16_t> end;
+            nsReadingIterator<char16_t> current;
+
+            linkRel.BeginReading(start);
+            linkRel.EndReading(end);
+
+            // Walk through space delimited string looking for "stylesheet"
+            for (current = start; current != end; ++current) {
+                // Ignore whitespace
+                if (nsCRT::IsAsciiSpace(*current)) {
+                    continue;
+                }
+
+                // Grab the next space delimited word
+                nsReadingIterator<char16_t> startWord = current;
+                do {
+                    ++current;
+                } while (current != end && !nsCRT::IsAsciiSpace(*current));
+
+                // Store the link for fix up if it says "stylesheet"
+                if (Substring(startWord, current)
+                        .LowerCaseEqualsLiteral("stylesheet")) {
+                    OnWalkAttribute(aNode, "href");
+                    return NS_OK;
+                }
+                if (current == end) {
+                    break;
+                }
+            }
+        }
+        return NS_OK;
+    }
+
+    nsCOMPtr<nsIDOMHTMLFrameElement> nodeAsFrame = do_QueryInterface(aNode);
+    if (nodeAsFrame) {
+        return OnWalkSubframe(aNode);
+    }
+
+    nsCOMPtr<nsIDOMHTMLIFrameElement> nodeAsIFrame = do_QueryInterface(aNode);
+    if (nodeAsIFrame && !(mPersistFlags &
+                          IWBP::PERSIST_FLAGS_IGNORE_IFRAMES)) {
+        return OnWalkSubframe(aNode);
+    }
+
+    nsCOMPtr<nsIDOMHTMLInputElement> nodeAsInput = do_QueryInterface(aNode);
+    if (nodeAsInput) {
+        return OnWalkAttribute(aNode, "src");
+    }
+
+    return NS_OK;
+}
+
+// Helper class for node rewriting in writeContent().
+class PersistNodeFixup final : public nsIDocumentEncoderNodeFixup {
+public:
+    PersistNodeFixup(WebBrowserPersistLocalDocument* aParent,
+                     nsIWebBrowserPersistURIMap* aMap,
+                     nsIURI* aTargetURI);
+
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIDOCUMENTENCODERNODEFIXUP
+private:
+    virtual ~PersistNodeFixup() { }
+    nsRefPtr<WebBrowserPersistLocalDocument> mParent;
+    nsClassHashtable<nsCStringHashKey, nsCString> mMap;
+    nsCOMPtr<nsIURI> mCurrentBaseURI;
+    nsCOMPtr<nsIURI> mTargetBaseURI;
+
+    bool IsFlagSet(uint32_t aFlag) const {
+        return mParent->GetPersistFlags() & aFlag;
+    }
+
+    nsresult GetNodeToFixup(nsIDOMNode* aNodeIn, nsIDOMNode** aNodeOut);
+    nsresult FixupURI(nsAString& aURI);
+    nsresult FixupAttribute(nsIDOMNode* aNode,
+                            const char* aAttribute,
+                            const char* aNamespaceURI = "");
+    nsresult FixupAnchor(nsIDOMNode* aNode);
+    nsresult FixupXMLStyleSheetLink(nsIDOMProcessingInstruction* aPI,
+                                    const nsAString& aHref);
+
+    using IWBP = nsIWebBrowserPersist;
+};
+
+NS_IMPL_ISUPPORTS(PersistNodeFixup, nsIDocumentEncoderNodeFixup)
+
+PersistNodeFixup::PersistNodeFixup(WebBrowserPersistLocalDocument* aParent,
+                                   nsIWebBrowserPersistURIMap* aMap,
+                                   nsIURI* aTargetURI)
+: mParent(aParent)
+, mCurrentBaseURI(aParent->GetBaseURI())
+, mTargetBaseURI(aTargetURI)
+{
+    if (aMap) {
+        uint32_t mapSize;
+        nsresult rv = aMap->GetNumMappedURIs(&mapSize);
+        MOZ_ASSERT(NS_SUCCEEDED(rv));
+        NS_ENSURE_SUCCESS_VOID(rv);
+        for (uint32_t i = 0; i < mapSize; ++i) {
+            nsAutoCString urlFrom;
+            nsCString* urlTo = new nsCString();
+
+            rv = aMap->GetURIMapping(i, urlFrom, *urlTo);
+            MOZ_ASSERT(NS_SUCCEEDED(rv));
+            if (NS_SUCCEEDED(rv)) {
+                mMap.Put(urlFrom, urlTo);
+            }
+        }
+    }
+}
+
+nsresult
+PersistNodeFixup::GetNodeToFixup(nsIDOMNode *aNodeIn, nsIDOMNode **aNodeOut)
+{
+    // Avoid mixups in FixupNode that could leak objects; this goes
+    // against the usual out parameter convention, but it's a private
+    // method so shouldn't be a problem.
+    MOZ_ASSERT(!*aNodeOut);
+
+    if (!IsFlagSet(IWBP::PERSIST_FLAGS_FIXUP_ORIGINAL_DOM)) {
+        nsresult rv = aNodeIn->CloneNode(false, 1, aNodeOut);
+        NS_ENSURE_SUCCESS(rv, rv);
+    } else {
+        NS_ADDREF(*aNodeOut = aNodeIn);
+    }
+    nsCOMPtr<nsIDOMHTMLElement> element(do_QueryInterface(*aNodeOut));
+    if (element) {
+        // Make sure this is not XHTML
+        nsAutoString namespaceURI;
+        element->GetNamespaceURI(namespaceURI);
+        if (namespaceURI.IsEmpty()) {
+            // This is a tag-soup node.  It may have a _base_href attribute
+            // stuck on it by the parser, but since we're fixing up all URIs
+            // relative to the overall document base that will screw us up.
+            // Just remove the _base_href.
+            element->RemoveAttribute(NS_LITERAL_STRING("_base_href"));
+        }
+    }
+    return NS_OK;
+}
+
+nsresult
+PersistNodeFixup::FixupURI(nsAString &aURI)
+{
+    // get the current location of the file (absolutized)
+    nsCOMPtr<nsIURI> uri;
+    nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI,
+                            mParent->GetCharacterSet().get(), mCurrentBaseURI);
+    NS_ENSURE_SUCCESS(rv, rv);
+    nsAutoCString spec;
+    rv = uri->GetSpec(spec);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    const nsCString* replacement = mMap.Get(spec);
+    if (!replacement) {
+        // Note that most callers ignore this "failure".
+        return NS_ERROR_FAILURE;
+    }
+    if (!replacement->IsEmpty()) {
+        aURI = NS_ConvertUTF8toUTF16(*replacement);
+    }
+    return NS_OK;
+}
+
+nsresult
+PersistNodeFixup::FixupAttribute(nsIDOMNode* aNode,
+                                 const char* aAttribute,
+                                 const char* aNamespaceURI)
+{
+    nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode);
+    MOZ_ASSERT(element);
+
+    nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap;
+    nsresult rv = element->GetAttributes(getter_AddRefs(attrMap));
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+    NS_ConvertASCIItoUTF16 attribute(aAttribute);
+    NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI);
+    nsCOMPtr<nsIDOMAttr> attr;
+    rv = attrMap->GetNamedItemNS(namespaceURI, attribute, getter_AddRefs(attr));
+    if (attr) {
+        nsString uri;
+        attr->GetValue(uri);
+        rv = FixupURI(uri);
+        if (NS_SUCCEEDED(rv)) {
+            attr->SetValue(uri);
+        }
+    }
+
+    return rv;
+}
+
+nsresult
+PersistNodeFixup::FixupAnchor(nsIDOMNode *aNode)
+{
+    if (IsFlagSet(IWBP::PERSIST_FLAGS_DONT_FIXUP_LINKS)) {
+        return NS_OK;
+    }
+
+    nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode);
+    MOZ_ASSERT(element);
+
+    nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap;
+    nsresult rv = element->GetAttributes(getter_AddRefs(attrMap));
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+    // Make all anchor links absolute so they point off onto the Internet
+    nsString attribute(NS_LITERAL_STRING("href"));
+    nsCOMPtr<nsIDOMAttr> attr;
+    rv = attrMap->GetNamedItem(attribute, getter_AddRefs(attr));
+    if (attr) {
+        nsString oldValue;
+        attr->GetValue(oldValue);
+        NS_ConvertUTF16toUTF8 oldCValue(oldValue);
+
+        // Skip empty values and self-referencing bookmarks
+        if (oldCValue.IsEmpty() || oldCValue.CharAt(0) == '#') {
+            return NS_OK;
+        }
+
+        // if saving file to same location, we don't need to do any fixup
+        bool isEqual;
+        if (mTargetBaseURI &&
+            NS_SUCCEEDED(mCurrentBaseURI->Equals(mTargetBaseURI, &isEqual)) &&
+            isEqual) {
+            return NS_OK;
+        }
+
+        nsCOMPtr<nsIURI> relativeURI;
+        relativeURI = IsFlagSet(IWBP::PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION)
+                      ? mTargetBaseURI : mCurrentBaseURI;
+        // Make a new URI to replace the current one
+        nsCOMPtr<nsIURI> newURI;
+        rv = NS_NewURI(getter_AddRefs(newURI), oldCValue,
+                       mParent->GetCharacterSet().get(), relativeURI);
+        if (NS_SUCCEEDED(rv) && newURI)
+        {
+            newURI->SetUserPass(EmptyCString());
+            nsAutoCString uriSpec;
+            newURI->GetSpec(uriSpec);
+            attr->SetValue(NS_ConvertUTF8toUTF16(uriSpec));
+        }
+    }
+
+    return NS_OK;
+}
+
+static void
+AppendXMLAttr(const nsAString& key, const nsAString& aValue, nsAString& aBuffer)
+{
+    if (!aBuffer.IsEmpty()) {
+        aBuffer.Append(' ');
+    }
+    aBuffer.Append(key);
+    aBuffer.AppendLiteral("=\"");
+    for (size_t i = 0; i < aValue.Length(); ++i) {
+        switch (aValue[i]) {
+            case '&':
+                aBuffer.AppendLiteral("&amp;");
+                break;
+            case '<':
+                aBuffer.AppendLiteral("&lt;");
+                break;
+            case '>':
+                aBuffer.AppendLiteral("&gt;");
+                break;
+            case '"':
+                aBuffer.AppendLiteral("&quot;");
+                break;
+            default:
+                aBuffer.Append(aValue[i]);
+                break;
+        }
+    }
+    aBuffer.Append('"');
+}
+
+nsresult
+PersistNodeFixup::FixupXMLStyleSheetLink(nsIDOMProcessingInstruction* aPI,
+                                         const nsAString& aHref)
+{
+    NS_ENSURE_ARG_POINTER(aPI);
+    nsresult rv = NS_OK;
+
+    nsAutoString data;
+    rv = aPI->GetData(data);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+    nsAutoString href;
+    nsContentUtils::GetPseudoAttributeValue(data,
+                                            nsGkAtoms::href,
+                                            href);
+
+    // Construct and set a new data value for the xml-stylesheet
+    if (!aHref.IsEmpty() && !href.IsEmpty())
+    {
+        nsAutoString alternate;
+        nsAutoString charset;
+        nsAutoString title;
+        nsAutoString type;
+        nsAutoString media;
+
+        nsContentUtils::GetPseudoAttributeValue(data,
+                                                nsGkAtoms::alternate,
+                                                alternate);
+        nsContentUtils::GetPseudoAttributeValue(data,
+                                                nsGkAtoms::charset,
+                                                charset);
+        nsContentUtils::GetPseudoAttributeValue(data,
+                                                nsGkAtoms::title,
+                                                title);
+        nsContentUtils::GetPseudoAttributeValue(data,
+                                                nsGkAtoms::type,
+                                                type);
+        nsContentUtils::GetPseudoAttributeValue(data,
+                                                nsGkAtoms::media,
+                                                media);
+
+        nsAutoString newData;
+        AppendXMLAttr(NS_LITERAL_STRING("href"), aHref, newData);
+        if (!title.IsEmpty()) {
+            AppendXMLAttr(NS_LITERAL_STRING("title"), title, newData);
+        }
+        if (!media.IsEmpty()) {
+            AppendXMLAttr(NS_LITERAL_STRING("media"), media, newData);
+        }
+        if (!type.IsEmpty()) {
+            AppendXMLAttr(NS_LITERAL_STRING("type"), type, newData);
+        }
+        if (!charset.IsEmpty()) {
+            AppendXMLAttr(NS_LITERAL_STRING("charset"), charset, newData);
+        }
+        if (!alternate.IsEmpty()) {
+            AppendXMLAttr(NS_LITERAL_STRING("alternate"), alternate, newData);
+        }
+        aPI->SetData(newData);
+    }
+
+    return rv;
+}
+
+NS_IMETHODIMP
+PersistNodeFixup::FixupNode(nsIDOMNode *aNodeIn,
+                            bool *aSerializeCloneKids,
+                            nsIDOMNode **aNodeOut)
+{
+    *aNodeOut = nullptr;
+    *aSerializeCloneKids = false;
+
+    uint16_t type;
+    nsresult rv = aNodeIn->GetNodeType(&type);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (type != nsIDOMNode::ELEMENT_NODE &&
+        type != nsIDOMNode::PROCESSING_INSTRUCTION_NODE) {
+        return NS_OK;
+    }
+
+    // Fixup xml-stylesheet processing instructions
+    nsCOMPtr<nsIDOMProcessingInstruction> nodeAsPI = do_QueryInterface(aNodeIn);
+    if (nodeAsPI) {
+        nsAutoString target;
+        nodeAsPI->GetTarget(target);
+        if (target.EqualsLiteral("xml-stylesheet"))
+        {
+            rv = GetNodeToFixup(aNodeIn, aNodeOut);
+            if (NS_SUCCEEDED(rv) && *aNodeOut) {
+                nsCOMPtr<nsIDOMProcessingInstruction> outNode =
+                    do_QueryInterface(*aNodeOut);
+                nsAutoString href;
+                GetXMLStyleSheetLink(nodeAsPI, href);
+                if (!href.IsEmpty()) {
+                    FixupURI(href);
+                    FixupXMLStyleSheetLink(outNode, href);
+                }
+            }
+        }
+        return NS_OK;
+    }
+
+    // BASE elements are replaced by a comment so relative links are not hosed.
+    if (!IsFlagSet(IWBP::PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS)) {
+        nsCOMPtr<nsIDOMHTMLBaseElement> nodeAsBase = do_QueryInterface(aNodeIn);
+        if (nodeAsBase) {
+            nsCOMPtr<nsIDOMDocument> ownerDocument;
+            auto* base = static_cast<dom::HTMLSharedElement*>(nodeAsBase.get());
+            base->GetOwnerDocument(getter_AddRefs(ownerDocument));
+            if (ownerDocument) {
+                nsAutoString href;
+                base->GetHref(href); // Doesn't matter if this fails
+                nsCOMPtr<nsIDOMComment> comment;
+                nsAutoString commentText;
+                commentText.AssignLiteral(" base ");
+                if (!href.IsEmpty()) {
+                    commentText += NS_LITERAL_STRING("href=\"") + href
+                                   + NS_LITERAL_STRING("\" ");
+                }
+                rv = ownerDocument->CreateComment(commentText,
+                                                  getter_AddRefs(comment));
+                if (comment) {
+                    return CallQueryInterface(comment, aNodeOut);
+                }
+            }
+            return NS_OK;
+        }
+    }
+
+    nsCOMPtr<nsIContent> content = do_QueryInterface(aNodeIn);
+    if (!content) {
+        return NS_OK;
+    }
+
+    // Fix up href and file links in the elements
+    nsCOMPtr<nsIDOMHTMLAnchorElement> nodeAsAnchor = do_QueryInterface(aNodeIn);
+    if (nodeAsAnchor) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            FixupAnchor(*aNodeOut);
+        }
+        return rv;
+    }
+
+    nsCOMPtr<nsIDOMHTMLAreaElement> nodeAsArea = do_QueryInterface(aNodeIn);
+    if (nodeAsArea) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            FixupAnchor(*aNodeOut);
+        }
+        return rv;
+    }
+
+    if (content->IsHTMLElement(nsGkAtoms::body)) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            FixupAttribute(*aNodeOut, "background");
+        }
+        return rv;
+    }
+
+    if (content->IsHTMLElement(nsGkAtoms::table)) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            FixupAttribute(*aNodeOut, "background");
+        }
+        return rv;
+    }
+
+    if (content->IsHTMLElement(nsGkAtoms::tr)) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            FixupAttribute(*aNodeOut, "background");
+        }
+        return rv;
+    }
+
+    if (content->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            FixupAttribute(*aNodeOut, "background");
+        }
+        return rv;
+    }
+
+    nsCOMPtr<nsIDOMHTMLImageElement> nodeAsImage = do_QueryInterface(aNodeIn);
+    if (nodeAsImage) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            // Disable image loads
+            nsCOMPtr<nsIImageLoadingContent> imgCon =
+                do_QueryInterface(*aNodeOut);
+            if (imgCon) {
+                imgCon->SetLoadingEnabled(false);
+            }
+            FixupAnchor(*aNodeOut);
+            FixupAttribute(*aNodeOut, "src");
+        }
+        return rv;
+    }
+
+    nsCOMPtr<nsIDOMHTMLMediaElement> nodeAsMedia = do_QueryInterface(aNodeIn);
+    if (nodeAsMedia) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            FixupAttribute(*aNodeOut, "src");
+        }
+        return rv;
+    }
+
+    nsCOMPtr<nsIDOMHTMLSourceElement> nodeAsSource = do_QueryInterface(aNodeIn);
+    if (nodeAsSource) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            FixupAttribute(*aNodeOut, "src");
+        }
+        return rv;
+    }
+
+    if (content->IsSVGElement(nsGkAtoms::img)) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            // Disable image loads
+            nsCOMPtr<nsIImageLoadingContent> imgCon =
+                do_QueryInterface(*aNodeOut);
+            if (imgCon)
+                imgCon->SetLoadingEnabled(false);
+
+            // FixupAnchor(*aNodeOut);  // XXXjwatt: is this line needed?
+            FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink");
+        }
+        return rv;
+    }
+
+    nsCOMPtr<nsIDOMHTMLScriptElement> nodeAsScript = do_QueryInterface(aNodeIn);
+    if (nodeAsScript) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            FixupAttribute(*aNodeOut, "src");
+        }
+        return rv;
+    }
+
+    if (content->IsSVGElement(nsGkAtoms::script)) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink");
+        }
+        return rv;
+    }
+
+        nsCOMPtr<nsIDOMHTMLEmbedElement> nodeAsEmbed = do_QueryInterface(aNodeIn);
+    if (nodeAsEmbed) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            FixupAttribute(*aNodeOut, "src");
+        }
+        return rv;
+    }
+
+    nsCOMPtr<nsIDOMHTMLObjectElement> nodeAsObject = do_QueryInterface(aNodeIn);
+    if (nodeAsObject) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            FixupAttribute(*aNodeOut, "data");
+        }
+        return rv;
+    }
+
+    nsCOMPtr<nsIDOMHTMLAppletElement> nodeAsApplet = do_QueryInterface(aNodeIn);
+    if (nodeAsApplet) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            nsCOMPtr<nsIDOMHTMLAppletElement> newApplet =
+                do_QueryInterface(*aNodeOut);
+            // For an applet, relative URIs are resolved relative to the
+            // codebase (which is resolved relative to the base URI).
+            nsCOMPtr<nsIURI> oldBase = mCurrentBaseURI;
+            nsAutoString codebase;
+            nodeAsApplet->GetCodeBase(codebase);
+            if (!codebase.IsEmpty()) {
+                nsCOMPtr<nsIURI> baseURI;
+                NS_NewURI(getter_AddRefs(baseURI), codebase,
+                          mParent->GetCharacterSet().get(), mCurrentBaseURI);
+                if (baseURI) {
+                    mCurrentBaseURI = baseURI;
+                }
+            }
+            // Unset the codebase too, since we'll correctly relativize the
+            // code and archive paths.
+            static_cast<dom::HTMLSharedObjectElement*>(newApplet.get())->
+              RemoveAttribute(NS_LITERAL_STRING("codebase"));
+            FixupAttribute(*aNodeOut, "code");
+            FixupAttribute(*aNodeOut, "archive");
+            // restore the base URI we really want to have
+            mCurrentBaseURI = oldBase;
+        }
+        return rv;
+    }
+
+    nsCOMPtr<nsIDOMHTMLLinkElement> nodeAsLink = do_QueryInterface(aNodeIn);
+    if (nodeAsLink) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            // First see if the link represents linked content
+            rv = FixupAttribute(*aNodeOut, "href");
+            if (NS_FAILED(rv)) {
+                // Perhaps this link is actually an anchor to related content
+                FixupAnchor(*aNodeOut);
+            }
+            // TODO if "type" attribute == "text/css"
+            //        fixup stylesheet
+        }
+        return rv;
+    }
+
+    nsCOMPtr<nsIDOMHTMLFrameElement> nodeAsFrame = do_QueryInterface(aNodeIn);
+    if (nodeAsFrame) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            FixupAttribute(*aNodeOut, "src");
+        }
+        return rv;
+    }
+
+    nsCOMPtr<nsIDOMHTMLIFrameElement> nodeAsIFrame = do_QueryInterface(aNodeIn);
+    if (nodeAsIFrame) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            FixupAttribute(*aNodeOut, "src");
+        }
+        return rv;
+    }
+
+        nsCOMPtr<nsIDOMHTMLInputElement> nodeAsInput = do_QueryInterface(aNodeIn);
+    if (nodeAsInput) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            // Disable image loads
+            nsCOMPtr<nsIImageLoadingContent> imgCon =
+                do_QueryInterface(*aNodeOut);
+            if (imgCon) {
+                imgCon->SetLoadingEnabled(false);
+            }
+
+            FixupAttribute(*aNodeOut, "src");
+
+            nsAutoString valueStr;
+            NS_NAMED_LITERAL_STRING(valueAttr, "value");
+            // Update element node attributes with user-entered form state
+            nsCOMPtr<nsIContent> content = do_QueryInterface(*aNodeOut);
+            nsRefPtr<dom::HTMLInputElement> outElt =
+              dom::HTMLInputElement::FromContentOrNull(content);
+            nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(*aNodeOut);
+            switch (formControl->GetType()) {
+                case NS_FORM_INPUT_EMAIL:
+                case NS_FORM_INPUT_SEARCH:
+                case NS_FORM_INPUT_TEXT:
+                case NS_FORM_INPUT_TEL:
+                case NS_FORM_INPUT_URL:
+                case NS_FORM_INPUT_NUMBER:
+                case NS_FORM_INPUT_RANGE:
+                case NS_FORM_INPUT_DATE:
+                case NS_FORM_INPUT_TIME:
+                case NS_FORM_INPUT_COLOR:
+                    nodeAsInput->GetValue(valueStr);
+                    // Avoid superfluous value="" serialization
+                    if (valueStr.IsEmpty())
+                      outElt->RemoveAttribute(valueAttr);
+                    else
+                      outElt->SetAttribute(valueAttr, valueStr);
+                    break;
+                case NS_FORM_INPUT_CHECKBOX:
+                case NS_FORM_INPUT_RADIO:
+                    bool checked;
+                    nodeAsInput->GetChecked(&checked);
+                    outElt->SetDefaultChecked(checked);
+                    break;
+                default:
+                    break;
+            }
+        }
+        return rv;
+    }
+
+    nsCOMPtr<nsIDOMHTMLTextAreaElement> nodeAsTextArea = do_QueryInterface(aNodeIn);
+    if (nodeAsTextArea) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            // Tell the document encoder to serialize the text child we create below
+            *aSerializeCloneKids = true;
+
+            nsAutoString valueStr;
+            nodeAsTextArea->GetValue(valueStr);
+
+            (*aNodeOut)->SetTextContent(valueStr);
+        }
+        return rv;
+    }
+
+    nsCOMPtr<nsIDOMHTMLOptionElement> nodeAsOption = do_QueryInterface(aNodeIn);
+    if (nodeAsOption) {
+        rv = GetNodeToFixup(aNodeIn, aNodeOut);
+        if (NS_SUCCEEDED(rv) && *aNodeOut) {
+            nsCOMPtr<nsIDOMHTMLOptionElement> outElt = do_QueryInterface(*aNodeOut);
+            bool selected;
+            nodeAsOption->GetSelected(&selected);
+            outElt->SetDefaultSelected(selected);
+        }
+        return rv;
+    }
+
+    return NS_OK;
+}
+
+} // unnamed namespace
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::ReadResources(nsIWebBrowserPersistResourceVisitor* aVisitor)
+{
+    nsresult rv = NS_OK;
+    nsCOMPtr<nsIWebBrowserPersistResourceVisitor> visitor = aVisitor;
+
+    nsCOMPtr<nsIDOMNode> docAsNode = do_QueryInterface(mDocument);
+    NS_ENSURE_TRUE(docAsNode, NS_ERROR_FAILURE);
+
+    nsCOMPtr<nsIDOMTreeWalker> walker;
+    nsCOMPtr<nsIDOMDocument> oldStyleDoc = do_QueryInterface(mDocument);
+    MOZ_ASSERT(oldStyleDoc);
+    rv = oldStyleDoc->CreateTreeWalker(docAsNode,
+            nsIDOMNodeFilter::SHOW_ELEMENT |
+            nsIDOMNodeFilter::SHOW_DOCUMENT |
+            nsIDOMNodeFilter::SHOW_PROCESSING_INSTRUCTION,
+            nullptr, 1, getter_AddRefs(walker));
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+    MOZ_ASSERT(walker);
+
+    nsRefPtr<ResourceReader> reader = new ResourceReader(this, aVisitor);
+    nsCOMPtr<nsIDOMNode> currentNode;
+    walker->GetCurrentNode(getter_AddRefs(currentNode));
+    while (currentNode) {
+        rv = reader->OnWalkDOMNode(currentNode);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+            break;
+        }
+        rv = walker->NextNode(getter_AddRefs(currentNode));
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+            break;
+        }
+    }
+    reader->DocumentDone(rv);
+    return rv;
+}
+
+static uint32_t
+ConvertEncoderFlags(uint32_t aEncoderFlags)
+{
+    uint32_t encoderFlags = 0;
+
+    if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_SELECTION_ONLY)
+        encoderFlags |= nsIDocumentEncoder::OutputSelectionOnly;
+    if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_FORMATTED)
+        encoderFlags |= nsIDocumentEncoder::OutputFormatted;
+    if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_RAW)
+        encoderFlags |= nsIDocumentEncoder::OutputRaw;
+    if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_BODY_ONLY)
+        encoderFlags |= nsIDocumentEncoder::OutputBodyOnly;
+    if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_PREFORMATTED)
+        encoderFlags |= nsIDocumentEncoder::OutputPreformatted;
+    if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_WRAP)
+        encoderFlags |= nsIDocumentEncoder::OutputWrap;
+    if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_FORMAT_FLOWED)
+        encoderFlags |= nsIDocumentEncoder::OutputFormatFlowed;
+    if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ABSOLUTE_LINKS)
+        encoderFlags |= nsIDocumentEncoder::OutputAbsoluteLinks;
+    if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_BASIC_ENTITIES)
+        encoderFlags |= nsIDocumentEncoder::OutputEncodeBasicEntities;
+    if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES)
+        encoderFlags |= nsIDocumentEncoder::OutputEncodeLatin1Entities;
+    if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_HTML_ENTITIES)
+        encoderFlags |= nsIDocumentEncoder::OutputEncodeHTMLEntities;
+    if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_W3C_ENTITIES)
+        encoderFlags |= nsIDocumentEncoder::OutputEncodeW3CEntities;
+    if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_CR_LINEBREAKS)
+        encoderFlags |= nsIDocumentEncoder::OutputCRLineBreak;
+    if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_LF_LINEBREAKS)
+        encoderFlags |= nsIDocumentEncoder::OutputLFLineBreak;
+    if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_NOSCRIPT_CONTENT)
+        encoderFlags |= nsIDocumentEncoder::OutputNoScriptContent;
+    if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_NOFRAMES_CONTENT)
+        encoderFlags |= nsIDocumentEncoder::OutputNoFramesContent;
+
+    return encoderFlags;
+}
+
+static bool
+ContentTypeEncoderExists(const nsACString& aType)
+{
+    nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE);
+    contractID.Append(aType);
+
+    nsCOMPtr<nsIComponentRegistrar> registrar;
+    nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(registrar));
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+    if (NS_SUCCEEDED(rv) && registrar) {
+        bool result;
+        rv = registrar->IsContractIDRegistered(contractID.get(), &result);
+        MOZ_ASSERT(NS_SUCCEEDED(rv));
+        return NS_SUCCEEDED(rv) && result;
+    }
+    return false;
+}
+
+void
+WebBrowserPersistLocalDocument::DecideContentType(nsACString& aContentType)
+{
+    if (aContentType.IsEmpty()) {
+        if (NS_WARN_IF(NS_FAILED(GetContentType(aContentType)))) {
+            aContentType.Truncate();
+        }
+    }
+    if (!aContentType.IsEmpty() &&
+        !ContentTypeEncoderExists(aContentType)) {
+        aContentType.Truncate();
+    }
+    if (aContentType.IsEmpty()) {
+        aContentType.AssignLiteral("text/html");
+    }
+}
+
+nsresult
+WebBrowserPersistLocalDocument::GetDocEncoder(const nsACString& aContentType,
+                                              uint32_t aEncoderFlags,
+                                              nsIDocumentEncoder** aEncoder)
+{
+    nsresult rv;
+    nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE);
+    contractID.Append(aContentType);
+    nsCOMPtr<nsIDocumentEncoder> encoder =
+        do_CreateInstance(contractID.get(), &rv);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+    rv = encoder->NativeInit(mDocument,
+                             NS_ConvertASCIItoUTF16(aContentType),
+                             ConvertEncoderFlags(aEncoderFlags));
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+    nsAutoCString charSet;
+    rv = GetCharacterSet(charSet);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+    rv = encoder->SetCharset(charSet);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+    encoder.forget(aEncoder);
+    return NS_OK;
+}
+
+
+NS_IMETHODIMP
+WebBrowserPersistLocalDocument::WriteContent(
+    nsIOutputStream* aStream,
+    nsIWebBrowserPersistURIMap* aMap,
+    const nsACString& aRequestedContentType,
+    uint32_t aEncoderFlags,
+    uint32_t aWrapColumn,
+    nsIWebBrowserPersistWriteCompletion* aCompletion)
+{
+    NS_ENSURE_ARG_POINTER(aStream);
+    NS_ENSURE_ARG_POINTER(aCompletion);
+    nsAutoCString contentType(aRequestedContentType);
+    DecideContentType(contentType);
+
+    nsCOMPtr<nsIDocumentEncoder> encoder;
+    nsresult rv = GetDocEncoder(contentType, aEncoderFlags,
+                                getter_AddRefs(encoder));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (aWrapColumn != 0 && (aEncoderFlags
+                             & nsIWebBrowserPersist::ENCODE_FLAGS_WRAP)) {
+        encoder->SetWrapColumn(aWrapColumn);
+    }
+
+    nsCOMPtr<nsIURI> targetURI;
+    if (aMap) {
+        nsAutoCString targetURISpec;
+        rv = aMap->GetTargetBaseURI(targetURISpec);
+        if (NS_SUCCEEDED(rv) && !targetURISpec.IsEmpty()) {
+            rv = NS_NewURI(getter_AddRefs(targetURI), targetURISpec,
+                           /* charset: */ nullptr, /* base: */ nullptr);
+            NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+        } else if (mPersistFlags & nsIWebBrowserPersist::PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION) {
+            return NS_ERROR_UNEXPECTED;
+        }
+    }
+    rv = encoder->SetNodeFixup(new PersistNodeFixup(this, aMap, targetURI));
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+    rv = encoder->EncodeToStream(aStream);
+    aCompletion->OnFinish(this, aStream, contentType, rv);
+    return NS_OK;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistLocalDocument_h__
+#define WebBrowserPersistLocalDocument_h__
+
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIDocument.h"
+#include "nsIURI.h"
+#include "nsIWebBrowserPersistDocument.h"
+
+class nsIDocumentEncoder;
+class nsISHEntry;
+
+namespace mozilla {
+
+class WebBrowserPersistLocalDocument final
+    : public nsIWebBrowserPersistDocument
+{
+public:
+    explicit WebBrowserPersistLocalDocument(nsIDocument* aDocument);
+
+    const nsCString& GetCharacterSet() const;
+    uint32_t GetPersistFlags() const;
+    already_AddRefed<nsIURI> GetBaseURI() const;
+
+    NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+    NS_DECL_NSIWEBBROWSERPERSISTDOCUMENT
+
+    NS_DECL_CYCLE_COLLECTION_CLASS(WebBrowserPersistLocalDocument)
+
+private:
+    nsCOMPtr<nsIDocument> mDocument;
+    uint32_t mPersistFlags;
+
+    void DecideContentType(nsACString& aContentType);
+    nsresult GetDocEncoder(const nsACString& aContentType,
+                           uint32_t aEncoderFlags,
+                           nsIDocumentEncoder** aEncoder);
+    nsresult GetHistory(nsISHEntry** aHistory);
+
+    virtual ~WebBrowserPersistLocalDocument();
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistLocalDocument_h__
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistRemoteDocument.h"
+#include "WebBrowserPersistDocumentParent.h"
+#include "WebBrowserPersistResourcesParent.h"
+#include "WebBrowserPersistSerializeParent.h"
+#include "mozilla/unused.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(WebBrowserPersistRemoteDocument,
+                  nsIWebBrowserPersistDocument)
+
+WebBrowserPersistRemoteDocument
+::WebBrowserPersistRemoteDocument(WebBrowserPersistDocumentParent* aActor,
+                                  const Attrs& aAttrs,
+                                  nsIInputStream* aPostData)
+: mActor(aActor)
+, mAttrs(aAttrs)
+, mPostData(aPostData)
+{
+}
+
+WebBrowserPersistRemoteDocument::~WebBrowserPersistRemoteDocument()
+{
+    if (mActor) {
+        unused << mActor->Send__delete__(mActor);
+        // That will call mActor->ActorDestroy, which calls this->ActorDestroy
+        // (whether or not the IPC send succeeds).
+    }
+    MOZ_ASSERT(!mActor);
+}
+
+void
+WebBrowserPersistRemoteDocument::ActorDestroy(void)
+{
+    mActor = nullptr;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetIsPrivate(bool* aIsPrivate)
+{
+    *aIsPrivate = mAttrs.isPrivate();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetDocumentURI(nsACString& aURISpec)
+{
+    aURISpec = mAttrs.documentURI();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetBaseURI(nsACString& aURISpec)
+{
+    aURISpec = mAttrs.baseURI();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetContentType(nsACString& aContentType)
+{
+    aContentType = mAttrs.contentType();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetCharacterSet(nsACString& aCharSet)
+{
+    aCharSet = mAttrs.characterSet();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetTitle(nsAString& aTitle)
+{
+    aTitle = mAttrs.title();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetReferrer(nsAString& aReferrer)
+{
+    aReferrer = mAttrs.referrer();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetContentDisposition(nsAString& aDisp)
+{
+    aDisp = mAttrs.contentDisposition();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetCacheKey(uint32_t* aCacheKey)
+{
+    *aCacheKey = mAttrs.cacheKey();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetPersistFlags(uint32_t* aFlags)
+{
+    *aFlags = mAttrs.persistFlags();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::SetPersistFlags(uint32_t aFlags)
+{
+    if (!mActor) {
+        return NS_ERROR_FAILURE;
+    }
+    if (!mActor->SendSetPersistFlags(aFlags)) {
+        return NS_ERROR_FAILURE;
+    }
+    mAttrs.persistFlags() = aFlags;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::GetPostData(nsIInputStream** aStream)
+{
+    nsCOMPtr<nsIInputStream> stream = mPostData;
+    stream.forget(aStream);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::ReadResources(nsIWebBrowserPersistResourceVisitor* aVisitor)
+{
+    if (!mActor) {
+        return NS_ERROR_FAILURE;
+    }
+    nsRefPtr<WebBrowserPersistResourcesParent> subActor =
+        new WebBrowserPersistResourcesParent(this, aVisitor);
+    return mActor->SendPWebBrowserPersistResourcesConstructor(
+        subActor.forget().take())
+        ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistRemoteDocument::WriteContent(
+    nsIOutputStream* aStream,
+    nsIWebBrowserPersistURIMap* aMap,
+    const nsACString& aRequestedContentType,
+    uint32_t aEncoderFlags,
+    uint32_t aWrapColumn,
+    nsIWebBrowserPersistWriteCompletion* aCompletion)
+{
+    if (!mActor) {
+        return NS_ERROR_FAILURE;
+    }
+
+    nsresult rv;
+    WebBrowserPersistURIMap map;
+    uint32_t numMappedURIs;
+    if (aMap) {
+        rv = aMap->GetTargetBaseURI(map.targetBaseURI());
+        NS_ENSURE_SUCCESS(rv, rv);
+        rv = aMap->GetNumMappedURIs(&numMappedURIs);
+        NS_ENSURE_SUCCESS(rv, rv);
+        for (uint32_t i = 0; i < numMappedURIs; ++i) {
+            WebBrowserPersistURIMapEntry& nextEntry =
+                *(map.mapURIs().AppendElement());
+            rv = aMap->GetURIMapping(i, nextEntry.mapFrom(), nextEntry.mapTo());
+            NS_ENSURE_SUCCESS(rv, rv);
+        }
+    }
+
+    auto* subActor = new WebBrowserPersistSerializeParent(this,
+                                                          aStream,
+                                                          aCompletion);
+    nsCString requestedContentType(aRequestedContentType); // Sigh.
+    return mActor->SendPWebBrowserPersistSerializeConstructor(
+        subActor, map, requestedContentType, aEncoderFlags, aWrapColumn)
+        ? NS_OK : NS_ERROR_FAILURE;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistRemoteDocument_h__
+#define WebBrowserPersistRemoteDocument_h__
+
+#include "mozilla/Maybe.h"
+#include "mozilla/PWebBrowserPersistDocumentParent.h"
+#include "nsCOMPtr.h"
+#include "nsIWebBrowserPersistDocument.h"
+#include "nsIInputStream.h"
+
+// This class is the XPCOM half of the glue between the
+// nsIWebBrowserPersistDocument interface and a remote document; it is
+// created by WebBrowserPersistDocumentParent when (and if) it
+// receives the information needed to populate the interface's
+// properties.
+//
+// This object has a normal refcounted lifetime.  The corresponding
+// IPC actor holds a weak reference to this class; when the last
+// strong reference is released, it sends an IPC delete message and
+// thereby removes that reference.
+
+namespace mozilla {
+
+class WebBrowserPersistDocumentParent;
+
+class WebBrowserPersistRemoteDocument final
+    : public nsIWebBrowserPersistDocument
+{
+public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIWEBBROWSERPERSISTDOCUMENT
+
+private:
+    using Attrs = WebBrowserPersistDocumentAttrs;
+    WebBrowserPersistDocumentParent* mActor;
+    Attrs mAttrs;
+    nsCOMPtr<nsIInputStream> mPostData;
+
+    friend class WebBrowserPersistDocumentParent;
+    WebBrowserPersistRemoteDocument(WebBrowserPersistDocumentParent* aActor,
+                                    const Attrs& aAttrs,
+                                    nsIInputStream* aPostData);
+    ~WebBrowserPersistRemoteDocument();
+
+    void ActorDestroy(void);
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistRemoteDocument_h__
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistResourcesChild.h"
+
+#include "WebBrowserPersistDocumentChild.h"
+#include "mozilla/dom/PBrowserChild.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(WebBrowserPersistResourcesChild,
+                  nsIWebBrowserPersistResourceVisitor)
+
+WebBrowserPersistResourcesChild::WebBrowserPersistResourcesChild()
+{
+}
+
+WebBrowserPersistResourcesChild::~WebBrowserPersistResourcesChild()
+{
+}
+
+NS_IMETHODIMP
+WebBrowserPersistResourcesChild::VisitResource(nsIWebBrowserPersistDocument *aDocument,
+                                               const nsACString& aURI)
+{
+    nsCString copiedURI(aURI); // Yay, XPIDL/IPDL mismatch.
+    SendVisitResource(copiedURI);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistResourcesChild::VisitDocument(nsIWebBrowserPersistDocument* aDocument,
+                                               nsIWebBrowserPersistDocument* aSubDocument)
+{
+    auto* subActor = new WebBrowserPersistDocumentChild();
+    dom::PBrowserChild* grandManager = Manager()->Manager();
+    if (!grandManager->SendPWebBrowserPersistDocumentConstructor(subActor)) {
+        // NOTE: subActor is freed at this point.
+        return NS_ERROR_FAILURE;
+    }
+    // ...but here, IPC won't free subActor until after this returns
+    // to the event loop.
+
+    // The order of these two messages will be preserved, because
+    // they're the same toplevel protocol and priority.
+    //
+    // With this ordering, it's always the transition out of START
+    // state that causes a document's parent actor to be exposed to
+    // XPCOM (for both parent->child and child->parent construction),
+    // which simplifies the lifetime management.
+    SendVisitDocument(subActor);
+    subActor->Start(aSubDocument);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistResourcesChild::EndVisit(nsIWebBrowserPersistDocument *aDocument,
+                                          nsresult aStatus)
+{
+    Send__delete__(this, aStatus);
+    return NS_OK;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistResourcesChild_h__
+#define WebBrowserPersistResourcesChild_h__
+
+#include "mozilla/PWebBrowserPersistResourcesChild.h"
+
+#include "nsIWebBrowserPersistDocument.h"
+
+namespace mozilla {
+
+class WebBrowserPersistResourcesChild final
+    : public PWebBrowserPersistResourcesChild
+    , public nsIWebBrowserPersistResourceVisitor
+{
+public:
+    WebBrowserPersistResourcesChild();
+
+    NS_DECL_NSIWEBBROWSERPERSISTRESOURCEVISITOR
+    NS_DECL_ISUPPORTS
+private:
+    virtual ~WebBrowserPersistResourcesChild();
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistDocumentChild_h__
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistResourcesParent.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(WebBrowserPersistResourcesParent,
+                  nsIWebBrowserPersistDocumentReceiver)
+
+WebBrowserPersistResourcesParent::WebBrowserPersistResourcesParent(
+        nsIWebBrowserPersistDocument* aDocument,
+        nsIWebBrowserPersistResourceVisitor* aVisitor)
+: mDocument(aDocument)
+, mVisitor(aVisitor)
+{
+    MOZ_ASSERT(aDocument);
+    MOZ_ASSERT(aVisitor);
+}
+
+WebBrowserPersistResourcesParent::~WebBrowserPersistResourcesParent()
+{
+}
+
+void
+WebBrowserPersistResourcesParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+    if (aWhy != Deletion && mVisitor) {
+        mVisitor->EndVisit(mDocument, NS_ERROR_FAILURE);
+    }
+    mVisitor = nullptr;
+}
+
+bool
+WebBrowserPersistResourcesParent::Recv__delete__(const nsresult& aStatus)
+{
+    mVisitor->EndVisit(mDocument, aStatus);
+    mVisitor = nullptr;
+    return true;
+}
+
+bool
+WebBrowserPersistResourcesParent::RecvVisitResource(const nsCString& aURI)
+{
+    mVisitor->VisitResource(mDocument, aURI);
+    return true;
+}
+
+bool
+WebBrowserPersistResourcesParent::RecvVisitDocument(PWebBrowserPersistDocumentParent* aSubDocument)
+{
+    // Don't expose the subdocument to the visitor until it's ready
+    // (until the actor isn't in START state).
+    static_cast<WebBrowserPersistDocumentParent*>(aSubDocument)
+        ->SetOnReady(this);
+    return true;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistResourcesParent::OnDocumentReady(nsIWebBrowserPersistDocument* aSubDocument)
+{
+    if (!mVisitor) {
+        return NS_ERROR_FAILURE;
+    }
+    mVisitor->VisitDocument(mDocument, aSubDocument);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistResourcesParent::OnError(nsresult aFailure)
+{
+    // Nothing useful to do but ignore the failed document.
+    return NS_OK;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistResourcesParent_h__
+#define WebBrowserPersistResourcesParent_h__
+
+#include "mozilla/PWebBrowserPersistResourcesParent.h"
+
+#include "WebBrowserPersistDocumentParent.h"
+#include "nsCOMPtr.h"
+#include "nsIWebBrowserPersistDocument.h"
+
+namespace mozilla {
+
+class WebBrowserPersistResourcesParent final
+    : public PWebBrowserPersistResourcesParent
+    , public nsIWebBrowserPersistDocumentReceiver
+{
+public:
+    WebBrowserPersistResourcesParent(nsIWebBrowserPersistDocument* aDocument,
+                                     nsIWebBrowserPersistResourceVisitor* aVisitor);
+
+    virtual bool
+    RecvVisitResource(const nsCString& aURI) override;
+
+    virtual bool
+    RecvVisitDocument(PWebBrowserPersistDocumentParent* aSubDocument) override;
+
+    virtual bool
+    Recv__delete__(const nsresult& aStatus) override;
+
+    virtual void
+    ActorDestroy(ActorDestroyReason aWhy) override;
+
+    NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER
+    NS_DECL_ISUPPORTS
+
+private:
+    // Note: even if the XPIDL didn't need mDocument for visitor
+    // callbacks, this object still needs to hold a strong reference
+    // to it to defer actor subtree deletion until after the
+    // visitation is finished.
+    nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
+    nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor;
+
+    virtual ~WebBrowserPersistResourcesParent();
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistResourcesParent_h__
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.cpp
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistSerializeChild.h"
+
+#include <algorithm>
+
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(WebBrowserPersistSerializeChild,
+                  nsIWebBrowserPersistWriteCompletion,
+                  nsIWebBrowserPersistURIMap,
+                  nsIOutputStream)
+
+WebBrowserPersistSerializeChild::WebBrowserPersistSerializeChild(const WebBrowserPersistURIMap& aMap)
+: mMap(aMap)
+{
+}
+
+WebBrowserPersistSerializeChild::~WebBrowserPersistSerializeChild()
+{
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::OnFinish(nsIWebBrowserPersistDocument* aDocument,
+                                          nsIOutputStream* aStream,
+                                          const nsACString& aContentType,
+                                          nsresult aStatus)
+{
+    MOZ_ASSERT(aStream == this);
+    nsCString contentType(aContentType);
+    Send__delete__(this, contentType, aStatus);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::GetNumMappedURIs(uint32_t* aNum)
+{
+    *aNum = static_cast<uint32_t>(mMap.mapURIs().Length());
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::GetURIMapping(uint32_t aIndex,
+                                               nsACString& aMapFrom,
+                                               nsACString& aMapTo)
+{
+    if (aIndex >= mMap.mapURIs().Length()) {
+        return NS_ERROR_INVALID_ARG;
+    }
+    aMapFrom = mMap.mapURIs()[aIndex].mapFrom();
+    aMapTo = mMap.mapURIs()[aIndex].mapTo();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::GetTargetBaseURI(nsACString& aURI)
+{
+    aURI = mMap.targetBaseURI();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::Close()
+{
+    NS_WARNING("WebBrowserPersistSerializeChild::Close()");
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::Flush()
+{
+    NS_WARNING("WebBrowserPersistSerializeChild::Flush()");
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::Write(const char* aBuf, uint32_t aCount,
+                                       uint32_t* aWritten)
+{
+    // Normally an nsIOutputStream would have to be thread-safe, but
+    // nsDocumentEncoder currently doesn't call this off the main
+    // thread (which also means it's difficult to test the
+    // thread-safety code this class doesn't yet have).
+    //
+    // This is *not* an NS_ERROR_NOT_IMPLEMENTED, because at this
+    // point we've probably already misused the non-thread-safe
+    // refcounting.
+    MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Fix this class to be thread-safe.");
+
+    // Limit the size of an individual IPC message.
+    static const uint32_t kMaxWrite = 65536;
+
+    // Work around bug 1181433 by sending multiple messages if
+    // necessary to write the entire aCount bytes, even though
+    // nsIOutputStream.idl says we're allowed to do a short write.
+    const char* buf = aBuf;
+    uint32_t count = aCount;
+    *aWritten = 0;
+    while (count > 0) {
+        uint32_t toWrite = std::min(kMaxWrite, count);
+        nsTArray<uint8_t> arrayBuf;
+        // It would be nice if this extra copy could be avoided.
+        arrayBuf.AppendElements(buf, toWrite);
+        SendWriteData(Move(arrayBuf));
+        *aWritten += toWrite;
+        buf += toWrite;
+        count -= toWrite;
+    }
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::WriteFrom(nsIInputStream* aFrom,
+                                           uint32_t aCount,
+                                           uint32_t* aWritten)
+{
+    NS_WARNING("WebBrowserPersistSerializeChild::WriteFrom()");
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::WriteSegments(nsReadSegmentFun aFun,
+                                               void* aCtx,
+                                               uint32_t aCount,
+                                               uint32_t* aWritten)
+{
+    NS_WARNING("WebBrowserPersistSerializeChild::WriteSegments()");
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserPersistSerializeChild::IsNonBlocking(bool* aNonBlocking)
+{
+    // Writes will never fail with NS_BASE_STREAM_WOULD_BLOCK, so:
+    *aNonBlocking = false;
+    return NS_OK;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistSerializeChild_h__
+#define WebBrowserPersistSerializeChild_h__
+
+#include "mozilla/PWebBrowserPersistSerializeChild.h"
+
+#include "mozilla/PWebBrowserPersistDocument.h"
+#include "nsIWebBrowserPersistDocument.h"
+#include "nsIOutputStream.h"
+
+namespace mozilla {
+
+class WebBrowserPersistSerializeChild final
+    : public PWebBrowserPersistSerializeChild
+    , public nsIWebBrowserPersistWriteCompletion
+    , public nsIWebBrowserPersistURIMap
+    , public nsIOutputStream
+{
+public:
+    explicit WebBrowserPersistSerializeChild(const WebBrowserPersistURIMap& aMap);
+
+    NS_DECL_NSIWEBBROWSERPERSISTWRITECOMPLETION
+    NS_DECL_NSIWEBBROWSERPERSISTURIMAP
+    NS_DECL_NSIOUTPUTSTREAM
+    NS_DECL_ISUPPORTS
+private:
+    WebBrowserPersistURIMap mMap;
+
+    virtual ~WebBrowserPersistSerializeChild();
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistSerializeChild_h__
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.cpp
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebBrowserPersistSerializeParent.h"
+
+#include "nsReadableUtils.h"
+
+namespace mozilla {
+
+WebBrowserPersistSerializeParent::WebBrowserPersistSerializeParent(
+        nsIWebBrowserPersistDocument* aDocument,
+        nsIOutputStream* aStream,
+        nsIWebBrowserPersistWriteCompletion* aFinish)
+: mDocument(aDocument)
+, mStream(aStream)
+, mFinish(aFinish)
+, mOutputError(NS_OK)
+{
+    MOZ_ASSERT(aDocument);
+    MOZ_ASSERT(aStream);
+    MOZ_ASSERT(aFinish);
+}
+
+WebBrowserPersistSerializeParent::~WebBrowserPersistSerializeParent()
+{
+}
+
+bool
+WebBrowserPersistSerializeParent::RecvWriteData(nsTArray<uint8_t>&& aData)
+{
+    if (NS_FAILED(mOutputError)) {
+        return true;
+    }
+
+    uint32_t written = 0;
+    static_assert(sizeof(char) == sizeof(uint8_t),
+                  "char must be (at least?) 8 bits");
+    const char* data = reinterpret_cast<const char*>(aData.Elements());
+    // nsIOutputStream::Write is allowed to return short writes.
+    while (written < aData.Length()) {
+        uint32_t writeReturn;
+        nsresult rv = mStream->Write(data + written,
+                                     aData.Length() - written,
+                                     &writeReturn);
+        if (NS_FAILED(rv)) {
+            mOutputError = rv;
+            return true;
+        }
+        written += writeReturn;
+    }
+    return true;
+}
+
+bool
+WebBrowserPersistSerializeParent::Recv__delete__(const nsCString& aContentType,
+                                                 const nsresult& aStatus)
+{
+    if (NS_SUCCEEDED(mOutputError)) {
+        mOutputError = aStatus;
+    }
+    mFinish->OnFinish(mDocument,
+                      mStream,
+                      aContentType,
+                      mOutputError);
+    mFinish = nullptr;
+    return true;
+}
+
+void
+WebBrowserPersistSerializeParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+    if (mFinish) {
+        MOZ_ASSERT(aWhy != Deletion);
+        mFinish->OnFinish(mDocument, mStream, EmptyCString(), NS_ERROR_FAILURE);
+        mFinish = nullptr;
+    }
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebBrowserPersistSerializeParent_h__
+#define WebBrowserPersistSerializeParent_h__
+
+#include "mozilla/PWebBrowserPersistSerializeParent.h"
+
+#include "nsCOMPtr.h"
+#include "nsIOutputStream.h"
+#include "nsIWebBrowserPersistDocument.h"
+
+namespace mozilla {
+
+class WebBrowserPersistSerializeParent
+    : public PWebBrowserPersistSerializeParent
+{
+public:
+    WebBrowserPersistSerializeParent(
+        nsIWebBrowserPersistDocument* aDocument,
+        nsIOutputStream* aStream,
+        nsIWebBrowserPersistWriteCompletion* aFinish);
+    virtual ~WebBrowserPersistSerializeParent();
+
+    virtual bool
+    RecvWriteData(nsTArray<uint8_t>&& aData) override;
+
+    virtual bool
+    Recv__delete__(const nsCString& aContentType,
+                   const nsresult& aStatus) override;
+
+    virtual void
+    ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+    // See also ...ReadParent::mDocument for the other reason this
+    // strong reference needs to be here.
+    nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
+    nsCOMPtr<nsIOutputStream> mStream;
+    nsCOMPtr<nsIWebBrowserPersistWriteCompletion> mFinish;
+    nsresult mOutputError;
+};
+
+} // namespace mozilla
+
+#endif // WebBrowserPersistSerializeParent_h__
--- a/embedding/components/webbrowserpersist/moz.build
+++ b/embedding/components/webbrowserpersist/moz.build
@@ -2,23 +2,47 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 XPIDL_SOURCES += [
     'nsCWebBrowserPersist.idl',
     'nsIWebBrowserPersist.idl',
+    'nsIWebBrowserPersistable.idl',
+    'nsIWebBrowserPersistDocument.idl',
 ]
 
 XPIDL_MODULE = 'webbrowserpersist'
 
+IPDL_SOURCES += [
+    'PWebBrowserPersistDocument.ipdl',
+    'PWebBrowserPersistResources.ipdl',
+    'PWebBrowserPersistSerialize.ipdl',
+]
+
 SOURCES += [
     'nsWebBrowserPersist.cpp',
+    'WebBrowserPersistDocumentChild.cpp',
+    'WebBrowserPersistDocumentParent.cpp',
+    'WebBrowserPersistLocalDocument.cpp',
+    'WebBrowserPersistRemoteDocument.cpp',
+    'WebBrowserPersistResourcesChild.cpp',
+    'WebBrowserPersistResourcesParent.cpp',
+    'WebBrowserPersistSerializeChild.cpp',
+    'WebBrowserPersistSerializeParent.cpp',
+]
+
+EXPORTS.mozilla += [
+    'WebBrowserPersistDocumentChild.h',
+    'WebBrowserPersistDocumentParent.h',
+    'WebBrowserPersistLocalDocument.h',
 ]
 
 FAIL_ON_WARNINGS = True
 
+include('/ipc/chromium/chromium-config.mozbuild')
+
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/base',
     '/dom/html',
 ]
--- a/embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl
+++ b/embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl
@@ -12,17 +12,17 @@ interface nsIDOMDocument;
 interface nsIWebProgressListener;
 interface nsIFile;
 interface nsIChannel;
 interface nsILoadContext;
 
 /**
  * Interface for persisting DOM documents and URIs to local or remote storage.
  */
-[scriptable, uuid(effe7374-5c7d-4b1e-a96f-27b283df189b)]
+[scriptable, uuid(8cd752a4-60b1-42c3-a819-65c7a1138a28)]
 interface nsIWebBrowserPersist : nsICancelable
 {
   /** No special persistence behaviour. */
   const unsigned long PERSIST_FLAGS_NONE = 0;
   /** Use cached data if present (skipping validation), else load from network */
   const unsigned long PERSIST_FLAGS_FROM_CACHE = 1;
   /** Bypass the cached data. */
   const unsigned long PERSIST_FLAGS_BYPASS_CACHE = 2;
@@ -243,38 +243,41 @@ interface nsIWebBrowserPersist : nsICanc
 
   /**
    * Save the specified DOM document to file and optionally all linked files
    * (e.g. images, CSS, JS & subframes). Do not call this method until the
    * document has finished loading!
    *
    * @param aDocument          Document to save to file. Some implementations of
    *                           this interface may also support <CODE>nullptr</CODE>
-   *                           to imply the currently loaded document.
+   *                           to imply the currently loaded document.  Can be an
+   *                           nsIWebBrowserPersistDocument or nsIDOMDocument.
    * @param aFile              Target local file. This may be a nsIFile object or an
    *                           nsIURI object with a file scheme or a scheme that
    *                           supports uploading (e.g. ftp).
    * @param aDataPath          Path to directory where URIs linked to the document
    *                           are saved or nullptr if no linked URIs should be saved.
    *                           This may be a nsIFile object or an nsIURI object
    *                           with a file scheme.
    * @param aOutputContentType The desired MIME type format to save the 
    *                           document and all subdocuments into or nullptr to use
    *                           the default behaviour.
    * @param aEncodingFlags     Flags to pass to the encoder.
    * @param aWrapColumn        For text documents, indicates the desired width to
    *                           wrap text at. Parameter is ignored if wrapping is not
    *                           specified by the encoding flags.
    *
+   * @see nsIWebBrowserPersistDocument
+   * @see nsIWebBrowserPersistable
    * @see nsIFile
    * @see nsIURI
    *
    * @throws NS_ERROR_INVALID_ARG One or more arguments was invalid.
    */
-  void saveDocument(in nsIDOMDocument aDocument,
+  void saveDocument(in nsISupports aDocument,
      in nsISupports aFile, in nsISupports aDataPath,
      in string aOutputContentType, in unsigned long aEncodingFlags,
      in unsigned long aWrapColumn);
 
   /**
    * Cancels the current operation. The caller is responsible for cleaning up
    * partially written files or directories. This has the same effect as calling
    * cancel with an argument of NS_BINDING_ABORTED.
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/nsIWebBrowserPersistDocument.idl
@@ -0,0 +1,197 @@
+/* -*- Mode: IDL; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMDocument;
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsITabParent;
+interface nsIWebBrowserPersistResourceVisitor;
+interface nsIWebBrowserPersistWriteCompletion;
+
+/**
+ * Interface for the URI-mapping information that can be supplied when
+ * serializing the DOM of an nsIWebBrowserPersistDocument.
+ *
+ * @see nsIWebBrowserPersistDocument
+ */
+[scriptable, uuid(d52e8b93-2771-45e8-a5b0-6e12b667046b)]
+interface nsIWebBrowserPersistURIMap : nsISupports
+{
+  /**
+   * The number of URI mappings.
+   */
+  readonly attribute unsigned long numMappedURIs;
+
+  /**
+   * Obtain the URI mapping at the given index, which must be less than
+   * numMappedURIs, as a pair of URI spec strings.
+   */
+  void getURIMapping(in unsigned long aIndex,
+                     out AUTF8String aMapFrom,
+                     out AUTF8String aMapTo);
+
+  /**
+   * The spec of the base URI that the document will have after it is
+   * serialized.
+   */
+  readonly attribute AUTF8String targetBaseURI;
+};
+
+/**
+ * Interface representing a document that can be serialized with
+ * nsIWebBrowserPersist; it may or may not be in this process.  Some
+ * information is exposed as attributes, which may or may not reflect
+ * changes made to the underlying document; most of these are
+ * self-explanatory from their names and types.
+ */
+[scriptable, uuid(74aa4918-5d15-46b6-9ccf-74f9696d721d)]
+interface nsIWebBrowserPersistDocument : nsISupports
+{
+  readonly attribute boolean isPrivate;
+  readonly attribute AUTF8String documentURI;
+  readonly attribute AUTF8String baseURI;
+  readonly attribute ACString contentType;
+  readonly attribute ACString characterSet;
+  readonly attribute AString title;
+  readonly attribute AString referrer;
+  readonly attribute AString contentDisposition;
+  readonly attribute nsIInputStream postData;
+
+  /**
+   * The cache key.  Unlike in nsISHEntry, where it's wrapped in an
+   * nsISupportsPRUint32, this is just the integer.
+   */
+  readonly attribute unsigned long cacheKey;
+
+  /**
+   * This attribute is set by nsIWebBrowserPersist implementations to
+   * propagate persist flags that apply to the DOM traversal and
+   * serialization (rather than to managing file I/O).
+   */
+  attribute unsigned long persistFlags;
+
+  /**
+   * Walk the DOM searching for external resources needed to render it.
+   * The visitor callbacks may be called either before or after
+   * readResources returns.
+   *
+   * @see nsIWebBrowserPersistResourceVisitor
+   */
+  void readResources(in nsIWebBrowserPersistResourceVisitor aVisitor);
+
+  /**
+   * Serialize the document's DOM.
+   *
+   * @param aStream       The output stream to write the document to.
+   *
+   * @param aURIMap       Optional; specifies URI rewriting to perform on
+   *                      external references (as read by readResources).
+   *                      If given, also causes relative hyperlinks to be
+   *                      converted to absolute in the written text.
+   *
+   * @param aRequestedContentType
+   *                      The desired MIME type to save the document as;
+   *                      optional and defaults to the document's type.
+   *                      (If no encoder exists for that type, "text/html"
+   *                      is used instead.)
+   *
+   * @param aEncoderFlags Flags to pass to the encoder.
+   *
+   * @param aWrapColumn   Desired text width, ignored if wrapping is not
+   *                      specified by the encoding flags, or if 0.
+   *
+   * @param aCompletion   Callback invoked when writing is complete.
+   *                      It may be called either before or after writeContent
+   *                      returns.
+   *
+   * @see nsIDocumentEncoder
+   */
+  void writeContent(in nsIOutputStream aStream,
+                    in nsIWebBrowserPersistURIMap aURIMap,
+                    in ACString aRequestedContentType,
+                    in unsigned long aEncoderFlags,
+                    in unsigned long aWrapColumn,
+                    in nsIWebBrowserPersistWriteCompletion aCompletion);
+};
+
+/**
+ * Asynchronous visitor that receives external resources linked by an
+ * nsIWebBrowserPersistDocument and which are needed to render the
+ * document.
+ */
+[scriptable, uuid(8ce37706-b7d3-481a-be68-54f174fc0d0a)]
+interface nsIWebBrowserPersistResourceVisitor : nsISupports
+{
+  /**
+   * Indicates a resource that is not a document; e.g., an image, script,
+   * or stylesheet.
+   *
+   * @param aDocument   The document containing the reference.
+   * @param aURI        The absolute URI spec for the referenced resource.
+   */
+  void visitResource(in nsIWebBrowserPersistDocument aDocument,
+                     in AUTF8String aURI);
+  /**
+   * Indicates a subdocument resource; e.g., a frame or iframe.
+   *
+   * @param aDocument     The document containing the reference.
+   * @param aSubDocument  The referenced document.
+   */
+  void visitDocument(in nsIWebBrowserPersistDocument aDocument,
+                     in nsIWebBrowserPersistDocument aSubDocument);
+
+  /**
+   * Indicates that the document traversal is complete.
+   *
+   * @param aDocument   The document that was being traversed.
+   * @param aStatus     Indicates whether the traversal encountered an error.
+   */
+  void endVisit(in nsIWebBrowserPersistDocument aDocument,
+                in nsresult aStatus);
+};
+
+/**
+ * Asynchronous callback for when nsIWebBrowserPersistDocument is finished
+ * serializing the document's DOM.
+ */
+[scriptable, function, uuid(a07e6892-38ae-4207-8340-7fa6ec446ed6)]
+interface nsIWebBrowserPersistWriteCompletion : nsISupports
+{
+  /**
+   * Indicates that serialization is finished.
+   *
+   * @param aDocument     The document that was being serialized.
+   *
+   * @param aStream       The stream that was being written to.  If it
+   *                      needs to be closed, the callback must do that;
+   *                      the serialization process leaves it open.
+   *
+   * @param aContentType  The content type with which the document was
+   *                      actually serialized; this may be useful to set
+   *                      metadata on the result, or if uploading it.
+   *
+   * @param aStatus       Indicates whether serialization encountered an error.
+   */
+  void onFinish(in nsIWebBrowserPersistDocument aDocument,
+                in nsIOutputStream aStream,
+                in ACString aContentType,
+                in nsresult aStatus);
+};
+
+/**
+ * Asynchronous callback for creating a persistable document from some
+ * other object.
+ *
+ * @see nsIWebBrowserPersistable.
+ */
+[scriptable, uuid(321e3174-594f-4036-b7be-791b821bd376)]
+interface nsIWebBrowserPersistDocumentReceiver : nsISupports
+{
+  void onDocumentReady(in nsIWebBrowserPersistDocument aDocument);
+  void onError(in nsresult aFailure);
+};
new file mode 100644
--- /dev/null
+++ b/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl
@@ -0,0 +1,30 @@
+/* -*- Mode: IDL; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIWebBrowserPersistDocumentReceiver;
+
+/**
+ * Interface for objects which represent a document that can be
+ * serialized with nsIWebBrowserPersist.  This interface is
+ * asynchronous because the actual document can be in another process
+ * (e.g., if this object is an nsFrameLoader for an out-of-process
+ * frame).
+ *
+ * Warning: this is currently implemented only by nsFrameLoader, and
+ * may change in the future to become more frame-loader-specific or be
+ * merged into nsIFrameLoader.  See bug 1101100 comment #34.
+ *
+ * @see nsIWebBrowserPersistDocumentReceiver
+ * @see nsIWebBrowserPersistDocument
+ * @see nsIWebBrowserPersist
+ */
+[scriptable, function, uuid(24d0dc9e-b970-4cca-898f-cbba03abaa73)]
+interface nsIWebBrowserPersistable : nsISupports
+{
+  void startPersistence(in nsIWebBrowserPersistDocumentReceiver aRecv);
+};
--- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp
+++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp
@@ -31,106 +31,87 @@
 #include "nsSupportsArray.h"
 #include "nsContentCID.h"
 #include "nsStreamUtils.h"
 
 #include "nsCExternalHandlerService.h"
 
 #include "nsIURL.h"
 #include "nsIFileURL.h"
-#include "nsIDocument.h"
-#include "nsIDOMDocument.h"
-#include "nsIDOMXMLDocument.h"
-#include "nsIDOMTreeWalker.h"
-#include "nsIDOMNode.h"
-#include "nsIDOMComment.h"
-#include "nsIDOMMozNamedAttrMap.h"
-#include "nsIDOMAttr.h"
-#include "nsIDOMNodeList.h"
 #include "nsIWebProgressListener.h"
 #include "nsIAuthPrompt.h"
 #include "nsIPrompt.h"
 #include "nsISHEntry.h"
 #include "nsIWebPageDescriptor.h"
 #include "nsIFormControl.h"
 #include "nsContentUtils.h"
 
-#include "nsIDOMNodeFilter.h"
-#include "nsIDOMProcessingInstruction.h"
-#include "nsIDOMHTMLAnchorElement.h"
-#include "nsIDOMHTMLAreaElement.h"
-#include "nsIDOMHTMLCollection.h"
-#include "nsIDOMHTMLImageElement.h"
-#include "nsIDOMHTMLScriptElement.h"
-#include "nsIDOMHTMLLinkElement.h"
-#include "nsIDOMHTMLBaseElement.h"
-#include "nsIDOMHTMLFrameElement.h"
-#include "nsIDOMHTMLIFrameElement.h"
-#include "nsIDOMHTMLInputElement.h"
-#include "nsIDOMHTMLEmbedElement.h"
-#include "nsIDOMHTMLObjectElement.h"
-#include "nsIDOMHTMLAppletElement.h"
-#include "nsIDOMHTMLOptionElement.h"
-#include "nsIDOMHTMLTextAreaElement.h"
-#include "nsIDOMHTMLDocument.h"
-#include "nsIDOMHTMLSourceElement.h"
-#include "nsIDOMHTMLMediaElement.h"
-
 #include "nsIImageLoadingContent.h"
 
 #include "ftpCore.h"
 #include "nsITransport.h"
 #include "nsISocketTransport.h"
 #include "nsIStringBundle.h"
 #include "nsIProtocolHandler.h"
 
+#include "nsIWebBrowserPersistable.h"
 #include "nsWebBrowserPersist.h"
+#include "WebBrowserPersistLocalDocument.h"
 
 #include "nsIContent.h"
 #include "nsIMIMEInfo.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/dom/HTMLSharedElement.h"
 #include "mozilla/dom/HTMLSharedObjectElement.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 // Buffer file writes in 32kb chunks
 #define BUFFERED_OUTPUT_SIZE (1024 * 32)
 
+struct nsWebBrowserPersist::WalkData
+{
+    nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
+    nsCOMPtr<nsIURI> mFile;
+    nsCOMPtr<nsIURI> mDataPath;
+};
+
 // Information about a DOM document
-struct DocData
+struct nsWebBrowserPersist::DocData
 {
     nsCOMPtr<nsIURI> mBaseURI;
-    nsCOMPtr<nsIDOMDocument> mDocument;
+    nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
     nsCOMPtr<nsIURI> mFile;
     nsCOMPtr<nsIURI> mDataPath;
     bool mDataPathIsRelative;
     nsCString mRelativePathToData;
     nsCString mCharset;
 };
 
 // Information about a URI
-struct URIData
+struct nsWebBrowserPersist::URIData
 {
     bool mNeedsPersisting;
     bool mSaved;
     bool mIsSubFrame;
     bool mDataPathIsRelative;
     bool mNeedsFixup;
     nsString mFilename;
     nsString mSubFrameExt;
     nsCOMPtr<nsIURI> mFile;
     nsCOMPtr<nsIURI> mDataPath;
     nsCString mRelativePathToData;
     nsCString mCharset;
+
+    nsresult GetLocalURI(nsCString& aSpec);
 };
 
 // Information about the output stream
-struct OutputData
+struct nsWebBrowserPersist::OutputData
 {
     nsCOMPtr<nsIURI> mFile;
     nsCOMPtr<nsIURI> mOriginalLocation;
     nsCOMPtr<nsIOutputStream> mStream;
     int64_t mSelfProgress;
     int64_t mSelfProgressMax;
     bool mCalcFileExt;
 
@@ -146,39 +127,142 @@ struct OutputData
     {
         if (mStream)
         {
             mStream->Close();
         }
     }
 };
 
-struct UploadData
+struct nsWebBrowserPersist::UploadData
 {
     nsCOMPtr<nsIURI> mFile;
     int64_t mSelfProgress;
     int64_t mSelfProgressMax;
 
     explicit UploadData(nsIURI *aFile) :
         mFile(aFile),
         mSelfProgress(0),
         mSelfProgressMax(10000)
     {
     }
 };
 
-struct CleanupData
+struct nsWebBrowserPersist::CleanupData
 {
     nsCOMPtr<nsIFile> mFile;
     // Snapshot of what the file actually is at the time of creation so that if
     // it transmutes into something else later on it can be ignored. For example,
     // catch files that turn into dirs or vice versa.
     bool mIsDirectory;
 };
 
+class nsWebBrowserPersist::OnWalk final
+    : public nsIWebBrowserPersistResourceVisitor
+{
+public:
+    OnWalk(nsWebBrowserPersist* aParent, nsIURI* aFile, nsIFile* aDataPath)
+    : mParent(aParent)
+    , mFile(aFile)
+    , mDataPath(aDataPath)
+    { }
+
+    NS_DECL_NSIWEBBROWSERPERSISTRESOURCEVISITOR
+    NS_DECL_ISUPPORTS
+private:
+    nsRefPtr<nsWebBrowserPersist> mParent;
+    nsCOMPtr<nsIURI> mFile;
+    nsCOMPtr<nsIFile> mDataPath;
+
+    virtual ~OnWalk() { }
+};
+
+NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWalk,
+                  nsIWebBrowserPersistResourceVisitor)
+
+class nsWebBrowserPersist::OnWrite final
+    : public nsIWebBrowserPersistWriteCompletion
+{
+public:
+    OnWrite(nsWebBrowserPersist* aParent,
+            nsIURI* aFile,
+            nsIFile* aLocalFile)
+    : mParent(aParent)
+    , mFile(aFile)
+    , mLocalFile(aLocalFile)
+    { }
+
+    NS_DECL_NSIWEBBROWSERPERSISTWRITECOMPLETION
+    NS_DECL_ISUPPORTS
+private:
+    nsRefPtr<nsWebBrowserPersist> mParent;
+    nsCOMPtr<nsIURI> mFile;
+    nsCOMPtr<nsIFile> mLocalFile;
+
+    virtual ~OnWrite() { }
+};
+
+NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWrite,
+                  nsIWebBrowserPersistWriteCompletion)
+
+class nsWebBrowserPersist::FlatURIMap final
+    : public nsIWebBrowserPersistURIMap
+{
+public:
+    explicit FlatURIMap(const nsACString& aTargetBase)
+    : mTargetBase(aTargetBase) { }
+
+    void Add(const nsACString& aMapFrom, const nsACString& aMapTo) {
+        mMapFrom.AppendElement(aMapFrom);
+        mMapTo.AppendElement(aMapTo);
+    }
+
+    NS_DECL_NSIWEBBROWSERPERSISTURIMAP
+    NS_DECL_ISUPPORTS
+
+private:
+    nsTArray<nsCString> mMapFrom;
+    nsTArray<nsCString> mMapTo;
+    nsCString mTargetBase;
+
+    virtual ~FlatURIMap() { }
+};
+
+NS_IMPL_ISUPPORTS(nsWebBrowserPersist::FlatURIMap, nsIWebBrowserPersistURIMap)
+
+NS_IMETHODIMP
+nsWebBrowserPersist::FlatURIMap::GetNumMappedURIs(uint32_t* aNum)
+{
+    MOZ_ASSERT(mMapFrom.Length() == mMapTo.Length());
+    *aNum = mMapTo.Length();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserPersist::FlatURIMap::GetTargetBaseURI(nsACString& aTargetBase)
+{
+    aTargetBase = mTargetBase;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserPersist::FlatURIMap::GetURIMapping(uint32_t aIndex,
+                                      nsACString& aMapFrom,
+                                      nsACString& aMapTo)
+{
+    MOZ_ASSERT(mMapFrom.Length() == mMapTo.Length());
+    if (aIndex >= mMapTo.Length()) {
+        return NS_ERROR_INVALID_ARG;
+    }
+    aMapFrom = mMapFrom[aIndex];
+    aMapTo = mMapTo[aIndex];
+    return NS_OK;
+}
+
+
 // Maximum file length constant. The max file name length is
 // volume / server dependent but it is difficult to obtain
 // that information. Instead this constant is a reasonable value that
 // modern systems should able to cope with.
 const uint32_t kDefaultMaxFilenameLength = 64;
 
 // Default flags for persistence
 const uint32_t kDefaultPersistFlags =
@@ -187,18 +271,18 @@ const uint32_t kDefaultPersistFlags =
 
 // String bundle where error messages come from
 const char *kWebBrowserPersistStringBundle =
     "chrome://global/locale/nsWebBrowserPersist.properties";
 
 nsWebBrowserPersist::nsWebBrowserPersist() :
     mCurrentThingsToPersist(0),
     mFirstAndOnlyUse(true),
+    mSavingDocument(false),
     mCancel(false),
-    mJustStartedLoading(true),
     mCompleted(false),
     mStartSaving(false),
     mReplaceExisting(true),
     mSerializingOutput(false),
     mIsPrivate(false),
     mPersistFlags(kDefaultPersistFlags),
     mPersistResult(NS_OK),
     mTotalCurrentProgress(0),
@@ -378,108 +462,79 @@ NS_IMETHODIMP nsWebBrowserPersist::SaveC
 
     // SaveURI doesn't like broken uris.
     mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS;
     rv = SaveChannelInternal(aChannel, fileAsURI, false);
     return NS_FAILED(rv) ? rv : NS_OK;
 }
 
 
-/* void saveDocument (in nsIDOMDocument aDocument, in nsIURI aFileURI,
-   in nsIURI aDataPathURI, in string aOutputContentType,
-   in unsigned long aEncodingFlags, in unsigned long aWrapColumn); */
 NS_IMETHODIMP nsWebBrowserPersist::SaveDocument(
-    nsIDOMDocument *aDocument, nsISupports *aFile, nsISupports *aDataPath,
+    nsISupports *aDocument, nsISupports *aFile, nsISupports *aDataPath,
     const char *aOutputContentType, uint32_t aEncodingFlags, uint32_t aWrapColumn)
 {
     NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE);
     mFirstAndOnlyUse = false; // Stop people from reusing this object!
 
+    // We need a STATE_IS_NETWORK start/stop pair to bracket the
+    // notification callbacks.  For a whole document we generate those
+    // here and in EndDownload(), but for the single-request methods
+    // that's done in On{Start,Stop}Request instead.
+    mSavingDocument = true;
+
+    NS_ENSURE_ARG_POINTER(aDocument);
+    NS_ENSURE_ARG_POINTER(aFile);
+
     nsCOMPtr<nsIURI> fileAsURI;
     nsCOMPtr<nsIURI> datapathAsURI;
     nsresult rv;
 
-    nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDocument);
-    nsCOMPtr<nsILoadContext> privacyContext = doc ? doc->GetLoadContext() : nullptr;
-    mIsPrivate = privacyContext && privacyContext->UsePrivateBrowsing();
-
     rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI));
     NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
     if (aDataPath)
     {
         rv = GetValidURIFromObject(aDataPath, getter_AddRefs(datapathAsURI));
         NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
     }
 
     mWrapColumn = aWrapColumn;
-
-    // Produce nsIDocumentEncoder encoding flags
-    mEncodingFlags = 0;
-    if (aEncodingFlags & ENCODE_FLAGS_SELECTION_ONLY)
-        mEncodingFlags |= nsIDocumentEncoder::OutputSelectionOnly;
-    if (aEncodingFlags & ENCODE_FLAGS_FORMATTED)
-        mEncodingFlags |= nsIDocumentEncoder::OutputFormatted;
-    if (aEncodingFlags & ENCODE_FLAGS_RAW)
-        mEncodingFlags |= nsIDocumentEncoder::OutputRaw;
-    if (aEncodingFlags & ENCODE_FLAGS_BODY_ONLY)
-        mEncodingFlags |= nsIDocumentEncoder::OutputBodyOnly;
-    if (aEncodingFlags & ENCODE_FLAGS_PREFORMATTED)
-        mEncodingFlags |= nsIDocumentEncoder::OutputPreformatted;
-    if (aEncodingFlags & ENCODE_FLAGS_WRAP)
-        mEncodingFlags |= nsIDocumentEncoder::OutputWrap;
-    if (aEncodingFlags & ENCODE_FLAGS_FORMAT_FLOWED)
-        mEncodingFlags |= nsIDocumentEncoder::OutputFormatFlowed;
-    if (aEncodingFlags & ENCODE_FLAGS_ABSOLUTE_LINKS)
-        mEncodingFlags |= nsIDocumentEncoder::OutputAbsoluteLinks;
-    if (aEncodingFlags & ENCODE_FLAGS_ENCODE_BASIC_ENTITIES)
-        mEncodingFlags |= nsIDocumentEncoder::OutputEncodeBasicEntities;
-    if (aEncodingFlags & ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES)
-        mEncodingFlags |= nsIDocumentEncoder::OutputEncodeLatin1Entities;
-    if (aEncodingFlags & ENCODE_FLAGS_ENCODE_HTML_ENTITIES)
-        mEncodingFlags |= nsIDocumentEncoder::OutputEncodeHTMLEntities;
-    if (aEncodingFlags & ENCODE_FLAGS_ENCODE_W3C_ENTITIES)
-        mEncodingFlags |= nsIDocumentEncoder::OutputEncodeW3CEntities;
-    if (aEncodingFlags & ENCODE_FLAGS_CR_LINEBREAKS)
-        mEncodingFlags |= nsIDocumentEncoder::OutputCRLineBreak;
-    if (aEncodingFlags & ENCODE_FLAGS_LF_LINEBREAKS)
-        mEncodingFlags |= nsIDocumentEncoder::OutputLFLineBreak;
-    if (aEncodingFlags & ENCODE_FLAGS_NOSCRIPT_CONTENT)
-        mEncodingFlags |= nsIDocumentEncoder::OutputNoScriptContent;
-    if (aEncodingFlags & ENCODE_FLAGS_NOFRAMES_CONTENT)
-        mEncodingFlags |= nsIDocumentEncoder::OutputNoFramesContent;
+    mEncodingFlags = aEncodingFlags;
 
     if (aOutputContentType)
     {
         mContentType.AssignASCII(aOutputContentType);
     }
 
-    rv = SaveDocumentInternal(aDocument, fileAsURI, datapathAsURI);
-
-    // Now save the URIs that have been gathered
-
-    if (NS_SUCCEEDED(rv) && datapathAsURI)
-    {
-        rv = SaveGatheredURIs(fileAsURI);
+    // State start notification
+    if (mProgressListener) {
+        mProgressListener->OnStateChange(nullptr, nullptr,
+            nsIWebProgressListener::STATE_START
+            | nsIWebProgressListener::STATE_IS_NETWORK, NS_OK);
     }
-    else if (mProgressListener)
-    {
-        // tell the listener we're done
-        mProgressListener->OnStateChange(nullptr, nullptr,
-                                         nsIWebProgressListener::STATE_START |
-                                         nsIWebProgressListener::STATE_IS_NETWORK,
-                                         NS_OK);
-        mProgressListener->OnStateChange(nullptr, nullptr,
-                                         nsIWebProgressListener::STATE_STOP |
-                                         nsIWebProgressListener::STATE_IS_NETWORK,
-                                         rv);
+
+    nsCOMPtr<nsIWebBrowserPersistDocument> doc = do_QueryInterface(aDocument);
+    if (!doc) {
+        nsCOMPtr<nsIDocument> localDoc = do_QueryInterface(aDocument);
+        if (localDoc) {
+            doc = new mozilla::WebBrowserPersistLocalDocument(localDoc);
+        } else {
+            rv = NS_ERROR_NO_INTERFACE;
+        }
     }
-
+    if (doc) {
+        rv = SaveDocumentInternal(doc, fileAsURI, datapathAsURI);
+    }
+    if (NS_FAILED(rv)) {
+        SendErrorStatusChange(true, rv, nullptr, mURI);
+        EndDownload(rv);
+    }
     return rv;
 }
 
+/* void cancel(nsresult aReason); */
 NS_IMETHODIMP nsWebBrowserPersist::Cancel(nsresult aReason)
 {
     mCancel = true;
     EndDownload(aReason);
     return NS_OK;
 }
 
 
@@ -519,118 +574,185 @@ nsWebBrowserPersist::StartUpload(nsIInpu
 
     // add this to the upload list
     nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(destChannel);
     mUploadList.Put(keyPtr, new UploadData(aDestinationURI));
 
     return NS_OK;
 }
 
-nsresult
-nsWebBrowserPersist::SaveGatheredURIs(nsIURI *aFileAsURI)
+void
+nsWebBrowserPersist::SerializeNextFile()
 {
     nsresult rv = NS_OK;
-
+    MOZ_ASSERT(mWalkStack.Length() == 0);
+
+    // First, handle gathered URIs.
     // Count how many URIs in the URI map require persisting
     uint32_t urisToPersist = 0;
-    if (mURIMap.Count() > 0)
-    {
+    if (mURIMap.Count() > 0) {
+        // This is potentially O(n^2), when taking into account the
+        // number of times this method is called.  If it becomes a
+        // bottleneck, the count of not-yet-persisted URIs could be
+        // maintained separately.
         mURIMap.EnumerateRead(EnumCountURIsToPersist, &urisToPersist);
     }
 
-    if (urisToPersist > 0)
-    {
+    if (urisToPersist > 0) {
         // Persist each file in the uri map. The document(s)
         // will be saved after the last one of these is saved.
         mURIMap.EnumerateRead(EnumPersistURIs, this);
     }
 
-    // if we don't have anything in mOutputMap (added from above enumeration)
-    // then we build the doc list (SaveDocuments)
-    if (mOutputMap.Count() == 0)
-    {
-        // There are no URIs to save, so just save the document(s)
-
-        // State start notification
-        uint32_t addToStateFlags = 0;
-        if (mProgressListener)
-        {
-            if (mJustStartedLoading)
-            {
-                addToStateFlags |= nsIWebProgressListener::STATE_IS_NETWORK;
-            }
-            mProgressListener->OnStateChange(nullptr, nullptr,
-                nsIWebProgressListener::STATE_START | addToStateFlags, NS_OK);
+    // If there are downloads happening, wait until they're done; the
+    // OnStopRequest handler will call this method again.
+    if (mOutputMap.Count() > 0) {
+        return;
+    }
+
+    // If serializing, also wait until last upload is done.
+    if (mSerializingOutput && mUploadList.Count() > 0) {
+        return;
+    }
+
+    // If there are also no more documents, then we're done.
+    if (mDocList.Length() == 0) {
+        // ...or not quite done, if there are still uploads.
+        if (mUploadList.Count() > 0) {
+            return;
         }
-
-        rv = SaveDocuments();
-        if (NS_FAILED(rv))
+        // Finish and clean things up.  Defer this because the caller
+        // may have been expecting to use the listeners that that
+        // method will clear.
+        NS_DispatchToCurrentThread(NS_NewRunnableMethod(this,
+            &nsWebBrowserPersist::FinishDownload));
+        return;
+    }
+
+    // There are no URIs to save, so just save the next document.
+    mStartSaving = true;
+    mozilla::UniquePtr<DocData> docData(mDocList.ElementAt(0));
+    mDocList.RemoveElementAt(0); // O(n^2) but probably doesn't matter.
+    MOZ_ASSERT(docData);
+    if (!docData) {
+        EndDownload(NS_ERROR_FAILURE);
+        return;
+    }
+
+    mCurrentBaseURI = docData->mBaseURI;
+    mCurrentCharset = docData->mCharset;
+    mTargetBaseURI = docData->mFile;
+
+    // Save the document, fixing it up with the new URIs as we do
+
+    if (!mFlatURIMap) {
+        nsAutoCString targetBaseSpec;
+        if (mTargetBaseURI) {
+            rv = mTargetBaseURI->GetSpec(targetBaseSpec);
+            if (NS_FAILED(rv)) {
+                SendErrorStatusChange(true, rv, nullptr, nullptr);
+                EndDownload(rv);
+                return;
+            }
+        }
+        nsRefPtr<FlatURIMap> flatMap = new FlatURIMap(targetBaseSpec);
+        mURIMap.EnumerateRead(EnumCopyURIsToFlatMap, flatMap);
+        mFlatURIMap = flatMap.forget();
+    }
+
+    nsCOMPtr<nsIFile> localFile;
+    GetLocalFileFromURI(docData->mFile, getter_AddRefs(localFile));
+    if (localFile) {
+        // if we're not replacing an existing file but the file
+        // exists, something is wrong
+        bool fileExists = false;
+        rv = localFile->Exists(&fileExists);
+        if (NS_SUCCEEDED(rv) && !mReplaceExisting && fileExists) {
+            rv = NS_ERROR_FILE_ALREADY_EXISTS;
+        }
+        if (NS_FAILED(rv)) {
+            SendErrorStatusChange(false, rv, nullptr, docData->mFile);
             EndDownload(rv);
-        else if (aFileAsURI)
-        {
-            // local files won't trigger OnStopRequest so we call EndDownload here
-            bool isFile = false;
-            aFileAsURI->SchemeIs("file", &isFile);
-            if (isFile)
-                EndDownload(NS_OK);
-        }
-
-        // State stop notification
-        if (mProgressListener)
-        {
-            mProgressListener->OnStateChange(nullptr, nullptr,
-                nsIWebProgressListener::STATE_STOP | addToStateFlags, rv);
+            return;
         }
     }
-
-    return rv;
-}
-
-// this method returns true if there is another file to persist and false if not
-bool
-nsWebBrowserPersist::SerializeNextFile()
-{
-    if (!mSerializingOutput)
-    {
-        return false;
+    nsCOMPtr<nsIOutputStream> outputStream;
+    rv = MakeOutputStream(docData->mFile, getter_AddRefs(outputStream));
+    if (NS_SUCCEEDED(rv) && !outputStream) {
+        rv = NS_ERROR_FAILURE;
+    }
+    if (NS_FAILED(rv)) {
+        SendErrorStatusChange(false, rv, nullptr, docData->mFile);
+        EndDownload(rv);
+        return;
+    }
+
+    nsRefPtr<OnWrite> finish = new OnWrite(this, docData->mFile, localFile);
+    rv = docData->mDocument->WriteContent(outputStream,
+                                          mFlatURIMap,
+                                          NS_ConvertUTF16toUTF8(mContentType),
+                                          mEncodingFlags,
+                                          mWrapColumn,
+                                          finish);
+    if (NS_FAILED(rv)) {
+        SendErrorStatusChange(false, rv, nullptr, docData->mFile);
+        EndDownload(rv);
     }
-
-    nsresult rv = SaveGatheredURIs(nullptr);
-    if (NS_FAILED(rv))
-    {
-        return false;
+}
+
+NS_IMETHODIMP
+nsWebBrowserPersist::OnWrite::OnFinish(nsIWebBrowserPersistDocument* aDoc,
+                                       nsIOutputStream *aStream,
+                                       const nsACString& aContentType,
+                                       nsresult aStatus)
+{
+    nsresult rv = aStatus;
+
+    if (NS_FAILED(rv)) {
+        mParent->SendErrorStatusChange(false, rv, nullptr, mFile);
+        mParent->EndDownload(rv);
+        return NS_OK;
     }
-
-    return (mURIMap.Count()
-        || mUploadList.Count()
-        || mDocList.Length()
-        || mOutputMap.Count());
+    if (!mLocalFile) {
+        nsCOMPtr<nsIStorageStream> storStream(do_QueryInterface(aStream));
+        if (storStream) {
+            aStream->Close();
+            rv = mParent->StartUpload(storStream, mFile, aContentType);
+            if (NS_FAILED(rv)) {
+                mParent->SendErrorStatusChange(false, rv, nullptr, mFile);
+                mParent->EndDownload(rv);
+            }
+            // Either we failed and we're done, or we're uploading and
+            // the OnStopRequest callback is responsible for the next
+            // SerializeNextFile().
+            return NS_OK;
+        }
+    }
+    NS_DispatchToCurrentThread(NS_NewRunnableMethod(mParent,
+        &nsWebBrowserPersist::SerializeNextFile));
+    return NS_OK;
 }
 
-
 //*****************************************************************************
 // nsWebBrowserPersist::nsIRequestObserver
 //*****************************************************************************
 
 NS_IMETHODIMP nsWebBrowserPersist::OnStartRequest(
     nsIRequest* request, nsISupports *ctxt)
 {
     if (mProgressListener)
     {
         uint32_t stateFlags = nsIWebProgressListener::STATE_START |
                               nsIWebProgressListener::STATE_IS_REQUEST;
-        if (mJustStartedLoading)
-        {
+        if (!mSavingDocument) {
             stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK;
         }
         mProgressListener->OnStateChange(nullptr, request, stateFlags, NS_OK);
     }
 
-    mJustStartedLoading = false;
-
     nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
     NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
 
     nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
     OutputData *data = mOutputMap.Get(keyPtr);
 
     // NOTE: This code uses the channel as a hash key so it will not
     //       recognize redirected channels because the key is not the same.
@@ -692,78 +814,42 @@ NS_IMETHODIMP nsWebBrowserPersist::OnSta
     return NS_OK;
 }
 
 NS_IMETHODIMP nsWebBrowserPersist::OnStopRequest(
     nsIRequest* request, nsISupports *ctxt, nsresult status)
 {
     nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
     OutputData *data = mOutputMap.Get(keyPtr);
-    if (data)
-    {
-        if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(status))
+    if (data) {
+        if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(status)) {
             SendErrorStatusChange(true, status, request, data->mFile);
+        }
 
         // This will automatically close the output stream
         mOutputMap.Remove(keyPtr);
-    }
-    else
-    {
+    } else {
         // if we didn't find the data in mOutputMap, try mUploadList
         UploadData *upData = mUploadList.Get(keyPtr);
-        if (upData)
-        {
+        if (upData) {
             mUploadList.Remove(keyPtr);
         }
     }
 
-    // ensure we call SaveDocuments if we:
-    // 1) aren't canceling
-    // 2) we haven't triggered the save (which we only want to trigger once)
-    // 3) we aren't serializing (which will call it inside SerializeNextFile)
-    if (mOutputMap.Count() == 0 && !mCancel && !mStartSaving && !mSerializingOutput)
-    {
-        nsresult rv = SaveDocuments();
-        NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-    }
-
-    bool completed = false;
-    if (mOutputMap.Count() == 0 && mUploadList.Count() == 0 && !mCancel)
-    {
-        // if no documents left in mDocList, --> done
-        // if we have no files left to serialize and no error result, --> done
-        if (mDocList.Length() == 0
-            || (!SerializeNextFile() && NS_SUCCEEDED(mPersistResult)))
-        {
-            completed = true;
-        }
-    }
-
-    if (completed)
-    {
-        // we're all done, do our cleanup
-        EndDownload(status);
-    }
-
-    if (mProgressListener)
-    {
+    // Do more work.
+    SerializeNextFile();
+
+    if (mProgressListener) {
         uint32_t stateFlags = nsIWebProgressListener::STATE_STOP |
                               nsIWebProgressListener::STATE_IS_REQUEST;
-        if (completed)
-        {
+        if (!mSavingDocument) {
             stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK;
         }
         mProgressListener->OnStateChange(nullptr, request, stateFlags, status);
     }
-    if (completed)
-    {
-        mProgressListener = nullptr;
-        mProgressListener2 = nullptr;
-        mEventSink = nullptr;
-    }
 
     return NS_OK;
 }
 
 //*****************************************************************************
 // nsWebBrowserPersist::nsIStreamListener
 //*****************************************************************************
 
@@ -1123,17 +1209,18 @@ nsresult nsWebBrowserPersist::GetLocalFi
     if (NS_FAILED(rv)) {
         return rv;
     }
 
     file.forget(aLocalFile);
     return NS_OK;
 }
 
-nsresult nsWebBrowserPersist::AppendPathToURI(nsIURI *aURI, const nsAString & aPath) const
+/* static */ nsresult
+nsWebBrowserPersist::AppendPathToURI(nsIURI *aURI, const nsAString & aPath)
 {
     NS_ENSURE_ARG_POINTER(aURI);
 
     nsAutoCString newPath;
     nsresult rv = aURI->GetPath(newPath);
     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
     // Append a forward slash if necessary
@@ -1393,147 +1480,80 @@ nsWebBrowserPersist::GetExtensionForCont
         NS_ENSURE_TRUE(*aExt, NS_ERROR_OUT_OF_MEMORY);
         return NS_OK;
     }
 
     return NS_ERROR_FAILURE;
 }
 
 nsresult
-nsWebBrowserPersist::GetDocumentExtension(nsIDOMDocument *aDocument, char16_t **aExt)
-{
-    NS_ENSURE_ARG_POINTER(aDocument);
-    NS_ENSURE_ARG_POINTER(aExt);
-
-    nsXPIDLString contentType;
-    nsresult rv = GetDocEncoderContentType(aDocument, nullptr, getter_Copies(contentType));
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-    return GetExtensionForContentType(contentType.get(), aExt);
-}
-
-nsresult
-nsWebBrowserPersist::GetDocEncoderContentType(nsIDOMDocument *aDocument, const char16_t *aContentType, char16_t **aRealContentType)
+nsWebBrowserPersist::SaveDocumentDeferred(mozilla::UniquePtr<WalkData>&& aData)
 {
-    NS_ENSURE_ARG_POINTER(aDocument);
-    NS_ENSURE_ARG_POINTER(aRealContentType);
-
-    *aRealContentType = nullptr;
-
-    nsAutoString defaultContentType(NS_LITERAL_STRING("text/html"));
-
-    // Get the desired content type for the document, either by using the one
-    // supplied or from the document itself.
-
-    nsAutoString contentType;
-    if (aContentType)
-    {
-        contentType.Assign(aContentType);
-    }
-    else
-    {
-        // Get the content type from the document
-        nsAutoString type;
-        if (NS_SUCCEEDED(aDocument->GetContentType(type)) && !type.IsEmpty())
-            contentType.Assign(type);
+    nsresult rv =
+        SaveDocumentInternal(aData->mDocument, aData->mFile, aData->mDataPath);
+    if (NS_FAILED(rv)) {
+        SendErrorStatusChange(true, rv, nullptr, mURI);
+        EndDownload(rv);
     }
-
-    // Check that an encoder actually exists for the desired output type. The
-    // following content types will usually yield an encoder.
-    //
-    //   text/xml
-    //   application/xml
-    //   application/xhtml+xml
-    //   image/svg+xml
-    //   text/html
-    //   text/plain
-
-    if (!contentType.IsEmpty() &&
-        !contentType.Equals(defaultContentType, nsCaseInsensitiveStringComparator()))
-    {
-        // Check if there is an encoder for the desired content type
-        nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE);
-        AppendUTF16toUTF8(contentType, contractID);
-
-        nsCOMPtr<nsIComponentRegistrar> registrar;
-        NS_GetComponentRegistrar(getter_AddRefs(registrar));
-        if (registrar)
-        {
-            bool result;
-            nsresult rv = registrar->IsContractIDRegistered(contractID.get(), &result);
-            if (NS_SUCCEEDED(rv) && result)
-            {
-                *aRealContentType = ToNewUnicode(contentType);
-            }
-        }
-    }
-
-    // Use the default if no encoder exists for the desired one
-    if (!*aRealContentType)
-    {
-        *aRealContentType = ToNewUnicode(defaultContentType);
-    }
-
-    NS_ENSURE_TRUE(*aRealContentType, NS_ERROR_OUT_OF_MEMORY);
-
-    return NS_OK;
+    return rv;
 }
 
 nsresult nsWebBrowserPersist::SaveDocumentInternal(
-    nsIDOMDocument *aDocument, nsIURI *aFile, nsIURI *aDataPath)
+    nsIWebBrowserPersistDocument *aDocument, nsIURI *aFile, nsIURI *aDataPath)
 {
+    mURI = nullptr;
     NS_ENSURE_ARG_POINTER(aDocument);
     NS_ENSURE_ARG_POINTER(aFile);
 
+    nsresult rv = aDocument->SetPersistFlags(mPersistFlags);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = aDocument->GetIsPrivate(&mIsPrivate);
+    NS_ENSURE_SUCCESS(rv, rv);
+
     // See if we can get the local file representation of this URI
     nsCOMPtr<nsIFile> localFile;
-    nsresult rv = GetLocalFileFromURI(aFile, getter_AddRefs(localFile));
+    rv = GetLocalFileFromURI(aFile, getter_AddRefs(localFile));
 
     nsCOMPtr<nsIFile> localDataPath;
     if (NS_SUCCEEDED(rv) && aDataPath)
     {
         // See if we can get the local file representation of this URI
         rv = GetLocalFileFromURI(aDataPath, getter_AddRefs(localDataPath));
         NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
     }
 
-    nsCOMPtr<nsIDOMNode> docAsNode = do_QueryInterface(aDocument);
-
     // Persist the main document
-    nsCOMPtr<nsIDocument> doc(do_QueryInterface(aDocument));
-    if (!doc) {
-        return NS_ERROR_UNEXPECTED;
-    }
-    mURI = doc->GetDocumentURI();
-
-    nsCOMPtr<nsIURI> oldBaseURI = mCurrentBaseURI;
-    nsAutoCString oldCharset(mCurrentCharset);
-
-    // Store the base URI and the charset
-    mCurrentBaseURI = doc->GetBaseURI();
-    mCurrentCharset = doc->GetDocumentCharacterSet();
+    rv = aDocument->GetCharacterSet(mCurrentCharset);
+    NS_ENSURE_SUCCESS(rv, rv);
+    nsAutoCString uriSpec;
+    rv = aDocument->GetDocumentURI(uriSpec);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = NS_NewURI(getter_AddRefs(mURI), uriSpec, mCurrentCharset.get());
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = aDocument->GetBaseURI(uriSpec);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = NS_NewURI(getter_AddRefs(mCurrentBaseURI), uriSpec,
+                   mCurrentCharset.get());
+    NS_ENSURE_SUCCESS(rv, rv);
 
     // Does the caller want to fixup the referenced URIs and save those too?
     if (aDataPath)
     {
         // Basic steps are these.
         //
         // 1. Iterate through the document (and subdocuments) building a list
         //    of unique URIs.
         // 2. For each URI create an OutputData entry and open a channel to save
         //    it. As each URI is saved, discover the mime type and fix up the
         //    local filename with the correct extension.
         // 3. Store the document in a list and wait for URI persistence to finish
         // 4. After URI persistence completes save the list of documents,
         //    fixing it up as it goes out to file.
 
-        nsCOMPtr<nsIURI> oldDataPath = mCurrentDataPath;
-        bool oldDataPathIsRelative = mCurrentDataPathIsRelative;
-        nsCString oldCurrentRelativePathToData = mCurrentRelativePathToData;
-        uint32_t oldThingsToPersist = mCurrentThingsToPersist;
-
         mCurrentDataPathIsRelative = false;
         mCurrentDataPath = aDataPath;
         mCurrentRelativePathToData = "";
         mCurrentThingsToPersist = 0;
 
         // Determine if the specified data path is relative to the
         // specified file, (e.g. c:\docs\htmldata is relative to
         // c:\docs\myfile.htm, but not to d:\foo\data.
@@ -1599,173 +1619,125 @@ nsresult nsWebBrowserPersist::SaveDocume
         docData->mDocument = aDocument;
         docData->mFile = aFile;
         docData->mRelativePathToData = mCurrentRelativePathToData;
         docData->mDataPath = mCurrentDataPath;
         docData->mDataPathIsRelative = mCurrentDataPathIsRelative;
         mDocList.AppendElement(docData);
 
         // Walk the DOM gathering a list of externally referenced URIs in the uri map
-        nsCOMPtr<nsIDOMTreeWalker> walker;
-        rv = aDocument->CreateTreeWalker(docAsNode,
-            nsIDOMNodeFilter::SHOW_ELEMENT |
-                nsIDOMNodeFilter::SHOW_DOCUMENT |
-                nsIDOMNodeFilter::SHOW_PROCESSING_INSTRUCTION,
-            nullptr, 1, getter_AddRefs(walker));
-        NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-
-        nsCOMPtr<nsIDOMNode> currentNode;
-        walker->GetCurrentNode(getter_AddRefs(currentNode));
-        while (currentNode)
-        {
-            OnWalkDOMNode(currentNode);
-            walker->NextNode(getter_AddRefs(currentNode));
-        }
-
-        // If there are things to persist, create a directory to hold them
-        if (mCurrentThingsToPersist > 0)
-        {
-            if (localDataPath)
-            {
-                bool exists = false;
-                bool haveDir = false;
-
-                localDataPath->Exists(&exists);
-                if (exists)
-                {
-                    localDataPath->IsDirectory(&haveDir);
-                }
-                if (!haveDir)
-                {
-                    rv = localDataPath->Create(nsIFile::DIRECTORY_TYPE, 0755);
-                    if (NS_SUCCEEDED(rv))
-                        haveDir = true;
-                    else
-                        SendErrorStatusChange(false, rv, nullptr, aFile);
-                }
-                if (!haveDir)
-                {
-                    EndDownload(NS_ERROR_FAILURE);
-                    mCurrentBaseURI = oldBaseURI;
-                    mCurrentCharset = oldCharset;
-                    return NS_ERROR_FAILURE;
-                }
-                if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE)
-                {
-                    // Add to list of things to delete later if all goes wrong
-                    CleanupData *cleanupData = new CleanupData;
-                    NS_ENSURE_TRUE(cleanupData, NS_ERROR_OUT_OF_MEMORY);
-                    cleanupData->mFile = localDataPath;
-                    cleanupData->mIsDirectory = true;
-                    mCleanupList.AppendElement(cleanupData);
-                }
-            }
-        }
-
-        mCurrentThingsToPersist = oldThingsToPersist;
-        mCurrentDataPath = oldDataPath;
-        mCurrentDataPathIsRelative = oldDataPathIsRelative;
-        mCurrentRelativePathToData = oldCurrentRelativePathToData;
+        nsCOMPtr<nsIWebBrowserPersistResourceVisitor> visit =
+            new OnWalk(this, aFile, localDataPath);
+        return aDocument->ReadResources(visit);
     }
     else
     {
-        // Get the content type to save with
-        nsXPIDLString realContentType;
-        GetDocEncoderContentType(aDocument,
-            !mContentType.IsEmpty() ? mContentType.get() : nullptr,
-            getter_Copies(realContentType));
-
-        nsAutoCString contentType; contentType.AssignWithConversion(realContentType);
-        nsAutoCString charType; // Empty
-
-        // Save the document
-        rv = SaveDocumentWithFixup(
-            aDocument,
-            aFile,
-            mReplaceExisting,
-            contentType,
-            charType,
-            mEncodingFlags);
-        NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+        DocData *docData = new DocData;
+        docData->mBaseURI = mCurrentBaseURI;
+        docData->mCharset = mCurrentCharset;
+        docData->mDocument = aDocument;
+        docData->mFile = aFile;
+        docData->mRelativePathToData = nullptr;
+        docData->mDataPath = nullptr;
+        docData->mDataPathIsRelative = false;
+        mDocList.AppendElement(docData);
+
+        // Not walking DOMs, so go directly to serialization.
+        SerializeNextFile();
+        return NS_OK;
     }
-
-    mCurrentBaseURI = oldBaseURI;
-    mCurrentCharset = oldCharset;
-
+}
+
+NS_IMETHODIMP
+nsWebBrowserPersist::OnWalk::VisitResource(nsIWebBrowserPersistDocument* aDoc,
+                                           const nsACString& aURI)
+{
+    return mParent->StoreURI(nsAutoCString(aURI).get());
+}
+
+NS_IMETHODIMP
+nsWebBrowserPersist::OnWalk::VisitDocument(nsIWebBrowserPersistDocument* aDoc,
+                                             nsIWebBrowserPersistDocument* aSubDoc)
+{
+    URIData* data = nullptr;
+    nsAutoCString uriSpec;
+    nsresult rv = aSubDoc->GetDocumentURI(uriSpec);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = mParent->StoreURI(uriSpec.get(), false, &data);
+    NS_ENSURE_SUCCESS(rv, rv);
+    data->mIsSubFrame = true;
+    return mParent->SaveSubframeContent(aSubDoc, uriSpec, data);
+}
+
+
+NS_IMETHODIMP
+nsWebBrowserPersist::OnWalk::EndVisit(nsIWebBrowserPersistDocument* aDoc,
+                                      nsresult aStatus)
+{
+    if (NS_FAILED(aStatus)) {
+        mParent->SendErrorStatusChange(true, aStatus, nullptr, mFile);
+        mParent->EndDownload(aStatus);
+        return aStatus;
+    }
+    mParent->FinishSaveDocumentInternal(mFile, mDataPath);
     return NS_OK;
 }
 
-nsresult nsWebBrowserPersist::SaveDocuments()
+void
+nsWebBrowserPersist::FinishSaveDocumentInternal(nsIURI* aFile,
+                                                nsIFile* aDataPath)
 {
-    nsresult rv = NS_OK;
-
-    mStartSaving = true;
-
-    // Iterate through all queued documents, saving them to file and fixing
-    // them up on the way.
-
-    uint32_t i;
-    for (i = 0; i < mDocList.Length(); i++)
-    {
-        DocData *docData = mDocList.ElementAt(i);
-        if (!docData)
-        {
-            rv = NS_ERROR_FAILURE;
-            break;
-        }
-
-        mCurrentBaseURI = docData->mBaseURI;
-        mCurrentCharset = docData->mCharset;
-
-        // Save the document, fixing it up with the new URIs as we do
-
-        // Get the content type
-        nsXPIDLString realContentType;
-        GetDocEncoderContentType(docData->mDocument,
-            !mContentType.IsEmpty() ? mContentType.get() : nullptr,
-            getter_Copies(realContentType));
-
-        nsAutoCString contentType; contentType.AssignWithConversion(realContentType.get());
-        nsAutoCString charType; // Empty
-
-        // Save the document, fixing up the links as it goes out
-        rv = SaveDocumentWithFixup(
-            docData->mDocument,
-            docData->mFile,
-            mReplaceExisting,
-            contentType,
-            charType,
-            mEncodingFlags);
-
-        if (NS_FAILED(rv))
-            break;
-
-        // if we're serializing, bail after first iteration of loop
-        if (mSerializingOutput)
-            break;
-    }
-
-    // delete, cleanup regardless of errors (bug 132417)
-    for (i = 0; i < mDocList.Length(); i++)
-    {
-        DocData *docData = mDocList.ElementAt(i);
-        delete docData;
-        if (mSerializingOutput)
-        {
-            mDocList.RemoveElementAt(i);
-            break;
+    // If there are things to persist, create a directory to hold them
+    if (mCurrentThingsToPersist > 0) {
+        if (aDataPath) {
+            bool exists = false;
+            bool haveDir = false;
+
+            aDataPath->Exists(&exists);
+            if (exists) {
+                aDataPath->IsDirectory(&haveDir);
+            }
+            if (!haveDir) {
+                nsresult rv =
+                    aDataPath->Create(nsIFile::DIRECTORY_TYPE, 0755);
+                if (NS_SUCCEEDED(rv)) {
+                    haveDir = true;
+                } else {
+                    SendErrorStatusChange(false, rv, nullptr, aFile);
+                }
+            }
+            if (!haveDir) {
+                EndDownload(NS_ERROR_FAILURE);
+                return;
+            }
+            if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE) {
+                // Add to list of things to delete later if all goes wrong
+                CleanupData *cleanupData = new CleanupData;
+                cleanupData->mFile = aDataPath;
+                cleanupData->mIsDirectory = true;
+                mCleanupList.AppendElement(cleanupData);
+            }
         }
     }
 
-    if (!mSerializingOutput)
-    {
-        mDocList.Clear();
+    if (mWalkStack.Length() > 0) {
+        mozilla::UniquePtr<WalkData> toWalk;
+        mWalkStack.LastElement().swap(toWalk);
+        mWalkStack.TruncateLength(mWalkStack.Length() - 1);
+        // Bounce this off the event loop to avoid stack overflow.
+        typedef StoreCopyPassByRRef<decltype(toWalk)> WalkStorage;
+        auto saveMethod = &nsWebBrowserPersist::SaveDocumentDeferred;
+        nsCOMPtr<nsIRunnable> saveLater =
+            NS_NewRunnableMethodWithArg<WalkStorage>(this, saveMethod,
+                                                     mozilla::Move(toWalk));
+        NS_DispatchToCurrentThread(saveLater);
+    } else {
+        // Done walking DOMs; on to the serialization phase.
+        SerializeNextFile();
     }
-
-    return rv;
 }
 
 void nsWebBrowserPersist::Cleanup()
 {
     mURIMap.Clear();
     mOutputMap.EnumerateRead(EnumCleanupOutputMap, this);
     mOutputMap.Clear();
     mUploadList.EnumerateRead(EnumCleanupUploadList, this);
@@ -2289,33 +2261,50 @@ nsWebBrowserPersist::MakeOutputStreamFro
     nsresult rv = NS_NewStorageStream(segsize, maxsize, getter_AddRefs(storStream));
     NS_ENSURE_SUCCESS(rv, rv);
 
     NS_ENSURE_SUCCESS(CallQueryInterface(storStream, aOutputStream), NS_ERROR_FAILURE);
     return NS_OK;
 }
 
 void
+nsWebBrowserPersist::FinishDownload()
+{
+    EndDownload(NS_OK);
+}
+
+void
 nsWebBrowserPersist::EndDownload(nsresult aResult)
 {
+    // State stop notification
+    if (mProgressListener) {
+        mProgressListener->OnStateChange(nullptr, nullptr,
+            nsIWebProgressListener::STATE_STOP
+            | nsIWebProgressListener::STATE_IS_NETWORK, mPersistResult);
+    }
+
     // Store the error code in the result if it is an error
     if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(aResult))
     {
         mPersistResult = aResult;
     }
 
     // Do file cleanup if required
     if (NS_FAILED(aResult) && (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE))
     {
         CleanupLocalFiles();
     }
 
     // Cleanup the channels
     mCompleted = true;
     Cleanup();
+
+    mProgressListener = nullptr;
+    mProgressListener2 = nullptr;
+    mEventSink = nullptr;
 }
 
 struct MOZ_STACK_CLASS FixRedirectData
 {
     nsCOMPtr<nsIChannel> mNewChannel;
     nsCOMPtr<nsIURI> mOriginalURI;
     nsCOMPtr<nsISupports> mMatchingKey;
 };
@@ -2510,759 +2499,28 @@ nsWebBrowserPersist::EnumCleanupUploadLi
     nsCOMPtr<nsIChannel> channel = do_QueryInterface(aKey);
     if (channel)
     {
         channel->Cancel(NS_BINDING_ABORTED);
     }
     return PL_DHASH_NEXT;
 }
 
-static void
-AppendXMLAttr(const nsAString& key, const nsAString& aValue, nsAString& aBuffer)
-{
-    if (!aBuffer.IsEmpty()) {
-        aBuffer.Append(' ');
-    }
-    aBuffer.Append(key);
-    aBuffer.AppendLiteral("=\"");
-    for (size_t i = 0; i < aValue.Length(); ++i) {
-        switch (aValue[i]) {
-            case '&':
-                aBuffer.AppendLiteral("&amp;");
-                break;
-            case '<':
-                aBuffer.AppendLiteral("&lt;");
-                break;
-            case '>':
-                aBuffer.AppendLiteral("&gt;");
-                break;
-            case '"':
-                aBuffer.AppendLiteral("&quot;");
-                break;
-            default:
-                aBuffer.Append(aValue[i]);
-                break;
-        }
-    }
-    aBuffer.Append('"');
-}
-
-nsresult nsWebBrowserPersist::FixupXMLStyleSheetLink(nsIDOMProcessingInstruction *aPI, const nsAString &aHref)
-{
-    NS_ENSURE_ARG_POINTER(aPI);
-    nsresult rv = NS_OK;
-
-    nsAutoString data;
-    rv = aPI->GetData(data);
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-
-    nsAutoString href;
-    nsContentUtils::GetPseudoAttributeValue(data,
-                                            nsGkAtoms::href,
-                                            href);
-
-    // Construct and set a new data value for the xml-stylesheet
-    if (!aHref.IsEmpty() && !href.IsEmpty())
-    {
-        nsAutoString alternate;
-        nsAutoString charset;
-        nsAutoString title;
-        nsAutoString type;
-        nsAutoString media;
-
-        nsContentUtils::GetPseudoAttributeValue(data,
-                                                nsGkAtoms::alternate,
-                                                alternate);
-        nsContentUtils::GetPseudoAttributeValue(data,
-                                                nsGkAtoms::charset,
-                                                charset);
-        nsContentUtils::GetPseudoAttributeValue(data,
-                                                nsGkAtoms::title,
-                                                title);
-        nsContentUtils::GetPseudoAttributeValue(data,
-                                                nsGkAtoms::type,
-                                                type);
-        nsContentUtils::GetPseudoAttributeValue(data,
-                                                nsGkAtoms::media,
-                                                media);
-
-        nsAutoString newData;
-        AppendXMLAttr(NS_LITERAL_STRING("href"), aHref, newData);
-        if (!title.IsEmpty())
-        {
-            AppendXMLAttr(NS_LITERAL_STRING("title"), title, newData);
-        }
-        if (!media.IsEmpty())
-        {
-            AppendXMLAttr(NS_LITERAL_STRING("media"), media, newData);
-        }
-        if (!type.IsEmpty())
-        {
-            AppendXMLAttr(NS_LITERAL_STRING("type"), type, newData);
-        }
-        if (!charset.IsEmpty())
-        {
-            AppendXMLAttr(NS_LITERAL_STRING("charset"), charset, newData);
-        }
-        if (!alternate.IsEmpty())
-        {
-            AppendXMLAttr(NS_LITERAL_STRING("alternate"), alternate, newData);
-        }
-        aPI->SetData(newData);
-    }
-
-    return rv;
-}
-
-nsresult nsWebBrowserPersist::GetXMLStyleSheetLink(nsIDOMProcessingInstruction *aPI, nsAString &aHref)
-{
-    NS_ENSURE_ARG_POINTER(aPI);
-
-    nsresult rv = NS_OK;
-    nsAutoString data;
-    rv = aPI->GetData(data);
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-
-    nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::href, aHref);
-
-    return NS_OK;
-}
-
-nsresult nsWebBrowserPersist::OnWalkDOMNode(nsIDOMNode *aNode)
-{
-    // Fixup xml-stylesheet processing instructions
-    nsCOMPtr<nsIDOMProcessingInstruction> nodeAsPI = do_QueryInterface(aNode);
-    if (nodeAsPI)
-    {
-        nsAutoString target;
-        nodeAsPI->GetTarget(target);
-        if (target.EqualsLiteral("xml-stylesheet"))
-        {
-            nsAutoString href;
-            GetXMLStyleSheetLink(nodeAsPI, href);
-            if (!href.IsEmpty())
-            {
-                StoreURI(NS_ConvertUTF16toUTF8(href).get());
-            }
-        }
-        return NS_OK;
-    }
-
-    nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
-    if (!content)
-    {
-        return NS_OK;
-    }
-
-    // Test the node to see if it's an image, frame, iframe, css, js
-    nsCOMPtr<nsIDOMHTMLImageElement> nodeAsImage = do_QueryInterface(aNode);
-    if (nodeAsImage)
-    {
-        StoreURIAttribute(aNode, "src");
-        return NS_OK;
-    }
-
-    if (content->IsSVGElement(nsGkAtoms::img))
-    {
-        StoreURIAttributeNS(aNode, "http://www.w3.org/1999/xlink", "href");
-        return NS_OK;
-    }
-
-    nsCOMPtr<nsIDOMHTMLMediaElement> nodeAsMedia = do_QueryInterface(aNode);
-    if (nodeAsMedia)
-    {
-        StoreURIAttribute(aNode, "src");
-        return NS_OK;
-    }
-    nsCOMPtr<nsIDOMHTMLSourceElement> nodeAsSource = do_QueryInterface(aNode);
-    if (nodeAsSource)
-    {
-        StoreURIAttribute(aNode, "src");
-        return NS_OK;
-    }
-
-    if (content->IsHTMLElement(nsGkAtoms::body)) {
-        StoreURIAttribute(aNode, "background");
-        return NS_OK;
-    }
-
-    if (content->IsHTMLElement(nsGkAtoms::table)) {
-        StoreURIAttribute(aNode, "background");
-        return NS_OK;
-    }
-
-    if (content->IsHTMLElement(nsGkAtoms::tr)) {
-        StoreURIAttribute(aNode, "background");
-        return NS_OK;
-    }
-
-    if (content->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) {
-        StoreURIAttribute(aNode, "background");
-        return NS_OK;
-    }
-
-    nsCOMPtr<nsIDOMHTMLScriptElement> nodeAsScript = do_QueryInterface(aNode);
-    if (nodeAsScript)
-    {
-        StoreURIAttribute(aNode, "src");
-        return NS_OK;
-    }
-
-    if (content->IsSVGElement(nsGkAtoms::script))
-    {
-        StoreURIAttributeNS(aNode, "http://www.w3.org/1999/xlink", "href");
-        return NS_OK;
-    }
-
-    nsCOMPtr<nsIDOMHTMLEmbedElement> nodeAsEmbed = do_QueryInterface(aNode);
-    if (nodeAsEmbed)
-    {
-        StoreURIAttribute(aNode, "src");
-        return NS_OK;
-    }
-
-    nsCOMPtr<nsIDOMHTMLObjectElement> nodeAsObject = do_QueryInterface(aNode);
-    if (nodeAsObject)
-    {
-        StoreURIAttribute(aNode, "data");
-        return NS_OK;
-    }
-
-    nsCOMPtr<nsIDOMHTMLAppletElement> nodeAsApplet = do_QueryInterface(aNode);
-    if (nodeAsApplet)
-    {
-        // For an applet, relative URIs are resolved relative to the
-        // codebase (which is resolved relative to the base URI).
-        nsCOMPtr<nsIURI> oldBase = mCurrentBaseURI;
-        nsAutoString codebase;
-        nodeAsApplet->GetCodeBase(codebase);
-        if (!codebase.IsEmpty()) {
-            nsCOMPtr<nsIURI> baseURI;
-            NS_NewURI(getter_AddRefs(baseURI), codebase,
-                      mCurrentCharset.get(), mCurrentBaseURI);
-            if (baseURI) {
-                mCurrentBaseURI = baseURI;
-            }
-        }
-
-        URIData *archiveURIData = nullptr;
-        StoreURIAttribute(aNode, "archive", true, &archiveURIData);
-        // We only store 'code' locally if there is no 'archive',
-        // otherwise we assume the archive file(s) contains it (bug 430283).
-        if (!archiveURIData)
-            StoreURIAttribute(aNode, "code");
-
-        // restore the base URI we really want to have
-        mCurrentBaseURI = oldBase;
-        return NS_OK;
-    }
-
-    nsCOMPtr<nsIDOMHTMLLinkElement> nodeAsLink = do_QueryInterface(aNode);
-    if (nodeAsLink)
-    {
-        // Test if the link has a rel value indicating it to be a stylesheet
-        nsAutoString linkRel;
-        if (NS_SUCCEEDED(nodeAsLink->GetRel(linkRel)) && !linkRel.IsEmpty())
-        {
-            nsReadingIterator<char16_t> start;
-            nsReadingIterator<char16_t> end;
-            nsReadingIterator<char16_t> current;
-
-            linkRel.BeginReading(start);
-            linkRel.EndReading(end);
-
-            // Walk through space delimited string looking for "stylesheet"
-            for (current = start; current != end; ++current)
-            {
-                // Ignore whitespace
-                if (nsCRT::IsAsciiSpace(*current))
-                    continue;
-
-                // Grab the next space delimited word
-                nsReadingIterator<char16_t> startWord = current;
-                do {
-                    ++current;
-                } while (current != end && !nsCRT::IsAsciiSpace(*current));
-
-                // Store the link for fix up if it says "stylesheet"
-                if (Substring(startWord, current)
-                        .LowerCaseEqualsLiteral("stylesheet"))
-                {
-                    StoreURIAttribute(aNode, "href");
-                    return NS_OK;
-                }
-                if (current == end)
-                    break;
-            }
-        }
-        return NS_OK;
-    }
-
-    nsCOMPtr<nsIDOMHTMLFrameElement> nodeAsFrame = do_QueryInterface(aNode);
-    if (nodeAsFrame)
-    {
-        URIData *data = nullptr;
-        StoreURIAttribute(aNode, "src", false, &data);
-        if (data)
-        {
-            data->mIsSubFrame = true;
-            // Save the frame content
-            nsCOMPtr<nsIDOMDocument> content;
-            nodeAsFrame->GetContentDocument(getter_AddRefs(content));
-            if (content)
-            {
-                SaveSubframeContent(content, data);
-            }
-        }
-        return NS_OK;
-    }
-
-    nsCOMPtr<nsIDOMHTMLIFrameElement> nodeAsIFrame = do_QueryInterface(aNode);
-    if (nodeAsIFrame && !(mPersistFlags & PERSIST_FLAGS_IGNORE_IFRAMES))
-    {
-        URIData *data = nullptr;
-        StoreURIAttribute(aNode, "src", false, &data);
-        if (data)
-        {
-            data->mIsSubFrame = true;
-            // Save the frame content
-            nsCOMPtr<nsIDOMDocument> content;
-            nodeAsIFrame->GetContentDocument(getter_AddRefs(content));
-            if (content)
-            {
-                SaveSubframeContent(content, data);
-            }
-        }
-        return NS_OK;
-    }
-
-    nsCOMPtr<nsIDOMHTMLInputElement> nodeAsInput = do_QueryInterface(aNode);
-    if (nodeAsInput)
-    {
-        StoreURIAttribute(aNode, "src");
-        return NS_OK;
-    }
-
-    return NS_OK;
-}
-
-nsresult
-nsWebBrowserPersist::GetNodeToFixup(nsIDOMNode *aNodeIn, nsIDOMNode **aNodeOut)
-{
-    if (!(mPersistFlags & PERSIST_FLAGS_FIXUP_ORIGINAL_DOM))
-    {
-        nsresult rv = aNodeIn->CloneNode(false, 1, aNodeOut);
-        NS_ENSURE_SUCCESS(rv, rv);
-    }
-    else
-    {
-        NS_ADDREF(*aNodeOut = aNodeIn);
-    }
-    nsCOMPtr<nsIDOMHTMLElement> element(do_QueryInterface(*aNodeOut));
-    if (element) {
-        // Make sure this is not XHTML
-        nsAutoString namespaceURI;
-        element->GetNamespaceURI(namespaceURI);
-        if (namespaceURI.IsEmpty()) {
-            // This is a tag-soup node.  It may have a _base_href attribute
-            // stuck on it by the parser, but since we're fixing up all URIs
-            // relative to the overall document base that will screw us up.
-            // Just remove the _base_href.
-            element->RemoveAttribute(NS_LITERAL_STRING("_base_href"));
-        }
-    }
-    return NS_OK;
-}
-
-nsresult
-nsWebBrowserPersist::CloneNodeWithFixedUpAttributes(
-    nsIDOMNode *aNodeIn, bool *aSerializeCloneKids, nsIDOMNode **aNodeOut)
+/* static */ PLDHashOperator
+nsWebBrowserPersist::EnumCopyURIsToFlatMap(const nsACString &aKey,
+                                          URIData *aData,
+                                          void* aClosure)
 {
-    nsresult rv;
-    *aNodeOut = nullptr;
-    *aSerializeCloneKids = false;
-
-    // Fixup xml-stylesheet processing instructions
-    nsCOMPtr<nsIDOMProcessingInstruction> nodeAsPI = do_QueryInterface(aNodeIn);
-    if (nodeAsPI)
-    {
-        nsAutoString target;
-        nodeAsPI->GetTarget(target);
-        if (target.EqualsLiteral("xml-stylesheet"))
-        {
-            rv = GetNodeToFixup(aNodeIn, aNodeOut);
-            if (NS_SUCCEEDED(rv) && *aNodeOut)
-            {
-                nsCOMPtr<nsIDOMProcessingInstruction> outNode = do_QueryInterface(*aNodeOut);
-                nsAutoString href;
-                GetXMLStyleSheetLink(nodeAsPI, href);
-                if (!href.IsEmpty())
-                {
-                    FixupURI(href);
-                    FixupXMLStyleSheetLink(outNode, href);
-                }
-            }
-        }
-    }
-
-    // BASE elements are replaced by a comment so relative links are not hosed.
-
-    if (!(mPersistFlags & PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS))
-    {
-        nsCOMPtr<nsIDOMHTMLBaseElement> nodeAsBase = do_QueryInterface(aNodeIn);
-        if (nodeAsBase)
-        {
-            nsCOMPtr<nsIDOMDocument> ownerDocument;
-            HTMLSharedElement* base = static_cast<HTMLSharedElement*>(nodeAsBase.get());
-            base->GetOwnerDocument(getter_AddRefs(ownerDocument));
-            if (ownerDocument)
-            {
-                nsAutoString href;
-                base->GetHref(href); // Doesn't matter if this fails
-                nsCOMPtr<nsIDOMComment> comment;
-                nsAutoString commentText; commentText.AssignLiteral(" base ");
-                if (!href.IsEmpty())
-                {
-                    commentText += NS_LITERAL_STRING("href=\"") + href + NS_LITERAL_STRING("\" ");
-                }
-                rv = ownerDocument->CreateComment(commentText, getter_AddRefs(comment));
-                if (comment)
-                {
-                    return CallQueryInterface(comment, aNodeOut);
-                }
-            }
-        }
-    }
-
-    nsCOMPtr<nsIContent> content = do_QueryInterface(aNodeIn);
-    if (!content)
-    {
-        return NS_OK;
-    }
-
-    // Fix up href and file links in the elements
-
-    nsCOMPtr<nsIDOMHTMLAnchorElement> nodeAsAnchor = do_QueryInterface(aNodeIn);
-    if (nodeAsAnchor)
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            FixupAnchor(*aNodeOut);
-        }
-        return rv;
-    }
-
-    nsCOMPtr<nsIDOMHTMLAreaElement> nodeAsArea = do_QueryInterface(aNodeIn);
-    if (nodeAsArea)
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            FixupAnchor(*aNodeOut);
-        }
-        return rv;
-    }
-
-    if (content->IsHTMLElement(nsGkAtoms::body)) {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            FixupNodeAttribute(*aNodeOut, "background");
-        }
-        return rv;
-    }
-
-    if (content->IsHTMLElement(nsGkAtoms::table)) {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            FixupNodeAttribute(*aNodeOut, "background");
-        }
-        return rv;
-    }
-
-    if (content->IsHTMLElement(nsGkAtoms::tr)) {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            FixupNodeAttribute(*aNodeOut, "background");
-        }
-        return rv;
-    }
-
-    if (content->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            FixupNodeAttribute(*aNodeOut, "background");
-        }
-        return rv;
-    }
-
-    nsCOMPtr<nsIDOMHTMLImageElement> nodeAsImage = do_QueryInterface(aNodeIn);
-    if (nodeAsImage)
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            // Disable image loads
-            nsCOMPtr<nsIImageLoadingContent> imgCon =
-                do_QueryInterface(*aNodeOut);
-            if (imgCon)
-                imgCon->SetLoadingEnabled(false);
-
-            FixupAnchor(*aNodeOut);
-            FixupNodeAttribute(*aNodeOut, "src");
-        }
-        return rv;
-    }
-
-    nsCOMPtr<nsIDOMHTMLMediaElement> nodeAsMedia = do_QueryInterface(aNodeIn);
-    if (nodeAsMedia)
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            FixupNodeAttribute(*aNodeOut, "src");
-        }
-
-        return rv;
-    }
-
-    nsCOMPtr<nsIDOMHTMLSourceElement> nodeAsSource = do_QueryInterface(aNodeIn);
-    if (nodeAsSource)
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            FixupNodeAttribute(*aNodeOut, "src");
-        }
-
-        return rv;
-    }
-
-    if (content->IsSVGElement(nsGkAtoms::img))
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            // Disable image loads
-            nsCOMPtr<nsIImageLoadingContent> imgCon =
-                do_QueryInterface(*aNodeOut);
-            if (imgCon)
-                imgCon->SetLoadingEnabled(false);
-
-            // FixupAnchor(*aNodeOut);  // XXXjwatt: is this line needed?
-            FixupNodeAttributeNS(*aNodeOut, "http://www.w3.org/1999/xlink", "href");
-        }
-        return rv;
-    }
-
-    nsCOMPtr<nsIDOMHTMLScriptElement> nodeAsScript = do_QueryInterface(aNodeIn);
-    if (nodeAsScript)
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            FixupNodeAttribute(*aNodeOut, "src");
-        }
-        return rv;
+    FlatURIMap* theMap = static_cast<FlatURIMap*>(aClosure);
+    nsAutoCString mapTo;
+    nsresult rv = aData->GetLocalURI(mapTo);
+    if (NS_SUCCEEDED(rv) || !mapTo.IsVoid()) {
+        theMap->Add(aKey, mapTo);
     }
-
-    if (content->IsSVGElement(nsGkAtoms::script))
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            FixupNodeAttributeNS(*aNodeOut, "http://www.w3.org/1999/xlink", "href");
-        }
-        return rv;
-    }
-
-    nsCOMPtr<nsIDOMHTMLEmbedElement> nodeAsEmbed = do_QueryInterface(aNodeIn);
-    if (nodeAsEmbed)
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            FixupNodeAttribute(*aNodeOut, "src");
-        }
-        return rv;
-    }
-
-    nsCOMPtr<nsIDOMHTMLObjectElement> nodeAsObject = do_QueryInterface(aNodeIn);
-    if (nodeAsObject)
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            FixupNodeAttribute(*aNodeOut, "data");
-        }
-        return rv;
-    }
-
-    nsCOMPtr<nsIDOMHTMLAppletElement> nodeAsApplet = do_QueryInterface(aNodeIn);
-    if (nodeAsApplet)
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            nsCOMPtr<nsIDOMHTMLAppletElement> newApplet =
-                do_QueryInterface(*aNodeOut);
-            // For an applet, relative URIs are resolved relative to the
-            // codebase (which is resolved relative to the base URI).
-            nsCOMPtr<nsIURI> oldBase = mCurrentBaseURI;
-            nsAutoString codebase;
-            nodeAsApplet->GetCodeBase(codebase);
-            if (!codebase.IsEmpty()) {
-                nsCOMPtr<nsIURI> baseURI;
-                NS_NewURI(getter_AddRefs(baseURI), codebase,
-                          mCurrentCharset.get(), mCurrentBaseURI);
-                if (baseURI) {
-                    mCurrentBaseURI = baseURI;
-                }
-            }
-            // Unset the codebase too, since we'll correctly relativize the
-            // code and archive paths.
-            static_cast<HTMLSharedObjectElement*>(newApplet.get())->
-              RemoveAttribute(NS_LITERAL_STRING("codebase"));
-            FixupNodeAttribute(*aNodeOut, "code");
-            FixupNodeAttribute(*aNodeOut, "archive");
-            // restore the base URI we really want to have
-            mCurrentBaseURI = oldBase;
-        }
-        return rv;
-    }
-
-    nsCOMPtr<nsIDOMHTMLLinkElement> nodeAsLink = do_QueryInterface(aNodeIn);
-    if (nodeAsLink)
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            // First see if the link represents linked content
-            rv = FixupNodeAttribute(*aNodeOut, "href");
-            if (NS_FAILED(rv))
-            {
-                // Perhaps this link is actually an anchor to related content
-                FixupAnchor(*aNodeOut);
-            }
-            // TODO if "type" attribute == "text/css"
-            //        fixup stylesheet
-        }
-        return rv;
-    }
-
-    nsCOMPtr<nsIDOMHTMLFrameElement> nodeAsFrame = do_QueryInterface(aNodeIn);
-    if (nodeAsFrame)
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            FixupNodeAttribute(*aNodeOut, "src");
-        }
-        return rv;
-    }
-
-    nsCOMPtr<nsIDOMHTMLIFrameElement> nodeAsIFrame = do_QueryInterface(aNodeIn);
-    if (nodeAsIFrame)
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            FixupNodeAttribute(*aNodeOut, "src");
-        }
-        return rv;
-    }
-
-    nsCOMPtr<nsIDOMHTMLInputElement> nodeAsInput = do_QueryInterface(aNodeIn);
-    if (nodeAsInput)
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            // Disable image loads
-            nsCOMPtr<nsIImageLoadingContent> imgCon =
-                do_QueryInterface(*aNodeOut);
-            if (imgCon)
-                imgCon->SetLoadingEnabled(false);
-
-            FixupNodeAttribute(*aNodeOut, "src");
-
-            nsAutoString valueStr;
-            NS_NAMED_LITERAL_STRING(valueAttr, "value");
-            // Update element node attributes with user-entered form state
-            nsCOMPtr<nsIContent> content = do_QueryInterface(*aNodeOut);
-            nsRefPtr<HTMLInputElement> outElt =
-              HTMLInputElement::FromContentOrNull(content);
-            nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(*aNodeOut);
-            switch (formControl->GetType()) {
-                case NS_FORM_INPUT_EMAIL:
-                case NS_FORM_INPUT_SEARCH:
-                case NS_FORM_INPUT_TEXT:
-                case NS_FORM_INPUT_TEL:
-                case NS_FORM_INPUT_URL:
-                case NS_FORM_INPUT_NUMBER:
-                case NS_FORM_INPUT_RANGE:
-                case NS_FORM_INPUT_DATE:
-                case NS_FORM_INPUT_TIME:
-                case NS_FORM_INPUT_COLOR:
-                    nodeAsInput->GetValue(valueStr);
-                    // Avoid superfluous value="" serialization
-                    if (valueStr.IsEmpty())
-                      outElt->RemoveAttribute(valueAttr);
-                    else
-                      outElt->SetAttribute(valueAttr, valueStr);
-                    break;
-                case NS_FORM_INPUT_CHECKBOX:
-                case NS_FORM_INPUT_RADIO:
-                    bool checked;
-                    nodeAsInput->GetChecked(&checked);
-                    outElt->SetDefaultChecked(checked);
-                    break;
-                default:
-                    break;
-            }
-        }
-        return rv;
-    }
-
-    nsCOMPtr<nsIDOMHTMLTextAreaElement> nodeAsTextArea = do_QueryInterface(aNodeIn);
-    if (nodeAsTextArea)
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            // Tell the document encoder to serialize the text child we create below
-            *aSerializeCloneKids = true;
-
-            nsAutoString valueStr;
-            nodeAsTextArea->GetValue(valueStr);
-
-            (*aNodeOut)->SetTextContent(valueStr);
-        }
-        return rv;
-    }
-
-    nsCOMPtr<nsIDOMHTMLOptionElement> nodeAsOption = do_QueryInterface(aNodeIn);
-    if (nodeAsOption)
-    {
-        rv = GetNodeToFixup(aNodeIn, aNodeOut);
-        if (NS_SUCCEEDED(rv) && *aNodeOut)
-        {
-            nsCOMPtr<nsIDOMHTMLOptionElement> outElt = do_QueryInterface(*aNodeOut);
-            bool selected;
-            nodeAsOption->GetSelected(&selected);
-            outElt->SetDefaultSelected(selected);
-        }
-        return rv;
-    }
-
-    return NS_OK;
+    return PL_DHASH_NEXT;
 }
 
 nsresult
 nsWebBrowserPersist::StoreURI(
     const char *aURI, bool aNeedsPersisting, URIData **aData)
 {
     NS_ENSURE_ARG_POINTER(aURI);
 
@@ -3308,234 +2566,69 @@ nsWebBrowserPersist::StoreURI(
     {
         *aData = data;
     }
 
     return NS_OK;
 }
 
 nsresult
-nsWebBrowserPersist::StoreURIAttributeNS(
-    nsIDOMNode *aNode, const char *aNamespaceURI, const char *aAttribute,
-    bool aNeedsPersisting, URIData **aData)
+nsWebBrowserPersist::URIData::GetLocalURI(nsCString& aSpecOut)
 {
-    NS_ENSURE_ARG_POINTER(aNode);
-    NS_ENSURE_ARG_POINTER(aNamespaceURI);
-    NS_ENSURE_ARG_POINTER(aAttribute);
-
-    nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode);
-    MOZ_ASSERT(element);
-
-    // Find the named URI attribute on the (element) node and store
-    // a reference to the URI that maps onto a local file name
-
-    nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap;
-    nsresult rv = element->GetAttributes(getter_AddRefs(attrMap));
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-
-    NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI);
-    NS_ConvertASCIItoUTF16 attribute(aAttribute);
-    nsCOMPtr<nsIDOMAttr> attr;
-    rv = attrMap->GetNamedItemNS(namespaceURI, attribute, getter_AddRefs(attr));
-    if (attr)
-    {
-        nsAutoString oldValue;
-        attr->GetValue(oldValue);
-        if (!oldValue.IsEmpty())
-        {
-            NS_ConvertUTF16toUTF8 oldCValue(oldValue);
-            return StoreURI(oldCValue.get(), aNeedsPersisting, aData);
-        }
-    }
-
-    return NS_OK;
-}
-
-nsresult
-nsWebBrowserPersist::FixupURI(nsAString &aURI)
-{
-    // get the current location of the file (absolutized)
-    nsCOMPtr<nsIURI> uri;
-    nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI,
-                            mCurrentCharset.get(), mCurrentBaseURI);
-    NS_ENSURE_SUCCESS(rv, rv);
-    nsAutoCString spec;
-    rv = uri->GetSpec(spec);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    // Search for the URI in the map and replace it with the local file
-    if (!mURIMap.Contains(spec))
-    {
-        return NS_ERROR_FAILURE;
-    }
-    URIData *data = mURIMap.Get(spec);
-    if (!data->mNeedsFixup)
-    {
+    aSpecOut.SetIsVoid(true);
+    if (!mNeedsFixup) {
         return NS_OK;
     }
+    nsresult rv;
     nsCOMPtr<nsIURI> fileAsURI;
-    if (data->mFile)
-    {
-        rv = data->mFile->Clone(getter_AddRefs(fileAsURI));
+    if (mFile) {
+        rv = mFile->Clone(getter_AddRefs(fileAsURI));
+        NS_ENSURE_SUCCESS(rv, rv);
+    } else {
+        rv = mDataPath->Clone(getter_AddRefs(fileAsURI));
+        NS_ENSURE_SUCCESS(rv, rv);
+        rv = AppendPathToURI(fileAsURI, mFilename);
         NS_ENSURE_SUCCESS(rv, rv);
     }
-    else
-    {
-        rv = data->mDataPath->Clone(getter_AddRefs(fileAsURI));
-        NS_ENSURE_SUCCESS(rv, rv);
-        rv = AppendPathToURI(fileAsURI, data->mFilename);
-        NS_ENSURE_SUCCESS(rv, rv);
-    }
-    nsAutoString newValue;
 
     // remove username/password if present
     fileAsURI->SetUserPass(EmptyCString());
 
     // reset node attribute
     // Use relative or absolute links
-    if (data->mDataPathIsRelative)
-    {
+    if (mDataPathIsRelative) {
         nsCOMPtr<nsIURL> url(do_QueryInterface(fileAsURI));
-        if (!url)
-          return NS_ERROR_FAILURE;
+        if (!url) {
+            return NS_ERROR_FAILURE;
+        }
 
         nsAutoCString filename;
         url->GetFileName(filename);
 
-        nsAutoCString rawPathURL(data->mRelativePathToData);
+        nsAutoCString rawPathURL(mRelativePathToData);
         rawPathURL.Append(filename);
 
         nsAutoCString buf;
-        AppendUTF8toUTF16(NS_EscapeURL(rawPathURL, esc_FilePath, buf),
-                          newValue);
-    }
-    else
-    {
-        nsAutoCString fileurl;
-        fileAsURI->GetSpec(fileurl);
-        AppendUTF8toUTF16(fileurl, newValue);
-    }
-    if (data->mIsSubFrame)
-    {
-        newValue.Append(data->mSubFrameExt);
-    }
-
-    aURI = newValue;
-    return NS_OK;
-}
-
-nsresult
-nsWebBrowserPersist::FixupNodeAttributeNS(nsIDOMNode *aNode,
-                                          const char *aNamespaceURI,
-                                          const char *aAttribute)
-{
-    NS_ENSURE_ARG_POINTER(aNode);
-    NS_ENSURE_ARG_POINTER(aNamespaceURI);
-    NS_ENSURE_ARG_POINTER(aAttribute);
-
-    // Find the named URI attribute on the (element) node and change it to reference
-    // a local file.
-
-    nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode);
-    MOZ_ASSERT(element);
-
-    nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap;
-    nsresult rv = element->GetAttributes(getter_AddRefs(attrMap));
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-
-    NS_ConvertASCIItoUTF16 attribute(aAttribute);
-    NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI);
-    nsCOMPtr<nsIDOMAttr> attr;
-    rv = attrMap->GetNamedItemNS(namespaceURI, attribute, getter_AddRefs(attr));
-    if (attr) {
-        nsString uri;
-        attr->GetValue(uri);
-        rv = FixupURI(uri);
-        if (NS_SUCCEEDED(rv))
-        {
-            attr->SetValue(uri);
-        }
+        aSpecOut = NS_EscapeURL(rawPathURL, esc_FilePath, buf);
+    } else {
+        fileAsURI->GetSpec(aSpecOut);
     }
-
-    return rv;
-}
-
-nsresult
-nsWebBrowserPersist::FixupAnchor(nsIDOMNode *aNode)
-{
-    NS_ENSURE_ARG_POINTER(aNode);
-
-    nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode);
-    MOZ_ASSERT(element);
-
-    nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap;
-    nsresult rv = element->GetAttributes(getter_AddRefs(attrMap));
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-
-    if (mPersistFlags & PERSIST_FLAGS_DONT_FIXUP_LINKS)
-    {
-        return NS_OK;
-    }
-
-    // Make all anchor links absolute so they point off onto the Internet
-    nsString attribute(NS_LITERAL_STRING("href"));
-    nsCOMPtr<nsIDOMAttr> attr;
-    rv = attrMap->GetNamedItem(attribute, getter_AddRefs(attr));
-    if (attr)
-    {
-        nsString oldValue;
-        attr->GetValue(oldValue);
-        NS_ConvertUTF16toUTF8 oldCValue(oldValue);
-
-        // Skip empty values and self-referencing bookmarks
-        if (oldCValue.IsEmpty() || oldCValue.CharAt(0) == '#')
-        {
-            return NS_OK;
-        }
-
-        // if saving file to same location, we don't need to do any fixup
-        bool isEqual = false;
-        if (NS_SUCCEEDED(mCurrentBaseURI->Equals(mTargetBaseURI, &isEqual))
-            && isEqual)
-        {
-            return NS_OK;
-        }
-
-        nsCOMPtr<nsIURI> relativeURI;
-        relativeURI = (mPersistFlags & PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION)
-                      ? mTargetBaseURI : mCurrentBaseURI;
-        // Make a new URI to replace the current one
-        nsCOMPtr<nsIURI> newURI;
-        rv = NS_NewURI(getter_AddRefs(newURI), oldCValue,
-                       mCurrentCharset.get(), relativeURI);
-        if (NS_SUCCEEDED(rv) && newURI)
-        {
-            newURI->SetUserPass(EmptyCString());
-            nsAutoCString uriSpec;
-            newURI->GetSpec(uriSpec);
-            attr->SetValue(NS_ConvertUTF8toUTF16(uriSpec));
-        }
+    if (mIsSubFrame) {
+        AppendUTF16toUTF8(mSubFrameExt, aSpecOut);
     }
 
     return NS_OK;
 }
 
-nsresult
-nsWebBrowserPersist::StoreAndFixupStyleSheet(nsIStyleSheet *aStyleSheet)
-{
-    // TODO go through the style sheet fixing up all links
-    return NS_OK;
-}
-
 bool
-nsWebBrowserPersist::DocumentEncoderExists(const char16_t *aContentType)
+nsWebBrowserPersist::DocumentEncoderExists(const char *aContentType)
 {
     // Check if there is an encoder for the desired content type.
     nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE);
-    AppendUTF16toUTF8(aContentType, contractID);
+    contractID.Append(aContentType);
 
     nsCOMPtr<nsIComponentRegistrar> registrar;
     NS_GetComponentRegistrar(getter_AddRefs(registrar));
     if (registrar)
     {
         bool result;
         nsresult rv = registrar->IsContractIDRegistered(contractID.get(),
                                                         &result);
@@ -3544,51 +2637,48 @@ nsWebBrowserPersist::DocumentEncoderExis
             return true;
         }
     }
     return false;
 }
 
 nsresult
 nsWebBrowserPersist::SaveSubframeContent(
-    nsIDOMDocument *aFrameContent, URIData *aData)
+    nsIWebBrowserPersistDocument *aFrameContent,
+    const nsCString& aURISpec,
+    URIData *aData)
 {
     NS_ENSURE_ARG_POINTER(aData);
 
     // Extract the content type for the frame's contents.
-    nsCOMPtr<nsIDocument> frameDoc(do_QueryInterface(aFrameContent));
-    NS_ENSURE_STATE(frameDoc);
-
-    nsAutoString contentType;
-    nsresult rv = frameDoc->GetContentType(contentType);
+    nsAutoCString contentType;
+    nsresult rv = aFrameContent->GetContentType(contentType);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsXPIDLString ext;
-    GetExtensionForContentType(contentType.get(), getter_Copies(ext));
+    GetExtensionForContentType(NS_ConvertASCIItoUTF16(contentType).get(),
+                               getter_Copies(ext));
 
     // We must always have an extension so we will try to re-assign
     // the original extension if GetExtensionForContentType fails.
-    if (ext.IsEmpty())
-    {
-        nsCOMPtr<nsIURL> url(do_QueryInterface(frameDoc->GetDocumentURI(),
-                                               &rv));
+    if (ext.IsEmpty()) {
+        nsCOMPtr<nsIURI> docURI;
+        rv = NS_NewURI(getter_AddRefs(docURI), aURISpec, mCurrentCharset.get());
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        nsCOMPtr<nsIURL> url(do_QueryInterface(docURI, &rv));
         nsAutoCString extension;
-        if (NS_SUCCEEDED(rv))
-        {
+        if (NS_SUCCEEDED(rv)) {
             url->GetFileExtension(extension);
-        }
-        else
-        {
+        } else {
             extension.AssignLiteral("htm");
         }
         aData->mSubFrameExt.Assign(char16_t('.'));
         AppendUTF8toUTF16(extension, aData->mSubFrameExt);
-    }
-    else
-    {
+    } else {
         aData->mSubFrameExt.Assign(char16_t('.'));
         aData->mSubFrameExt.Append(ext);
     }
 
     nsString filenameWithExt = aData->mFilename;
     filenameWithExt.Append(aData->mSubFrameExt);
 
     // Work out the path for the subframe
@@ -3614,23 +2704,24 @@ nsWebBrowserPersist::SaveSubframeContent
     NS_ENSURE_SUCCESS(rv, rv);
     rv = CalculateUniqueFilename(frameDataURI);
     NS_ENSURE_SUCCESS(rv, rv);
 
     mCurrentThingsToPersist++;
 
     // We shouldn't use SaveDocumentInternal for the contents
     // of frames that are not documents, e.g. images.
-    if (DocumentEncoderExists(contentType.get()))
-    {
-        rv = SaveDocumentInternal(aFrameContent, frameURI, frameDataURI);
-    }
-    else
-    {
-        rv = StoreURI(frameDoc->GetDocumentURI());
+    if (DocumentEncoderExists(contentType.get())) {
+        auto toWalk = mozilla::MakeUnique<WalkData>();
+        toWalk->mDocument = aFrameContent;
+        toWalk->mFile = frameURI;
+        toWalk->mDataPath = frameDataURI;
+        mWalkStack.AppendElement(mozilla::Move(toWalk));
+    } else {
+        rv = StoreURI(aURISpec.get());
     }
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Store the updated uri to the frame
     aData->mFile = frameURI;
     aData->mSubFrameExt.Truncate(); // we already put this in frameURI
 
     return NS_OK;
@@ -3650,98 +2741,16 @@ nsWebBrowserPersist::CreateChannelFromUR
     NS_ENSURE_SUCCESS(rv, rv);
     NS_ENSURE_ARG_POINTER(*aChannel);
 
     rv = (*aChannel)->SetNotificationCallbacks(static_cast<nsIInterfaceRequestor*>(this));
     NS_ENSURE_SUCCESS(rv, rv);
     return NS_OK;
 }
 
-nsresult
-nsWebBrowserPersist::SaveDocumentWithFixup(
-    nsIDOMDocument *aDocument,
-    nsIURI *aFile, bool aReplaceExisting, const nsACString &aFormatType,
-    const nsCString &aSaveCharset, uint32_t aFlags)
-{
-    NS_ENSURE_ARG_POINTER(aFile);
-
-    nsRefPtr<nsEncoderNodeFixup> nodeFixup = new nsEncoderNodeFixup();
-    nodeFixup->mWebBrowserPersist = this;
-
-    nsresult  rv = NS_OK;
-    nsCOMPtr<nsIFile> localFile;
-    GetLocalFileFromURI(aFile, getter_AddRefs(localFile));
-    if (localFile)
-    {
-        // if we're not replacing an existing file but the file
-        // exists, something is wrong
-        bool fileExists = false;
-        rv = localFile->Exists(&fileExists);
-        NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-
-        if (!aReplaceExisting && fileExists)
-            return NS_ERROR_FAILURE;                // where are the file I/O errors?
-    }
-
-    nsCOMPtr<nsIOutputStream> outputStream;
-    rv = MakeOutputStream(aFile, getter_AddRefs(outputStream));
-    if (NS_FAILED(rv))
-    {
-        SendErrorStatusChange(false, rv, nullptr, aFile);
-        return NS_ERROR_FAILURE;
-    }
-    NS_ENSURE_TRUE(outputStream, NS_ERROR_FAILURE);
-
-    // Get a document encoder instance
-    nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE);
-    contractID.Append(aFormatType);
-
-    nsCOMPtr<nsIDocumentEncoder> encoder = do_CreateInstance(contractID.get(), &rv);
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-
-    NS_ConvertASCIItoUTF16 newContentType(aFormatType);
-    rv = encoder->Init(aDocument, newContentType, aFlags);
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-
-    mTargetBaseURI = aFile;
-
-    // Set the node fixup callback
-    encoder->SetNodeFixup(nodeFixup);
-
-    if (mWrapColumn && (aFlags & ENCODE_FLAGS_WRAP))
-        encoder->SetWrapColumn(mWrapColumn);
-
-    nsAutoCString charsetStr(aSaveCharset);
-    if (charsetStr.IsEmpty())
-    {
-        nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDocument);
-        NS_ASSERTION(doc, "Need a document");
-        charsetStr = doc->GetDocumentCharacterSet();
-    }
-
-    rv = encoder->SetCharset(charsetStr);
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-
-    rv = encoder->EncodeToStream(outputStream);
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-
-    if (!localFile)
-    {
-        nsCOMPtr<nsIStorageStream> storStream(do_QueryInterface(outputStream));
-        if (storStream)
-        {
-            outputStream->Close();
-            rv = StartUpload(storStream, aFile, aFormatType);
-            NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-        }
-    }
-
-    return rv;
-}
-
 
 // we store the current location as the key (absolutized version of domnode's attribute's value)
 nsresult
 nsWebBrowserPersist::MakeAndStoreLocalFilenameInURIMap(
     nsIURI *aURI, bool aNeedsPersisting, URIData **aData)
 {
     NS_ENSURE_ARG_POINTER(aURI);
 
@@ -3834,52 +2843,8 @@ void nsWebBrowserPersist::SetApplyConver
             bool applyConversion = false;
             rv = helperAppService->ApplyDecodingForExtension(extension, encType,
                                                              &applyConversion);
             if (NS_SUCCEEDED(rv))
                 encChannel->SetApplyConversion(applyConversion);
         }
     }
 }
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-nsEncoderNodeFixup::nsEncoderNodeFixup() : mWebBrowserPersist(nullptr)
-{
-}
-
-
-nsEncoderNodeFixup::~nsEncoderNodeFixup()
-{
-}
-
-
-NS_IMPL_ADDREF(nsEncoderNodeFixup)
-NS_IMPL_RELEASE(nsEncoderNodeFixup)
-
-
-NS_INTERFACE_MAP_BEGIN(nsEncoderNodeFixup)
-    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentEncoderNodeFixup)
-    NS_INTERFACE_MAP_ENTRY(nsIDocumentEncoderNodeFixup)
-NS_INTERFACE_MAP_END
-
-
-NS_IMETHODIMP nsEncoderNodeFixup::FixupNode(
-    nsIDOMNode *aNode, bool *aSerializeCloneKids, nsIDOMNode **aOutNode)
-{
-    NS_ENSURE_ARG_POINTER(aNode);
-    NS_ENSURE_ARG_POINTER(aOutNode);
-    NS_ENSURE_TRUE(mWebBrowserPersist, NS_ERROR_FAILURE);
-
-    *aOutNode = nullptr;
-
-    // Test whether we need to fixup the node
-    uint16_t type = 0;
-    aNode->GetNodeType(&type);
-    if (type == nsIDOMNode::ELEMENT_NODE ||
-        type == nsIDOMNode::PROCESSING_INSTRUCTION_NODE)
-    {
-        return mWebBrowserPersist->CloneNodeWithFixedUpAttributes(aNode, aSerializeCloneKids, aOutNode);
-    }
-
-    return NS_OK;
-}
--- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h
+++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h
@@ -17,81 +17,86 @@
 #include "nsIInputStream.h"
 #include "nsIChannel.h"
 #include "nsIStyleSheet.h"
 #include "nsIDocumentEncoder.h"
 #include "nsITransport.h"
 #include "nsIProgressEventSink.h"
 #include "nsIFile.h"
 #include "nsIWebProgressListener2.h"
+#include "nsIWebBrowserPersistDocument.h"
 
+#include "mozilla/UniquePtr.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 #include "nsTArray.h"
 
 #include "nsCWebBrowserPersist.h"
 
-class nsEncoderNodeFixup;
 class nsIStorageStream;
+class nsIWebBrowserPersistDocument;
 
-struct CleanupData;
-struct DocData;
-struct OutputData;
-struct UploadData;
-struct URIData;
-
-class nsWebBrowserPersist : public nsIInterfaceRequestor,
-                            public nsIWebBrowserPersist,
-                            public nsIStreamListener,
-                            public nsIProgressEventSink,
-                            public nsSupportsWeakReference
+class nsWebBrowserPersist final : public nsIInterfaceRequestor,
+                                  public nsIWebBrowserPersist,
+                                  public nsIStreamListener,
+                                  public nsIProgressEventSink,
+                                  public nsSupportsWeakReference
 {
     friend class nsEncoderNodeFixup;
 
 // Public members
 public:
     nsWebBrowserPersist();
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIINTERFACEREQUESTOR
     NS_DECL_NSICANCELABLE
     NS_DECL_NSIWEBBROWSERPERSIST
     NS_DECL_NSIREQUESTOBSERVER
     NS_DECL_NSISTREAMLISTENER
     NS_DECL_NSIPROGRESSEVENTSINK
 
-// Protected members
-protected:
+// Private members
+private:
     virtual ~nsWebBrowserPersist();
-    nsresult CloneNodeWithFixedUpAttributes(
-        nsIDOMNode *aNodeIn, bool *aSerializeCloneKids, nsIDOMNode **aNodeOut);
     nsresult SaveURIInternal(
         nsIURI *aURI, nsISupports *aCacheKey, nsIURI *aReferrer,
         uint32_t aReferrerPolicy, nsIInputStream *aPostData,
         const char *aExtraHeaders, nsIURI *aFile,
         bool aCalcFileExt, bool aIsPrivate);
     nsresult SaveChannelInternal(
         nsIChannel *aChannel, nsIURI *aFile, bool aCalcFileExt);
     nsresult SaveDocumentInternal(
-        nsIDOMDocument *aDocument, nsIURI *aFile, nsIURI *aDataPath);
+        nsIWebBrowserPersistDocument *aDocument,
+        nsIURI *aFile,
+        nsIURI *aDataPath);
     nsresult SaveDocuments();
-    nsresult GetDocEncoderContentType(
-        nsIDOMDocument *aDocument, const char16_t *aContentType,
-        char16_t **aRealContentType);
+    void FinishSaveDocumentInternal(nsIURI* aFile, nsIFile* aDataPath);
     nsresult GetExtensionForContentType(
         const char16_t *aContentType, char16_t **aExt);
-    nsresult GetDocumentExtension(nsIDOMDocument *aDocument, char16_t **aExt);
+
+    struct CleanupData;
+    struct DocData;
+    struct OutputData;
+    struct UploadData;
+    struct URIData;
+    struct WalkData;
 
-// Private members
-private:
+    class OnWalk;
+    class OnWrite;
+    class FlatURIMap;
+    friend class OnWalk;
+    friend class OnWrite;
+
+    nsresult SaveDocumentDeferred(mozilla::UniquePtr<WalkData>&& aData);
     void Cleanup();
     void CleanupLocalFiles();
     nsresult GetValidURIFromObject(nsISupports *aObject, nsIURI **aURI) const;
     nsresult GetLocalFileFromURI(nsIURI *aURI, nsIFile **aLocalFile) const;
-    nsresult AppendPathToURI(nsIURI *aURI, const nsAString & aPath) const;
+    static nsresult AppendPathToURI(nsIURI *aURI, const nsAString & aPath);
     nsresult MakeAndStoreLocalFilenameInURIMap(
         nsIURI *aURI, bool aNeedsPersisting, URIData **aData);
     nsresult MakeOutputStream(
         nsIURI *aFile, nsIOutputStream **aOutputStream);
     nsresult MakeOutputStreamFromFile(
         nsIFile *aFile, nsIOutputStream **aOutputStream);
     nsresult MakeOutputStreamFromURI(nsIURI *aURI, nsIOutputStream  **aOutStream);
     nsresult CreateChannelFromURI(nsIURI *aURI, nsIChannel **aChannel);
@@ -107,56 +112,30 @@ private:
     nsresult StoreURI(
         const char *aURI,
         bool aNeedsPersisting = true,
         URIData **aData = nullptr);
     nsresult StoreURI(
         nsIURI *aURI,
         bool aNeedsPersisting = true,
         URIData **aData = nullptr);
-    nsresult StoreURIAttributeNS(
-        nsIDOMNode *aNode, const char *aNamespaceURI, const char *aAttribute,
-        bool aNeedsPersisting = true,
-        URIData **aData = nullptr);
-    nsresult StoreURIAttribute(
-        nsIDOMNode *aNode, const char *aAttribute,
-        bool aNeedsPersisting = true,
-        URIData **aData = nullptr)
-    {
-        return StoreURIAttributeNS(aNode, "", aAttribute, aNeedsPersisting, aData);
-    }
-    bool DocumentEncoderExists(const char16_t *aContentType);
+    bool DocumentEncoderExists(const char *aContentType);
 
-    nsresult GetNodeToFixup(nsIDOMNode *aNodeIn, nsIDOMNode **aNodeOut);
-    nsresult FixupURI(nsAString &aURI);
-    nsresult FixupNodeAttributeNS(nsIDOMNode *aNode, const char *aNamespaceURI, const char *aAttribute);
-    nsresult FixupNodeAttribute(nsIDOMNode *aNode, const char *aAttribute)
-    {
-        return FixupNodeAttributeNS(aNode, "", aAttribute);
-    }
-    nsresult FixupAnchor(nsIDOMNode *aNode);
-    nsresult FixupXMLStyleSheetLink(nsIDOMProcessingInstruction *aPI, const nsAString &aHref);
-    nsresult GetXMLStyleSheetLink(nsIDOMProcessingInstruction *aPI, nsAString &aHref);
-
-    nsresult StoreAndFixupStyleSheet(nsIStyleSheet *aStyleSheet);
-    nsresult SaveDocumentWithFixup(
-        nsIDOMDocument *pDocument,
-        nsIURI *aFile, bool aReplaceExisting, const nsACString &aFormatType,
-        const nsCString &aSaveCharset, uint32_t  aFlags);
     nsresult SaveSubframeContent(
-        nsIDOMDocument *aFrameContent, URIData *aData);
+        nsIWebBrowserPersistDocument *aFrameContent,
+        const nsCString& aURISpec,
+        URIData *aData);
     nsresult SendErrorStatusChange(
         bool aIsReadError, nsresult aResult, nsIRequest *aRequest, nsIURI *aURI);
-    nsresult OnWalkDOMNode(nsIDOMNode *aNode);
 
     nsresult FixRedirectedChannelEntry(nsIChannel *aNewChannel);
 
-    void EndDownload(nsresult aResult = NS_OK);
-    nsresult SaveGatheredURIs(nsIURI *aFileAsURI);
-    bool SerializeNextFile();
+    void EndDownload(nsresult aResult);
+    void FinishDownload();
+    void SerializeNextFile();
     void CalcTotalProgress();
 
     void SetApplyConversionIfNeeded(nsIChannel *aChannel);
 
     // Hash table enumerators
     static PLDHashOperator EnumPersistURIs(
         const nsACString &aKey, URIData *aData, void* aClosure);
     static PLDHashOperator EnumCleanupOutputMap(
@@ -166,16 +145,18 @@ private:
     static PLDHashOperator EnumCalcProgress(
         nsISupports *aKey, OutputData *aData, void* aClosure);
     static PLDHashOperator EnumCalcUploadProgress(
         nsISupports *aKey, UploadData *aData, void* aClosure);
     static PLDHashOperator EnumFixRedirect(
         nsISupports *aKey, OutputData *aData, void* aClosure);
     static PLDHashOperator EnumCountURIsToPersist(
         const nsACString &aKey, URIData *aData, void* aClosure);
+    static PLDHashOperator EnumCopyURIsToFlatMap(
+        const nsACString &aKey, URIData *aData, void* aClosure);
 
     nsCOMPtr<nsIURI>          mCurrentDataPath;
     bool                      mCurrentDataPathIsRelative;
     nsCString                 mCurrentRelativePathToData;
     nsCOMPtr<nsIURI>          mCurrentBaseURI;
     nsCString                 mCurrentCharset;
     nsCOMPtr<nsIURI>          mTargetBaseURI;
     uint32_t                  mCurrentThingsToPersist;
@@ -188,44 +169,31 @@ private:
      * mProgressListener, but is a member to avoid having to qi it for each
      * progress notification.
      */
     nsCOMPtr<nsIWebProgressListener2> mProgressListener2;
     nsCOMPtr<nsIProgressEventSink> mEventSink;
     nsClassHashtable<nsISupportsHashKey, OutputData> mOutputMap;
     nsClassHashtable<nsISupportsHashKey, UploadData> mUploadList;
     nsClassHashtable<nsCStringHashKey, URIData> mURIMap;
+    nsCOMPtr<nsIWebBrowserPersistURIMap> mFlatURIMap;
+    nsTArray<mozilla::UniquePtr<WalkData>> mWalkStack;
     nsTArray<DocData*>        mDocList;
     nsTArray<CleanupData*>    mCleanupList;
     nsTArray<nsCString>       mFilenameList;
     bool                      mFirstAndOnlyUse;
+    bool                      mSavingDocument;
     bool                      mCancel;
-    bool                      mJustStartedLoading;
     bool                      mCompleted;
     bool                      mStartSaving;
     bool                      mReplaceExisting;
     bool                      mSerializingOutput;
     bool                      mIsPrivate;
     uint32_t                  mPersistFlags;
     nsresult                  mPersistResult;
     int64_t                   mTotalCurrentProgress;
     int64_t                   mTotalMaxProgress;
     int16_t                   mWrapColumn;
     uint32_t                  mEncodingFlags;
     nsString                  mContentType;
 };
 
-// Helper class does node fixup during persistence
-class nsEncoderNodeFixup : public nsIDocumentEncoderNodeFixup
-{
-public:
-    nsEncoderNodeFixup();
-
-    NS_DECL_ISUPPORTS
-    NS_IMETHOD FixupNode(nsIDOMNode *aNode, bool *aSerializeCloneKids, nsIDOMNode **aOutNode) override;
-
-    nsWebBrowserPersist *mWebBrowserPersist;
-
-protected:
-    virtual ~nsEncoderNodeFixup();
-};
-
 #endif
--- a/toolkit/content/contentAreaUtils.js
+++ b/toolkit/content/contentAreaUtils.js
@@ -121,56 +121,101 @@ function saveImageURL(aURL, aFileName, a
       // Failure to get type and content-disposition off the image is non-fatal
     }
   }
   internalSave(aURL, null, aFileName, aContentDisp, aContentType,
                aShouldBypassCache, aFilePickerTitleKey, null, aReferrer,
                aDoc, aSkipPrompt, null);
 }
 
+// This is like saveDocument, but takes any browser/frame-like element
+// (nsIFrameLoaderOwner) and saves the current document inside it,
+// whether in-process or out-of-process.
+function saveBrowser(aBrowser, aSkipPrompt)
+{
+  if (!aBrowser) {
+    throw "Must have a browser when calling saveBrowser";
+  }
+  let persistable = aBrowser.QueryInterface(Ci.nsIFrameLoaderOwner)
+                    .frameLoader
+                    .QueryInterface(Ci.nsIWebBrowserPersistable);
+  persistable.startPersistence({
+    onDocumentReady: function (document) {
+      saveDocument(document, aSkipPrompt);
+    }
+    // This interface also has an |onError| method which takes an
+    // nsresult, but in case of asynchronous failure there isn't
+    // really anything useful that can be done here.
+  });
+}
+
+// Saves a document; aDocument can be an nsIWebBrowserPersistDocument
+// (see saveBrowser, above) or an nsIDOMDocument.
+//
+// aDocument can also be a CPOW for a remote nsIDOMDocument, in which
+// case "save as" modes that serialize the document's DOM are
+// unavailable.  This is a temporary measure for the "Save Frame As"
+// command (bug 1141337), and it's possible that there could be
+// add-ons doing something similar.
 function saveDocument(aDocument, aSkipPrompt)
 {
+  const Ci = Components.interfaces;
+
   if (!aDocument)
     throw "Must have a document when calling saveDocument";
 
-  // We want to use cached data because the document is currently visible.
-  var ifreq =
-    aDocument.defaultView
-             .QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+  let contentDisposition = null;
+  let cacheKeyInt = null;
+
+  if (aDocument instanceof Ci.nsIWebBrowserPersistDocument) {
+    // nsIWebBrowserPersistDocument exposes these directly.
+    contentDisposition = aDocument.contentDisposition;
+    cacheKeyInt = aDocument.cacheKey;
+  } else if (aDocument instanceof Ci.nsIDOMDocument) {
+    // Otherwise it's an actual nsDocument (and possibly a CPOW).
+    // We want to use cached data because the document is currently visible.
+    let ifreq =
+      aDocument.defaultView
+               .QueryInterface(Ci.nsIInterfaceRequestor);
 
-  var contentDisposition = null;
-  try {
-    contentDisposition =
-      ifreq.getInterface(Components.interfaces.nsIDOMWindowUtils)
-           .getDocumentMetadata("content-disposition");
-  } catch (ex) {
-    // Failure to get a content-disposition is ok
+    try {
+      contentDisposition =
+        ifreq.getInterface(Ci.nsIDOMWindowUtils)
+             .getDocumentMetadata("content-disposition");
+    } catch (ex) {
+      // Failure to get a content-disposition is ok
+    }
+
+    try {
+      let shEntry =
+        ifreq.getInterface(Ci.nsIWebNavigation)
+             .QueryInterface(Ci.nsIWebPageDescriptor)
+             .currentDescriptor
+             .QueryInterface(Ci.nsISHEntry);
+
+      let cacheKey = shEntry.cacheKey
+                            .QueryInterface(Ci.nsISupportsPRUint32)
+                            .data;
+      // cacheKey might be a CPOW, which can't be passed to native
+      // code, but the data attribute is just a number.
+      cacheKeyInt = cacheKey.data;
+    } catch (ex) {
+      // We might not find it in the cache.  Oh, well.
+    }
   }
 
+  // Convert the cacheKey back into an XPCOM object.
   let cacheKey = null;
-  try {
-    let shEntry =
-      ifreq.getInterface(Components.interfaces.nsIWebNavigation)
-           .QueryInterface(Components.interfaces.nsIWebPageDescriptor)
-           .currentDescriptor
-           .QueryInterface(Components.interfaces.nsISHEntry);
-
-    shEntry.cacheKey.QueryInterface(Components.interfaces.nsISupportsPRUint32);
-
-    // In the event that the cacheKey is a CPOW, we cannot pass it to
-    // nsIWebBrowserPersist, so we create a new one and copy the value
-    // over. This is a workaround until bug 1101100 is fixed.
+  if (cacheKeyInt) {
     cacheKey = Cc["@mozilla.org/supports-PRUint32;1"]
-                 .createInstance(Ci.nsISupportsPRUint32);
-    cacheKey.data = shEntry.cacheKey.data;
-  } catch (ex) {
-    // We might not find it in the cache.  Oh, well.
+      .createInstance(Ci.nsISupportsPRUint32);
+    cacheKey.data = cacheKeyInt;
   }
 
-  internalSave(aDocument.location.href, aDocument, null, contentDisposition,
+  internalSave(aDocument.documentURI, aDocument, null, contentDisposition,
                aDocument.contentType, false, null, null,
                aDocument.referrer ? makeURI(aDocument.referrer) : null,
                aDocument, aSkipPrompt, cacheKey);
 }
 
 function DownloadListener(win, transfer) {
   function makeClosure(name) {
     return function() {
@@ -348,27 +393,33 @@ function internalSave(aURL, aDocument, a
                           (((saveMode & SAVEMODE_COMPLETE_DOM) && (saveAsType == kSaveAsType_Complete)) ||
                            ((saveMode & SAVEMODE_COMPLETE_TEXT) && (saveAsType == kSaveAsType_Text)));
     // If we're saving a document, and are saving either in complete mode or
     // as converted text, pass the document to the web browser persist component.
     // If we're just saving the HTML (second option in the list), send only the URI.
     let nonCPOWDocument =
       aDocument && !Components.utils.isCrossProcessWrapper(aDocument);
 
+    let isPrivate = aIsContentWindowPrivate;
+    if (isPrivate === undefined) {
+      isPrivate = aInitiatingDocument instanceof Components.interfaces.nsIDOMDocument
+        ? PrivateBrowsingUtils.isContentWindowPrivate(aInitiatingDocument.defaultView)
+        : aInitiatingDocument.isPrivate;
+    }
+
     var persistArgs = {
       sourceURI         : sourceURI,
       sourceReferrer    : aReferrer,
       sourceDocument    : useSaveDocument ? aDocument : null,
       targetContentType : (saveAsType == kSaveAsType_Text) ? "text/plain" : null,
       targetFile        : file,
       sourceCacheKey    : aCacheKey,
       sourcePostData    : nonCPOWDocument ? getPostData(aDocument) : null,
       bypassCache       : aShouldBypassCache,
-      initiatingWindow  : aInitiatingDocument && aInitiatingDocument.defaultView,
-      isContentWindowPrivate : aIsContentWindowPrivate
+      isPrivate         : isPrivate,
     };
 
     // Start the actual save process
     internalPersist(persistArgs);
   }
 }
 
 /**
@@ -393,22 +444,18 @@ function internalSave(aURL, aDocument, a
  *        The nsIFile of the file to create
  * @param persistArgs.targetContentType
  *        Required and used only when persistArgs.sourceDocument is present,
  *        determines the final content type of the saved file, or null to use
  *        the same content type as the source document. Currently only
  *        "text/plain" is meaningful.
  * @param persistArgs.bypassCache
  *        If true, the document will always be refetched from the server
- * @param persistArgs.initiatingWindow [optional]
- *        The window from which the save operation was initiated.
- *        If this is omitted then isContentWindowPrivate has to be provided.
- * @param persistArgs.isContentWindowPrivate [optional]
- *        If present then isPrivate is set to this value without using
- *        persistArgs.initiatingWindow.
+ * @param persistArgs.isPrivate
+ *        Indicates whether this is taking place in a private browsing context.
  */
 function internalPersist(persistArgs)
 {
   var persist = makeWebBrowserPersist();
 
   // Calculate persist flags.
   const nsIWBP = Components.interfaces.nsIWebBrowserPersist;
   const flags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
@@ -419,25 +466,20 @@ function internalPersist(persistArgs)
     persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_FROM_CACHE;
 
   // Leave it to WebBrowserPersist to discover the encoding type (or lack thereof):
   persist.persistFlags |= nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
 
   // Find the URI associated with the target file
   var targetFileURL = makeFileURI(persistArgs.targetFile);
 
-  let isPrivate = persistArgs.isContentWindowPrivate;
-  if (isPrivate === undefined) {
-    isPrivate = PrivateBrowsingUtils.isContentWindowPrivate(persistArgs.initiatingWindow);
-  }
-
   // Create download and initiate it (below)
   var tr = Components.classes["@mozilla.org/transfer;1"].createInstance(Components.interfaces.nsITransfer);
   tr.init(persistArgs.sourceURI,
-          targetFileURL, "", null, null, null, persist, isPrivate);
+          targetFileURL, "", null, null, null, persist, persistArgs.isPrivate);
   persist.progressListener = new DownloadListener(window, tr);
 
   if (persistArgs.sourceDocument) {
     // Saving a Document, not a URI:
     var filesFolder = null;
     if (persistArgs.targetContentType != "text/plain") {
       // Create the local directory into which to save associated files.
       filesFolder = persistArgs.targetFile.clone();
@@ -466,17 +508,17 @@ function internalPersist(persistArgs)
   } else {
     persist.savePrivacyAwareURI(persistArgs.sourceURI,
                                 persistArgs.sourceCacheKey,
                                 persistArgs.sourceReferrer,
                                 Components.interfaces.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE,
                                 persistArgs.sourcePostData,
                                 null,
                                 targetFileURL,
-                                isPrivate);
+                                persistArgs.isPrivate);
   }
 }
 
 /**
  * Structure for holding info about automatically supplied parameters for
  * internalSave(...). This allows parameters to be supplied so the user does not
  * need to be prompted for file info.
  * @param aFileAutoChosen This is an nsIFile object that has been
@@ -823,27 +865,32 @@ function appendFiltersForContentType(aFi
     aFilePicker.appendFilters(Components.interfaces.nsIFilePicker.filterText);
 
   // Always append the all files (*) filter
   aFilePicker.appendFilters(Components.interfaces.nsIFilePicker.filterAll);
 }
 
 function getPostData(aDocument)
 {
+  const Ci = Components.interfaces;
+
+  if (aDocument instanceof Ci.nsIWebBrowserPersistDocument) {
+    return aDocument.postData;
+  }
   try {
     // Find the session history entry corresponding to the given document. In
     // the current implementation, nsIWebPageDescriptor.currentDescriptor always
     // returns a session history entry.
-    var sessionHistoryEntry =
+    let sessionHistoryEntry =
         aDocument.defaultView
-                 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                 .getInterface(Components.interfaces.nsIWebNavigation)
-                 .QueryInterface(Components.interfaces.nsIWebPageDescriptor)
+                 .QueryInterface(Ci.nsIInterfaceRequestor)
+                 .getInterface(Ci.nsIWebNavigation)
+                 .QueryInterface(Ci.nsIWebPageDescriptor)
                  .currentDescriptor
-                 .QueryInterface(Components.interfaces.nsISHEntry);
+                 .QueryInterface(Ci.nsISHEntry);
     return sessionHistoryEntry.postData;
   }
   catch (e) {
   }
   return null;
 }
 
 function makeWebBrowserPersist()