Bug 1101100 - Multiprocess-enable nsWebBrowserPersist. r=billm
authorJed Davis <jld@mozilla.com>
Wed, 05 Aug 2015 14:25:39 -0700
changeset 283417 d19a0d92455b5606149d90df5ce80278c73d5309
parent 283416 45ea852a8f61a301a4e698bbd32d05b53415b588
child 283418 331e489c753420ba3aeb537fe6af196f1a0ffee2
push id4143
push userbcampen@mozilla.com
push dateThu, 06 Aug 2015 20:00:54 +0000
reviewersbillm
bugs1101100
milestone42.0a1
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()