[tbb-commits] [tor-browser/tor-browser-60.3.0esr-8.5-1] Bug 22343: Make 'Save Page As' obey first-party isolation

gk at torproject.org gk at torproject.org
Thu Nov 15 20:35:28 UTC 2018


commit c2b7e89ecaf3ea6a14a650a0a71c6dc3ebb02c50
Author: Arthur Edelstein <arthuredelstein at gmail.com>
Date:   Tue May 30 10:25:34 2017 -0700

    Bug 22343: Make 'Save Page As' obey first-party isolation
    
    Fixes:
    * File menu:
      - Save Page As
    * Context menu in content pages:
      - Save Page As
      - Save Image As
      - Save Video As
      - Save Link As
      - Save Frame As
    * Page Info "Media" Panel:
      - Save As
---
 browser/base/content/browser.js                |  2 +-
 browser/base/content/nsContextMenu.js          | 39 ++++++++++++++++++--------
 browser/base/content/pageinfo/pageInfo.js      |  5 ++--
 browser/base/content/utilityOverlay.js         |  6 ++--
 dom/webbrowserpersist/nsIWebBrowserPersist.idl |  9 +++++-
 dom/webbrowserpersist/nsWebBrowserPersist.cpp  | 18 ++++++++++--
 dom/webbrowserpersist/nsWebBrowserPersist.h    |  2 ++
 mobile/android/chrome/content/browser.js       |  6 ++--
 netwerk/base/LoadContextInfo.cpp               | 18 ++++++++++--
 toolkit/components/browser/nsWebBrowser.cpp    | 12 ++++++++
 toolkit/content/contentAreaUtils.js            | 32 +++++++++++++++------
 11 files changed, 115 insertions(+), 34 deletions(-)

diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 0fc6a72daf66..5eed34e08086 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6175,7 +6175,7 @@ function handleLinkClick(event, href, linkNode) {
 
   if (where == "save") {
     saveURL(href, linkNode ? gatherTextUnder(linkNode) : "", null, true,
-            true, doc.documentURIObject, doc);
+            true, doc.documentURIObject, doc, undefined, doc.nodePrincipal);
     event.preventDefault();
     return true;
   }
diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js
index 368d0475ac34..37ebde22ea07 100644
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -989,9 +989,11 @@ nsContextMenu.prototype = {
     let onMessage = (message) => {
       mm.removeMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage);
       let dataURL = message.data.dataURL;
+      const principal = Services.scriptSecurityManager.createCodebasePrincipal(
+        makeURI(dataURL), this.principal.originAttributes);
       saveImageURL(dataURL, name, "SaveImageTitle", true, false,
                    document.documentURIObject, null, null, null,
-                   isPrivate);
+                   isPrivate, principal);
     };
     mm.addMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage);
   },
@@ -1063,7 +1065,7 @@ nsContextMenu.prototype = {
   // Helper function to wait for appropriate MIME-type headers and
   // then prompt the user with a file picker
   saveHelper(linkURL, linkText, dialogTitle, bypassCache, doc, docURI,
-             windowID, linkDownload, isContentWindowPrivate) {
+             windowID, linkDownload, isContentWindowPrivate, contentPrincipal) {
     // canonical def in nsURILoader.h
     const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
 
@@ -1116,7 +1118,7 @@ nsContextMenu.prototype = {
           // do it the old fashioned way, which will pick the best filename
           // it can without waiting.
           saveURL(linkURL, linkText, dialogTitle, bypassCache, false, docURI,
-                  doc, isContentWindowPrivate);
+                  doc, isContentWindowPrivate, contentPrincipal);
         }
         if (this.extListener)
           this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
@@ -1156,10 +1158,13 @@ nsContextMenu.prototype = {
       }
     };
 
+    const principal = Services.scriptSecurityManager.createCodebasePrincipal(
+      makeURI(linkURL), this.principal.originAttributes);
+
     // setting up a new channel for 'right click - save link as ...'
     var channel = NetUtil.newChannel({
                     uri: makeURI(linkURL),
-                    loadingPrincipal: this.principal,
+                    loadingPrincipal: principal,
                     contentPolicyType: Ci.nsIContentPolicy.TYPE_SAVEAS_DOWNLOAD,
                     securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
                   });
@@ -1201,14 +1206,17 @@ nsContextMenu.prototype = {
 
   // Save URL of clicked-on link.
   saveLink() {
-    urlSecurityCheck(this.linkURL, this.principal);
+    const principal = Services.scriptSecurityManager.createCodebasePrincipal(
+      makeURI(this.linkURL), this.principal.originAttributes);
+    urlSecurityCheck(this.linkURL, principal);
 
     let isContentWindowPrivate = this.isRemote ? this.ownerDoc.isPrivate : undefined;
     this.saveHelper(this.linkURL, this.linkTextStr, null, true, this.ownerDoc,
                     gContextMenuContentData.documentURIObject,
                     this.frameOuterWindowID,
                     this.linkDownload,
-                    isContentWindowPrivate);
+                    isContentWindowPrivate,
+                    principal);
   },
 
   // Backwards-compatibility wrapper
@@ -1223,23 +1231,32 @@ nsContextMenu.prototype = {
     let isContentWindowPrivate = this.isRemote ? this.ownerDoc.isPrivate : undefined;
     let referrerURI = gContextMenuContentData.documentURIObject;
     let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
+    let thisPrincipal = this.principal;
     if (this.onCanvas) {
       // Bypass cache, since it's a data: URL.
       this._canvasToBlobURL(this.target).then(function(blobURL) {
+        const principal = Services.scriptSecurityManager.createCodebasePrincipal(
+          makeURI(blobURL), thisPrincipal.originAttributes);
         saveImageURL(blobURL, "canvas.png", "SaveImageTitle",
                      true, false, referrerURI, null, null, null,
-                     isPrivate);
+                     isPrivate, principal);
       }, Cu.reportError);
     } else if (this.onImage) {
-      urlSecurityCheck(this.mediaURL, this.principal);
+      const principal = Services.scriptSecurityManager.createCodebasePrincipal(
+        makeURI(this.mediaURL), thisPrincipal.originAttributes);
+      urlSecurityCheck(this.mediaURL, principal);
       saveImageURL(this.mediaURL, null, "SaveImageTitle", false,
                    false, referrerURI, null, gContextMenuContentData.contentType,
-                   gContextMenuContentData.contentDisposition, isPrivate);
+                   gContextMenuContentData.contentDisposition, isPrivate,
+                   principal);
     } else if (this.onVideo || this.onAudio) {
-      urlSecurityCheck(this.mediaURL, this.principal);
+      const principal = Services.scriptSecurityManager.createCodebasePrincipal(
+        makeURI(this.mediaURL), thisPrincipal.originAttributes);
+      urlSecurityCheck(this.mediaURL, principal);
       var dialogTitle = this.onVideo ? "SaveVideoTitle" : "SaveAudioTitle";
       this.saveHelper(this.mediaURL, null, dialogTitle, false, doc, referrerURI,
-                      this.frameOuterWindowID, "", isContentWindowPrivate);
+                      this.frameOuterWindowID, "", isContentWindowPrivate,
+                      principal);
     }
   },
 
diff --git a/browser/base/content/pageinfo/pageInfo.js b/browser/base/content/pageinfo/pageInfo.js
index 86f548c74494..efe24f7487d9 100644
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -696,7 +696,7 @@ function saveMedia() {
         titleKey = "SaveAudioTitle";
 
       saveURL(url, null, titleKey, false, false, makeURI(item.baseURI),
-              null, gDocInfo.isContentWindowPrivate);
+              null, gDocInfo.isContentWindowPrivate, gDocInfo.principal);
     }
   } else {
     selectSaveFolder(function(aDirectory) {
@@ -704,7 +704,8 @@ function saveMedia() {
         var saveAnImage = function(aURIString, aChosenData, aBaseURI) {
           uniqueFile(aChosenData.file);
           internalSave(aURIString, null, null, null, null, false, "SaveImageTitle",
-                       aChosenData, aBaseURI, null, false, null, gDocInfo.isContentWindowPrivate);
+                       aChosenData, aBaseURI, null, false, null, gDocInfo.isContentWindowPrivate,
+                       gDocInfo.principal);
         };
 
         for (var i = 0; i < rowArray.length; i++) {
diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js
index b73a01a0b0f3..4cd7d91e4e9a 100644
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -258,14 +258,16 @@ function openLinkIn(url, where, params) {
 
     // ContentClick.jsm passes isContentWindowPrivate for saveURL instead of passing a CPOW initiatingDoc
     if ("isContentWindowPrivate" in params) {
-      saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI, null, params.isContentWindowPrivate);
+      saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI,
+              null, params.isContentWindowPrivate,
+              aPrincipal || aTriggeringPrincipal);
     } else {
       if (!aInitiatingDoc) {
         Cu.reportError("openUILink/openLinkIn was called with " +
           "where == 'save' but without initiatingDoc.  See bug 814264.");
         return;
       }
-      saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI, aInitiatingDoc);
+      saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI, aInitiatingDoc, params.isContentWindowPrivate, aPrincipal || aTriggeringPrincipal);
     }
     return;
   }
diff --git a/dom/webbrowserpersist/nsIWebBrowserPersist.idl b/dom/webbrowserpersist/nsIWebBrowserPersist.idl
index 8de84f5e2904..62ac1c1cd7bd 100644
--- a/dom/webbrowserpersist/nsIWebBrowserPersist.idl
+++ b/dom/webbrowserpersist/nsIWebBrowserPersist.idl
@@ -13,11 +13,12 @@ interface nsIWebProgressListener;
 interface nsIFile;
 interface nsIChannel;
 interface nsILoadContext;
+interface nsIPrincipal;
 
 /**
  * Interface for persisting DOM documents and URIs to local or remote storage.
  */
-[scriptable, uuid(8cd752a4-60b1-42c3-a819-65c7a1138a28)]
+[scriptable, uuid(ccdbc750-be09-4f11-bb01-4e0a4db76c41)]
 interface nsIWebBrowserPersist : nsICancelable
 {
   /** No special persistence behaviour. */
@@ -112,6 +113,12 @@ interface nsIWebBrowserPersist : nsICancelable
   attribute nsIWebProgressListener progressListener;
 
   /**
+   * This attribute can be used to set the loading principal
+   * of the document or URI to be persisted.
+   */
+  attribute nsIPrincipal loadingPrincipal;
+
+  /**
    * Save the specified URI to file.
    *
    * @param aURI       URI to save to file. Some implementations of this interface
diff --git a/dom/webbrowserpersist/nsWebBrowserPersist.cpp b/dom/webbrowserpersist/nsWebBrowserPersist.cpp
index fd6d9d0d9315..4f49b63475f3 100644
--- a/dom/webbrowserpersist/nsWebBrowserPersist.cpp
+++ b/dom/webbrowserpersist/nsWebBrowserPersist.cpp
@@ -277,6 +277,7 @@ const char *kWebBrowserPersistStringBundle =
 nsWebBrowserPersist::nsWebBrowserPersist() :
     mCurrentDataPathIsRelative(false),
     mCurrentThingsToPersist(0),
+    mLoadingPrincipal(nsContentUtils::GetSystemPrincipal()),
     mFirstAndOnlyUse(true),
     mSavingDocument(false),
     mCancel(false),
@@ -413,6 +414,19 @@ NS_IMETHODIMP nsWebBrowserPersist::SetProgressListener(
     return NS_OK;
 }
 
+NS_IMETHODIMP nsWebBrowserPersist::GetLoadingPrincipal(nsIPrincipal** loadingPrincipal)
+{
+    *loadingPrincipal = mLoadingPrincipal;
+    return NS_OK;
+}
+
+NS_IMETHODIMP nsWebBrowserPersist::SetLoadingPrincipal(nsIPrincipal* loadingPrincipal)
+{
+    mLoadingPrincipal = loadingPrincipal ? loadingPrincipal :
+        nsContentUtils::GetSystemPrincipal();
+    return NS_OK;
+}
+
 NS_IMETHODIMP nsWebBrowserPersist::SaveURI(
     nsIURI *aURI, nsISupports *aCacheKey,
     nsIURI *aReferrer, uint32_t aReferrerPolicy,
@@ -1385,7 +1399,7 @@ nsresult nsWebBrowserPersist::SaveURIInternal(
     nsCOMPtr<nsIChannel> inputChannel;
     rv = NS_NewChannel(getter_AddRefs(inputChannel),
                        aURI,
-                       nsContentUtils::GetSystemPrincipal(),
+                       mLoadingPrincipal,
                        nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                        nsIContentPolicy::TYPE_OTHER,
                        nullptr,  // aPerformanceStorage
@@ -2756,7 +2770,7 @@ nsWebBrowserPersist::CreateChannelFromURI(nsIURI *aURI, nsIChannel **aChannel)
 
     rv = NS_NewChannel(aChannel,
                        aURI,
-                       nsContentUtils::GetSystemPrincipal(),
+                       mLoadingPrincipal,
                        nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                        nsIContentPolicy::TYPE_OTHER);
     NS_ENSURE_SUCCESS(rv, rv);
diff --git a/dom/webbrowserpersist/nsWebBrowserPersist.h b/dom/webbrowserpersist/nsWebBrowserPersist.h
index 17b570d783e7..f95300be12cb 100644
--- a/dom/webbrowserpersist/nsWebBrowserPersist.h
+++ b/dom/webbrowserpersist/nsWebBrowserPersist.h
@@ -147,6 +147,8 @@ private:
     nsCOMPtr<nsIMIMEService>  mMIMEService;
     nsCOMPtr<nsIURI>          mURI;
     nsCOMPtr<nsIWebProgressListener> mProgressListener;
+    nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+
     /**
      * Progress listener for 64-bit values; this is the same object as
      * mProgressListener, but is a member to avoid having to qi it for each
diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js
index d081fde9b20e..51fe1422acc5 100644
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -886,10 +886,10 @@ var BrowserApp = {
             if (!permissionGranted) {
                 return;
             }
-
+            let doc = aTarget.ownerDocument;
             ContentAreaUtils.saveImageURL(aTarget.currentRequestFinalURI.spec, null, "SaveImageTitle",
-                                          false, true, aTarget.ownerDocument.documentURIObject,
-                                          aTarget.ownerDocument);
+                                          false, true, doc.documentURIObject,
+                                          null, null, null, doc.isPrivate, doc.nodePrincipal);
         });
       });
 
diff --git a/netwerk/base/LoadContextInfo.cpp b/netwerk/base/LoadContextInfo.cpp
index 79f870e8d20d..1218345b63ed 100644
--- a/netwerk/base/LoadContextInfo.cpp
+++ b/netwerk/base/LoadContextInfo.cpp
@@ -121,8 +121,6 @@ GetLoadContextInfo(nsIChannel * aChannel)
 {
   nsresult rv;
 
-  DebugOnly<bool> pb = NS_UsePrivateBrowsing(aChannel);
-
   bool anon = false;
   nsLoadFlags loadFlags;
   rv = aChannel->GetLoadFlags(&loadFlags);
@@ -132,7 +130,21 @@ GetLoadContextInfo(nsIChannel * aChannel)
 
   OriginAttributes oa;
   NS_GetOriginAttributes(aChannel, oa);
-  MOZ_ASSERT(pb == (oa.mPrivateBrowsingId > 0));
+
+#ifdef DEBUG
+  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+  if (loadInfo) {
+    nsCOMPtr<nsIPrincipal> principal = loadInfo->LoadingPrincipal();
+    if (principal) {
+      bool chrome;
+      principal->GetIsSystemPrincipal(&chrome);
+      if (!chrome) {
+        bool pb = NS_UsePrivateBrowsing(aChannel);
+        MOZ_ASSERT(pb == (oa.mPrivateBrowsingId > 0));
+      }
+    }
+  }
+#endif
 
   return new LoadContextInfo(anon, oa);
 }
diff --git a/toolkit/components/browser/nsWebBrowser.cpp b/toolkit/components/browser/nsWebBrowser.cpp
index 40ac82210502..ff1e728243b4 100644
--- a/toolkit/components/browser/nsWebBrowser.cpp
+++ b/toolkit/components/browser/nsWebBrowser.cpp
@@ -997,6 +997,18 @@ nsWebBrowser::SetProgressListener(nsIWebProgressListener* aProgressListener)
 }
 
 NS_IMETHODIMP
+nsWebBrowser::GetLoadingPrincipal(nsIPrincipal** loadingPrincipal)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetLoadingPrincipal(nsIPrincipal* loadingPrincipal)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
 nsWebBrowser::SaveURI(nsIURI* aURI,
                       nsISupports* aCacheKey,
                       nsIURI* aReferrer,
diff --git a/toolkit/content/contentAreaUtils.js b/toolkit/content/contentAreaUtils.js
index 48cf448798a5..e213a0e4333e 100644
--- a/toolkit/content/contentAreaUtils.js
+++ b/toolkit/content/contentAreaUtils.js
@@ -62,14 +62,14 @@ function forbidCPOW(arg, func, argname) {
 // - A linked document using Alt-click Save Link As...
 //
 function saveURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache,
-                 aSkipPrompt, aReferrer, aSourceDocument, aIsContentWindowPrivate) {
+                 aSkipPrompt, aReferrer, aSourceDocument, aIsContentWindowPrivate,
+                 aContentPrincipal) {
   forbidCPOW(aURL, "saveURL", "aURL");
   forbidCPOW(aReferrer, "saveURL", "aReferrer");
   // Allow aSourceDocument to be a CPOW.
-
   internalSave(aURL, null, aFileName, null, null, aShouldBypassCache,
                aFilePickerTitleKey, null, aReferrer, aSourceDocument,
-               aSkipPrompt, null, aIsContentWindowPrivate);
+               aSkipPrompt, null, aIsContentWindowPrivate, aContentPrincipal);
 }
 
 // Just like saveURL, but will get some info off the image before
@@ -109,10 +109,12 @@ const nsISupportsCString = Ci.nsISupportsCString;
  * @param aIsContentWindowPrivate (bool)
  *        Whether or not the containing window is in private browsing mode.
  *        Does not need to be provided is aDoc is passed.
+ * @param aContentPrincipal [optional]
+ *        The principal to be used for fetching and saving the target URL.
  */
 function saveImageURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache,
                       aSkipPrompt, aReferrer, aDoc, aContentType, aContentDisp,
-                      aIsContentWindowPrivate) {
+                      aIsContentWindowPrivate, aContentPrincipal) {
   forbidCPOW(aURL, "saveImageURL", "aURL");
   forbidCPOW(aReferrer, "saveImageURL", "aReferrer");
 
@@ -156,7 +158,8 @@ function saveImageURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache,
 
   internalSave(aURL, null, aFileName, aContentDisp, aContentType,
                aShouldBypassCache, aFilePickerTitleKey, null, aReferrer,
-               null, aSkipPrompt, null, aIsContentWindowPrivate);
+               null, aSkipPrompt, null, aIsContentWindowPrivate,
+               aContentPrincipal);
 }
 
 // This is like saveDocument, but takes any browser/frame-like element
@@ -170,7 +173,7 @@ function saveBrowser(aBrowser, aSkipPrompt, aOuterWindowID = 0) {
   let stack = Components.stack.caller;
   persistable.startPersistence(aOuterWindowID, {
     onDocumentReady(document) {
-      saveDocument(document, aSkipPrompt);
+      saveDocument(document, aSkipPrompt, aBrowser.contentPrincipal);
     },
     onError(status) {
       throw new Components.Exception("saveBrowser failed asynchronously in startPersistence",
@@ -186,7 +189,9 @@ function saveBrowser(aBrowser, aSkipPrompt, aOuterWindowID = 0) {
 // 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 pre-e10s add-ons.
-function saveDocument(aDocument, aSkipPrompt) {
+//
+// aContentPrincipal is the principal for downloading and saving the document.
+function saveDocument(aDocument, aSkipPrompt, aContentPrincipal) {
   if (!aDocument)
     throw "Must have a document when calling saveDocument";
 
@@ -241,7 +246,7 @@ function saveDocument(aDocument, aSkipPrompt) {
   internalSave(aDocument.documentURI, aDocument, null, contentDisposition,
                aDocument.contentType, false, null, null,
                aDocument.referrer ? makeURI(aDocument.referrer) : null,
-               aDocument, aSkipPrompt, cacheKey);
+               aDocument, aSkipPrompt, cacheKey, undefined, aContentPrincipal);
 }
 
 function DownloadListener(win, transfer) {
@@ -350,11 +355,13 @@ XPCOMUtils.defineConstant(this, "kSaveAsType_Text", kSaveAsType_Text);
  *        This parameter is provided when the aInitiatingDocument is not a
  *        real document object. Stores whether aInitiatingDocument.defaultView
  *        was private or not.
+ * @param aContentPrincipal [optional]
+ *        The principal to be used for fetching and saving the target URL.
  */
 function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition,
                       aContentType, aShouldBypassCache, aFilePickerTitleKey,
                       aChosenData, aReferrer, aInitiatingDocument, aSkipPrompt,
-                      aCacheKey, aIsContentWindowPrivate) {
+                      aCacheKey, aIsContentWindowPrivate, aContentPrincipal) {
   forbidCPOW(aURL, "internalSave", "aURL");
   forbidCPOW(aReferrer, "internalSave", "aReferrer");
   forbidCPOW(aCacheKey, "internalSave", "aCacheKey");
@@ -423,6 +430,7 @@ function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition,
     let nonCPOWDocument =
       aDocument && !Cu.isCrossProcessWrapper(aDocument);
 
+
     let isPrivate = aIsContentWindowPrivate;
     if (isPrivate === undefined) {
       isPrivate = aInitiatingDocument instanceof Ci.nsIDOMDocument
@@ -440,6 +448,7 @@ function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition,
       sourcePostData: nonCPOWDocument ? getPostData(aDocument) : null,
       bypassCache: aShouldBypassCache,
       isPrivate,
+      loadingPrincipal: aContentPrincipal,
     };
 
     // Start the actual save process
@@ -476,10 +485,15 @@ function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition,
  *        If true, the document will always be refetched from the server
  * @param persistArgs.isPrivate
  *        Indicates whether this is taking place in a private browsing context.
+ * @param persistArgs.loadingPrincipal
+ *        The principal assigned to the document being saved.
  */
 function internalPersist(persistArgs) {
   var persist = makeWebBrowserPersist();
 
+  if (["http", "https", "ftp"].includes(persistArgs.sourceURI.scheme)) {
+    persist.loadingPrincipal = persistArgs.loadingPrincipal;
+  }
   // Calculate persist flags.
   const nsIWBP = Ci.nsIWebBrowserPersist;
   const flags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES |



More information about the tbb-commits mailing list