Bug 1319111 - Exposing URI to make security check against on LoadInfo (no LOAD_REPLACE flag). r=bz
☠☠ backed out by 99fec9a1c204 ☠ ☠
authorHonza Bambas <honzab.moz@firemni.cz>
Fri, 27 Jan 2017 19:10:01 +0100
changeset 355162 88c2a0b2dde53837f770d76050f35aaa18c03bbd
parent 355161 3dc6e29a0128ede6af3f325239dfff97d8ec2cef
child 355163 525f7c248f91327a8dfd27f4aad68adf919156f6
push id89654
push userryanvm@gmail.com
push dateThu, 27 Apr 2017 18:42:19 +0000
treeherdermozilla-inbound@88c2a0b2dde5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1319111
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1319111 - Exposing URI to make security check against on LoadInfo (no LOAD_REPLACE flag). r=bz
addon-sdk/source/test/test-xpcom.js
browser/components/about/AboutRedirector.cpp
browser/components/feeds/FeedConverter.js
browser/components/sessionstore/test/browser_parentProcessRestoreHash.js
browser/extensions/pdfjs/content/PdfStreamConverter.jsm
browser/extensions/pocket/content/AboutPocket.jsm
chrome/nsChromeProtocolHandler.cpp
devtools/client/framework/about-devtools-toolbox.js
docshell/base/nsAboutRedirector.cpp
docshell/base/nsDocShell.cpp
dom/file/nsHostObjectProtocolHandler.cpp
dom/html/nsHTMLDocument.cpp
mobile/android/components/AboutRedirector.js
mobile/android/extensions/flyweb/bootstrap.js
netwerk/base/LoadInfo.cpp
netwerk/base/LoadInfo.h
netwerk/base/nsILoadInfo.idl
netwerk/base/nsNetUtil.cpp
netwerk/base/nsNetUtil.h
netwerk/protocol/file/nsFileChannel.cpp
netwerk/protocol/http/HttpBaseChannel.cpp
netwerk/protocol/http/HttpBaseChannel.h
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/res/ExtensionProtocolHandler.h
netwerk/protocol/res/SubstitutingProtocolHandler.cpp
netwerk/protocol/viewsource/nsViewSourceHandler.cpp
netwerk/protocol/wyciwyg/WyciwygChannelParent.cpp
netwerk/streamconv/converters/nsMultiMixedConv.cpp
netwerk/test/unit/test_file_protocol.js
services/sync/Weave.js
testing/mochitest/BrowserTestUtils/content/content-about-page-utils.js
testing/talos/talos/tests/tabswitch/content/tabswitch-content-process.js
toolkit/components/mozprotocol/mozProtocolHandler.js
toolkit/components/passwordmgr/test/browser/browser_hasInsecureLoginForms_streamConverter.js
toolkit/components/places/PageIconProtocolHandler.js
toolkit/components/thumbnails/PageThumbsProtocol.js
toolkit/mozapps/extensions/test/browser/browser_openDialog.js
--- a/addon-sdk/source/test/test-xpcom.js
+++ b/addon-sdk/source/test/test-xpcom.js
@@ -140,16 +140,17 @@ function testRegister(assert, text) {
       newChannel : function(aURI, aLoadInfo) {
         var ios = Cc["@mozilla.org/network/io-service;1"].
                   getService(Ci.nsIIOService);
 
         var uri = ios.newURI("data:text/plain;charset=utf-8," + text);
         var channel = ios.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
 
         channel.originalURI = aURI;
+        aLoadInfo.resultPrincipalURI = aURI;
         return channel;
       },
       getURIFlags: function(aURI) {
         return Ci.nsIAboutModule.ALLOW_SCRIPT;
       }
     })
   });
 
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -167,37 +167,33 @@ AboutRedirector::NewChannel(nsIURI* aURI
         url.AssignASCII(redir.url);
       }
 
       nsCOMPtr<nsIChannel> tempChannel;
       nsCOMPtr<nsIURI> tempURI;
       rv = NS_NewURI(getter_AddRefs(tempURI), url);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      // If tempURI links to an external URI (i.e. something other than
-      // chrome:// or resource://) then set the LOAD_REPLACE flag on the
-      // channel which forces the channel owner to reflect the displayed
-      // URL rather then being the systemPrincipal.
+      // If tempURI links to an internal URI (chrome://, resource://)
+      // then set the result principal URL on the channel's load info.
+      // Otherwise, we leave it null which forces the channel principal
+      // to reflect the displayed URL rather than being the systemPrincipal.
       bool isUIResource = false;
       rv = NS_URIChainHasFlags(tempURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
                                &isUIResource);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      nsLoadFlags loadFlags = isUIResource
-                    ? static_cast<nsLoadFlags>(nsIChannel::LOAD_NORMAL)
-                    : static_cast<nsLoadFlags>(nsIChannel::LOAD_REPLACE);
-
       rv = NS_NewChannelInternal(getter_AddRefs(tempChannel),
                                  tempURI,
-                                 aLoadInfo,
-                                 nullptr, // aLoadGroup
-                                 nullptr, // aCallbacks
-                                 loadFlags);
+                                 aLoadInfo);
       NS_ENSURE_SUCCESS(rv, rv);
 
+      if (isUIResource) {
+        aLoadInfo->SetResultPrincipalURI(aURI);
+      }
       tempChannel->SetOriginalURI(aURI);
 
       NS_ADDREF(*result = tempChannel);
       return rv;
     }
   }
 
   return NS_ERROR_ILLEGAL_VALUE;
--- a/browser/components/feeds/FeedConverter.js
+++ b/browser/components/feeds/FeedConverter.js
@@ -248,16 +248,17 @@ FeedConverter.prototype = {
         // Store the result in the result service so that the display
         // page can access it.
         feedService.addFeedResult(result);
 
         // Now load the actual XUL document.
         let aboutFeedsURI = ios.newURI("about:feeds");
         chromeChannel = ios.newChannelFromURIWithLoadInfo(aboutFeedsURI, loadInfo);
         chromeChannel.originalURI = result.uri;
+        loadInfo.resultPrincipalURI = result.uri;
 
         // carry the origin attributes from the channel that loaded the feed.
         chromeChannel.owner =
           Services.scriptSecurityManager.createCodebasePrincipal(aboutFeedsURI,
                                                                  loadInfo.originAttributes);
       } else {
         chromeChannel = ios.newChannelFromURIWithLoadInfo(result.uri, loadInfo);
       }
@@ -555,20 +556,22 @@ GenericProtocolHandler.prototype = {
     let inner = aUri.QueryInterface(Ci.nsINestedURI).innerURI;
     let channel = Cc["@mozilla.org/network/io-service;1"].
                   getService(Ci.nsIIOService).
                   newChannelFromURIWithLoadInfo(inner, aLoadInfo);
 
     const schemeId = this._getTelemetrySchemeId();
     Services.telemetry.getHistogramById("FEED_PROTOCOL_USAGE").add(schemeId);
 
-    if (channel instanceof Components.interfaces.nsIHttpChannel)
+    if (channel instanceof Components.interfaces.nsIHttpChannel) {
       // Set this so we know this is supposed to be a feed
       channel.setRequestHeader("X-Moz-Is-Feed", "1", false);
+    }
     channel.originalURI = aUri;
+    aLoadInfo.resultPrincipalURI = aUri;
     return channel;
   },
 
   QueryInterface(iid) {
     if (iid.equals(Ci.nsIProtocolHandler) ||
         iid.equals(Ci.nsISupports))
       return this;
     throw Cr.NS_ERROR_NO_INTERFACE;
--- a/browser/components/sessionstore/test/browser_parentProcessRestoreHash.js
+++ b/browser/components/sessionstore/test/browser_parentProcessRestoreHash.js
@@ -21,16 +21,17 @@ let TestAboutPage = {
   },
 
   newChannel(aURI, aLoadInfo) {
     // about: page inception!
     let newURI = Services.io.newURI(SELFCHROMEURL);
     let channel = Services.io.newChannelFromURIWithLoadInfo(newURI,
                                                             aLoadInfo);
     channel.originalURI = aURI;
+    aLoadInfo.resultPrincipalURI = aURI;
     return channel;
   },
 
   createInstance(outer, iid) {
     if (outer != null) {
       throw Cr.NS_ERROR_NO_AGGREGATION;
     }
     return this.QueryInterface(iid);
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -998,16 +998,17 @@ PdfStreamConverter.prototype = {
             domWindow.frameElement.className === "previewPluginContentFrame";
           PdfJsTelemetry.onEmbed(isObjectEmbed);
         }
       }
     };
 
     // Keep the URL the same so the browser sees it as the same.
     channel.originalURI = aRequest.URI;
+    channel.loadInfo.resultPrincipalURI = aRequest.loadInfo.resultPrincipalURI;
     channel.loadGroup = aRequest.loadGroup;
     channel.loadInfo.originAttributes = aRequest.loadInfo.originAttributes;
 
     // We can use the resource principal when data is fetched by the chrome,
     // e.g. useful for NoScript. Make make sure we reuse the origin attributes
     // from the request channel to keep isolation consistent.
     var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
                 .getService(Ci.nsIScriptSecurityManager);
--- a/browser/extensions/pocket/content/AboutPocket.jsm
+++ b/browser/extensions/pocket/content/AboutPocket.jsm
@@ -35,16 +35,17 @@ AboutPage.prototype = {
     return this.uriFlags;
   },
 
   newChannel(aURI, aLoadInfo) {
     let newURI = Services.io.newURI(this.chromeURL);
     let channel = Services.io.newChannelFromURIWithLoadInfo(newURI,
                                                             aLoadInfo);
     channel.originalURI = aURI;
+    aLoadInfo.resultPrincipalURI = aURI;
 
     if (this.uriFlags & Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT) {
       let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(aURI);
       channel.owner = principal;
     }
     return channel;
   },
 
--- a/chrome/nsChromeProtocolHandler.cpp
+++ b/chrome/nsChromeProtocolHandler.cpp
@@ -163,22 +163,21 @@ nsChromeProtocolHandler::NewChannel2(nsI
             file->GetNativePath(path);
             printf("Chrome file doesn't exist: %s\n", path.get());
         }
     }
 #endif
 
     // Make sure that the channel remembers where it was
     // originally loaded from.
-    nsLoadFlags loadFlags = 0;
-    result->GetLoadFlags(&loadFlags);
-    result->SetLoadFlags(loadFlags & ~nsIChannel::LOAD_REPLACE);
     rv = result->SetOriginalURI(aURI);
     if (NS_FAILED(rv)) return rv;
 
+    aLoadInfo->SetResultPrincipalURI(aURI);
+
     // Get a system principal for content files and set the owner
     // property of the result
     nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
     nsAutoCString path;
     rv = url->GetPath(path);
     if (StringBeginsWith(path, NS_LITERAL_CSTRING("/content/")))
     {
         nsCOMPtr<nsIScriptSecurityManager> securityManager =
--- a/devtools/client/framework/about-devtools-toolbox.js
+++ b/devtools/client/framework/about-devtools-toolbox.js
@@ -21,16 +21,20 @@ AboutURL.prototype = {
   classID: components.ID("11342911-3135-45a8-8d71-737a2b0ad469"),
   contractID: "@mozilla.org/network/protocol/about;1?what=devtools-toolbox",
 
   QueryInterface: XPCOMUtils.generateQI([nsIAboutModule]),
 
   newChannel: function (aURI, aLoadInfo) {
     let chan = Services.io.newChannelFromURIWithLoadInfo(this.uri, aLoadInfo);
     chan.owner = Services.scriptSecurityManager.getSystemPrincipal();
+
+    // Must set the result principal URI _after_ we've created the channel
+    // since the chrome protocol would overwrite it with a chrome:// URL.
+    aLoadInfo.resultPrincipalURI = aURI;
     return chan;
   },
 
   getURIFlags: function (aURI) {
     return nsIAboutModule.ALLOW_SCRIPT || nsIAboutModule.ENABLE_INDEXED_DB;
   }
 };
 
--- a/docshell/base/nsAboutRedirector.cpp
+++ b/docshell/base/nsAboutRedirector.cpp
@@ -169,39 +169,35 @@ nsAboutRedirector::NewChannel(nsIURI* aU
 
   for (int i = 0; i < kRedirTotal; i++) {
     if (!strcmp(path.get(), kRedirMap[i].id)) {
       nsCOMPtr<nsIChannel> tempChannel;
       nsCOMPtr<nsIURI> tempURI;
       rv = NS_NewURI(getter_AddRefs(tempURI), kRedirMap[i].url);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      // If tempURI links to an external URI (i.e. something other than
-      // chrome:// or resource://) then set the LOAD_REPLACE flag on the
-      // channel which forces the channel owner to reflect the displayed
-      // URL rather then being the systemPrincipal.
+      // If tempURI links to an internal URI (chrome://, resource://, about:)
+      // then set the result principal URL on the channel's load info.
+      // Otherwise, we leave it null which forces the channel principal
+      // to reflect the displayed URL rather than being the systemPrincipal.
       bool isUIResource = false;
       rv = NS_URIChainHasFlags(tempURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
                                &isUIResource);
       NS_ENSURE_SUCCESS(rv, rv);
 
       bool isAboutBlank = NS_IsAboutBlank(tempURI);
 
-      nsLoadFlags loadFlags = isUIResource || isAboutBlank
-                    ? static_cast<nsLoadFlags>(nsIChannel::LOAD_NORMAL)
-                    : static_cast<nsLoadFlags>(nsIChannel::LOAD_REPLACE);
-
       rv = NS_NewChannelInternal(getter_AddRefs(tempChannel),
                                  tempURI,
-                                 aLoadInfo,
-                                 nullptr, // aLoadGroup
-                                 nullptr, // aCallbacks
-                                 loadFlags);
+                                 aLoadInfo);
       NS_ENSURE_SUCCESS(rv, rv);
 
+      if (isUIResource || isAboutBlank) {
+        aLoadInfo->SetResultPrincipalURI(aURI);
+      }
       tempChannel->SetOriginalURI(aURI);
 
       tempChannel.forget(aResult);
       return rv;
     }
   }
 
   NS_ERROR("nsAboutRedirector called for unknown case");
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -11163,16 +11163,22 @@ nsDocShell::DoURILoad(nsIURI* aURI,
   // Make sure to give the caller a channel if we managed to create one
   // This is important for correct error page/session history interaction
   if (aRequest) {
     NS_ADDREF(*aRequest = channel);
   }
 
   if (aOriginalURI) {
     channel->SetOriginalURI(aOriginalURI);
+    // The LOAD_REPLACE flag and its handling here will be removed as part
+    // of bug 1319110.  For now preserve its restoration here to not break
+    // any code expecting it being set specially on redirected channels.
+    // If the flag has originally been set to change result of
+    // NS_GetFinalChannelURI it won't have any effect and also won't cause
+    // any harm.
     if (aLoadReplace) {
       uint32_t loadFlags;
       channel->GetLoadFlags(&loadFlags);
       NS_ENSURE_SUCCESS(rv, rv);
       channel->SetLoadFlags(loadFlags | nsIChannel::LOAD_REPLACE);
     }
   } else {
     channel->SetOriginalURI(aURI);
--- a/dom/file/nsHostObjectProtocolHandler.cpp
+++ b/dom/file/nsHostObjectProtocolHandler.cpp
@@ -859,16 +859,18 @@ nsHostObjectProtocolHandler::NewChannel2
   }
 
   uint64_t size = blobImpl->GetSize(rv);
   if (NS_WARN_IF(rv.Failed())) {
     return rv.StealNSResult();
   }
 
   channel->SetOriginalURI(uri);
+  aLoadInfo->SetResultPrincipalURI(uri);
+
   channel->SetContentType(NS_ConvertUTF16toUTF8(contentType));
   channel->SetContentLength(size);
 
   channel.forget(result);
 
   return NS_OK;
 }
 
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -2407,16 +2407,17 @@ nsHTMLDocument::CreateAndAddWyciwygChann
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsLoadFlags loadFlags = 0;
     channel->GetLoadFlags(&loadFlags);
     loadFlags |= nsIChannel::LOAD_DOCUMENT_URI;
     channel->SetLoadFlags(loadFlags);
 
     channel->SetOriginalURI(wcwgURI);
+    loadInfo->SetResultPrincipalURI(wcwgURI);
 
     rv = loadGroup->AddRequest(mWyciwygChannel, nullptr);
     NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to add request to load group.");
   }
 
   return rv;
 }
 
--- a/mobile/android/components/AboutRedirector.js
+++ b/mobile/android/components/AboutRedirector.js
@@ -118,15 +118,16 @@ AboutRedirector.prototype = {
     if (!moduleInfo.privileged) {
       // Setting the owner to null means that we'll go through the normal
       // path in GetChannelPrincipal and create a codebase principal based
       // on the channel's originalURI
       channel.owner = null;
     }
 
     channel.originalURI = aURI;
+    aLoadInfo.resultPrincipalURI = aURI;
 
     return channel;
   }
 };
 
 const components = [AboutRedirector];
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
--- a/mobile/android/extensions/flyweb/bootstrap.js
+++ b/mobile/android/extensions/flyweb/bootstrap.js
@@ -33,16 +33,17 @@ AboutFlyWeb.prototype = Object.freeze({
   getURIFlags: function(aURI) {
     return Ci.nsIAboutModule.ALLOW_SCRIPT;
   },
 
   newChannel: function(aURI, aLoadInfo) {
     let uri = Services.io.newURI("chrome://flyweb/content/aboutFlyWeb.xhtml");
     let channel = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
     channel.originalURI = aURI;
+    aLoadInfo.resultPrincipalURI = aURI;
     return channel;
   }
 });
 
 function Factory(component) {
   this.createInstance = function(outer, iid) {
     if (outer) {
       throw Cr.NS_ERROR_NO_AGGREGATION;
--- a/netwerk/base/LoadInfo.cpp
+++ b/netwerk/base/LoadInfo.cpp
@@ -264,16 +264,17 @@ LoadInfo::LoadInfo(nsPIDOMWindowOuter* a
 #endif
 }
 
 LoadInfo::LoadInfo(const LoadInfo& rhs)
   : mLoadingPrincipal(rhs.mLoadingPrincipal)
   , mTriggeringPrincipal(rhs.mTriggeringPrincipal)
   , mPrincipalToInherit(rhs.mPrincipalToInherit)
   , mSandboxedLoadingPrincipal(rhs.mSandboxedLoadingPrincipal)
+  , mResultPrincipalURI(rhs.mResultPrincipalURI)
   , mLoadingContext(rhs.mLoadingContext)
   , mSecurityFlags(rhs.mSecurityFlags)
   , mInternalContentPolicyType(rhs.mInternalContentPolicyType)
   , mTainting(rhs.mTainting)
   , mUpgradeInsecureRequests(rhs.mUpgradeInsecureRequests)
   , mVerifySignedContent(rhs.mVerifySignedContent)
   , mEnforceSRI(rhs.mEnforceSRI)
   , mForceInheritPrincipalDropped(rhs.mForceInheritPrincipalDropped)
@@ -931,10 +932,24 @@ LoadInfo::MaybeIncreaseTainting(uint32_t
 NS_IMETHODIMP
 LoadInfo::GetIsTopLevelLoad(bool *aResult)
 {
   *aResult = mFrameOuterWindowID ? mFrameOuterWindowID == mOuterWindowID
                                  : mParentOuterWindowID == mOuterWindowID;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+LoadInfo::GetResultPrincipalURI(nsIURI **aURI)
+{
+  NS_IF_ADDREF(*aURI = mResultPrincipalURI);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetResultPrincipalURI(nsIURI *aURI)
+{
+  mResultPrincipalURI = aURI;
+  return NS_OK;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/base/LoadInfo.h
+++ b/netwerk/base/LoadInfo.h
@@ -123,16 +123,17 @@ private:
   void SetIncludeCookiesSecFlag();
   friend class mozilla::dom::XMLHttpRequestMainThread;
 
   // if you add a member, please also update the copy constructor
   nsCOMPtr<nsIPrincipal>           mLoadingPrincipal;
   nsCOMPtr<nsIPrincipal>           mTriggeringPrincipal;
   nsCOMPtr<nsIPrincipal>           mPrincipalToInherit;
   nsCOMPtr<nsIPrincipal>           mSandboxedLoadingPrincipal;
+  nsCOMPtr<nsIURI>                 mResultPrincipalURI;
   nsWeakPtr                        mLoadingContext;
   nsSecurityFlags                  mSecurityFlags;
   nsContentPolicyType              mInternalContentPolicyType;
   LoadTainting                     mTainting;
   bool                             mUpgradeInsecureRequests;
   bool                             mVerifySignedContent;
   bool                             mEnforceSRI;
   bool                             mForceInheritPrincipalDropped;
--- a/netwerk/base/nsILoadInfo.idl
+++ b/netwerk/base/nsILoadInfo.idl
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 #include "nsIContentPolicy.idl"
 
 interface nsIDOMDocument;
 interface nsINode;
 interface nsIPrincipal;
+interface nsIURI;
 
 %{C++
 #include "nsTArray.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/LoadTainting.h"
 
 class nsCString;
 %}
@@ -736,15 +737,23 @@ interface nsILoadInfo : nsISupports
 
   /**
    * Returns true if this load is for top level document.
    * Note that the load for a sub-frame's document will return false here.
    */
   [infallible] readonly attribute boolean isTopLevelLoad;
 
   /**
+   * If this is non-null, this property represents two things: (1) the
+   * URI to be used for the principal if the channel with this loadinfo
+   * gets a principal based on URI and (2) the URI to use for a document
+   * created from the channel with this loadinfo.
+   */
+  attribute nsIURI resultPrincipalURI;
+
+  /**
    * Returns the null principal of the resulting resource if the SEC_SANDBOXED
    * flag is set.  Otherwise returns null.  This is used by
    * GetChannelResultPrincipal() to ensure that the same null principal object
    * is returned every time.
    */
   [noscript] readonly attribute nsIPrincipal sandboxedLoadingPrincipal;
 };
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -184,23 +184,31 @@ NS_NewChannelInternal(nsIChannel        
   }
 
   if (aCallbacks) {
     rv = channel->SetNotificationCallbacks(aCallbacks);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
-    // Retain the LOAD_REPLACE load flag if set.
-    nsLoadFlags normalLoadFlags = 0;
-    channel->GetLoadFlags(&normalLoadFlags);
-    rv = channel->SetLoadFlags(aLoadFlags | (normalLoadFlags & nsIChannel::LOAD_REPLACE));
+    rv = channel->SetLoadFlags(aLoadFlags);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+#ifdef DEBUG
+  nsLoadFlags channelLoadFlags = 0;
+  channel->GetLoadFlags(&channelLoadFlags);
+  // Will be removed when we remove LOAD_REPLACE altogether
+  // This check is trying to catch protocol handlers that still
+  // try to set the LOAD_REPLACE flag.  Only exception is when
+  // this flag is carried in the aLoadFlags argument (e.g. when
+  // cloning redirected channels for CORS preflight.)
+  MOZ_ASSERT(!((channelLoadFlags & ~aLoadFlags) & nsIChannel::LOAD_REPLACE));
+#endif
+
   channel.forget(outChannel);
   return NS_OK;
 }
 
 nsresult
 NS_NewChannel(nsIChannel           **outChannel,
               nsIURI                *aUri,
               nsIPrincipal          *aLoadingPrincipal,
@@ -263,23 +271,31 @@ NS_NewChannelInternal(nsIChannel        
   }
 
   if (aCallbacks) {
     rv = channel->SetNotificationCallbacks(aCallbacks);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
-    // Retain the LOAD_REPLACE load flag if set.
-    nsLoadFlags normalLoadFlags = 0;
-    channel->GetLoadFlags(&normalLoadFlags);
-    rv = channel->SetLoadFlags(aLoadFlags | (normalLoadFlags & nsIChannel::LOAD_REPLACE));
+    rv = channel->SetLoadFlags(aLoadFlags);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+#ifdef DEBUG
+  nsLoadFlags channelLoadFlags = 0;
+  channel->GetLoadFlags(&channelLoadFlags);
+  // Will be removed when we remove LOAD_REPLACE altogether
+  // This check is trying to catch protocol handlers that still
+  // try to set the LOAD_REPLACE flag.  Only exception is when
+  // this flag is carried in the aLoadFlags argument (e.g. when
+  // cloning redirected channels for CORS preflight.)
+  MOZ_ASSERT(!((channelLoadFlags & ~aLoadFlags) & nsIChannel::LOAD_REPLACE));
+#endif
+
   channel.forget(outChannel);
   return NS_OK;
 }
 
 nsresult /*NS_NewChannelWithNodeAndTriggeringPrincipal */
 NS_NewChannelWithTriggeringPrincipal(nsIChannel           **outChannel,
                                      nsIURI                *aUri,
                                      nsINode               *aLoadingNode,
@@ -1881,25 +1897,28 @@ NS_GetInnermostURI(nsIURI *aURI)
 
     return uri.forget();
 }
 
 nsresult
 NS_GetFinalChannelURI(nsIChannel *channel, nsIURI **uri)
 {
     *uri = nullptr;
-    nsLoadFlags loadFlags = 0;
-    nsresult rv = channel->GetLoadFlags(&loadFlags);
-    NS_ENSURE_SUCCESS(rv, rv);
 
-    if (loadFlags & nsIChannel::LOAD_REPLACE) {
-        return channel->GetURI(uri);
+    nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+    if (loadInfo) {
+        nsCOMPtr<nsIURI> resultPrincipalURI;
+        loadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
+        if (resultPrincipalURI) {
+            resultPrincipalURI.forget(uri);
+            return NS_OK;
+        }
     }
 
-    return channel->GetOriginalURI(uri);
+    return channel->GetURI(uri);
 }
 
 nsresult
 NS_URIChainHasFlags(nsIURI   *uri,
                     uint32_t  flags,
                     bool     *result)
 {
     nsresult rv;
--- a/netwerk/base/nsNetUtil.h
+++ b/netwerk/base/nsNetUtil.h
@@ -777,21 +777,19 @@ nsresult NS_URIChainHasFlags(nsIURI   *u
 
 /**
  * Helper function for getting the innermost URI for a given URI.  The return
  * value could be just the object passed in if it's not a nested URI.
  */
 already_AddRefed<nsIURI> NS_GetInnermostURI(nsIURI *aURI);
 
 /**
- * Get the "final" URI for a channel.  This is either the same as GetURI or
- * GetOriginalURI, depending on whether this channel has
- * nsIChanel::LOAD_REPLACE set.  For channels without that flag set, the final
- * URI is the original URI, while for ones with the flag the final URI is the
- * channel URI.
+ * Get the "final" URI for a channel.  This is either channel's load info
+ * resultPrincipalURI, if set, or GetURI.  In most cases (but not all) load
+ * info resultPrincipalURI, if set, corresponds to originalURI of the channel.
  */
 nsresult NS_GetFinalChannelURI(nsIChannel *channel, nsIURI **uri);
 
 // NS_SecurityHashURI must return the same hash value for any two URIs that
 // compare equal according to NS_SecurityCompareURIs.  Unfortunately, in the
 // case of files, it's not clear we can do anything better than returning
 // the schemeHash, so hashing files degenerates to storing them in a list.
 uint32_t NS_SecurityHashURI(nsIURI *aURI);
--- a/netwerk/protocol/file/nsFileChannel.cpp
+++ b/netwerk/protocol/file/nsFileChannel.cpp
@@ -285,19 +285,16 @@ nsFileChannel::nsFileChannel(nsIURI *uri
     nsCOMPtr<nsIURL> targetURL = do_QueryInterface(targetURI);
     nsAutoCString queryString;
     if (origURL && targetURL && NS_SUCCEEDED(origURL->GetQuery(queryString))) {
       targetURL->SetQuery(queryString);
     }
 
     SetURI(targetURI);
     SetOriginalURI(uri);
-    nsLoadFlags loadFlags = 0;
-    GetLoadFlags(&loadFlags);
-    SetLoadFlags(loadFlags | nsIChannel::LOAD_REPLACE);
   } else {
     SetURI(uri);
   }
 }
 
 nsFileChannel::~nsFileChannel()
 {
 }
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -2948,16 +2948,74 @@ void HttpBaseChannel::AssertPrivateBrows
 
   OriginAttributes docShellAttrs;
   loadContext->GetOriginAttributes(docShellAttrs);
   MOZ_ASSERT(mLoadInfo->GetOriginAttributes().mPrivateBrowsingId == docShellAttrs.mPrivateBrowsingId,
              "PrivateBrowsingId values are not the same between LoadInfo and LoadContext.");
 }
 #endif
 
+already_AddRefed<nsILoadInfo>
+HttpBaseChannel::CloneLoadInfoForRedirect(nsIURI * newURI, uint32_t redirectFlags)
+{
+  // make a copy of the loadinfo, append to the redirectchain
+  // this will be set on the newly created channel for the redirect target.
+  if (!mLoadInfo) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsILoadInfo> newLoadInfo =
+    static_cast<mozilla::LoadInfo*>(mLoadInfo.get())->Clone();
+
+  nsContentPolicyType contentPolicyType = mLoadInfo->GetExternalContentPolicyType();
+  if (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT ||
+      contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT) {
+    nsCOMPtr<nsIPrincipal> nullPrincipalToInherit = NullPrincipal::Create();
+    newLoadInfo->SetPrincipalToInherit(nullPrincipalToInherit);
+  }
+
+  // re-compute the origin attributes of the loadInfo if it's top-level load.
+  bool isTopLevelDoc =
+    newLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_DOCUMENT;
+
+  if (isTopLevelDoc) {
+    nsCOMPtr<nsILoadContext> loadContext;
+    NS_QueryNotificationCallbacks(this, loadContext);
+    OriginAttributes docShellAttrs;
+    if (loadContext) {
+      loadContext->GetOriginAttributes(docShellAttrs);
+    }
+
+    OriginAttributes attrs = newLoadInfo->GetOriginAttributes();
+
+    MOZ_ASSERT(docShellAttrs.mUserContextId == attrs.mUserContextId,
+                "docshell and necko should have the same userContextId attribute.");
+    MOZ_ASSERT(docShellAttrs.mInIsolatedMozBrowser == attrs.mInIsolatedMozBrowser,
+                "docshell and necko should have the same inIsolatedMozBrowser attribute.");
+    MOZ_ASSERT(docShellAttrs.mPrivateBrowsingId == attrs.mPrivateBrowsingId,
+                "docshell and necko should have the same privateBrowsingId attribute.");
+
+    attrs = docShellAttrs;
+    attrs.SetFirstPartyDomain(true, newURI);
+    newLoadInfo->SetOriginAttributes(attrs);
+  }
+
+  // Drop the target principal URI from the cloned load info because we want
+  // NS_GetFinalChannelURI to return either the URI of the target channel
+  // or anything that the target protocol handler potentially sets itself.
+  newLoadInfo->SetResultPrincipalURI(nullptr);
+
+  bool isInternalRedirect =
+    (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+                      nsIChannelEventSink::REDIRECT_STS_UPGRADE));
+  newLoadInfo->AppendRedirectedPrincipal(GetURIPrincipal(), isInternalRedirect);
+
+  return newLoadInfo.forget();
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsITraceableChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpBaseChannel::SetNewListener(nsIStreamListener *aListener, nsIStreamListener **_retval)
 {
   LOG(("HttpBaseChannel::SetNewListener [this=%p, mListener=%p, newListener=%p]",
@@ -3136,67 +3194,16 @@ HttpBaseChannel::SetupReplacementChannel
   if (mPrivateBrowsingOverriden) {
     nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel =
       do_QueryInterface(newChannel);
     if (newPBChannel) {
       newPBChannel->SetPrivate(mPrivateBrowsing);
     }
   }
 
-  // make a copy of the loadinfo, append to the redirectchain
-  // and set it on the new channel
-  if (mLoadInfo) {
-    nsCOMPtr<nsILoadInfo> newLoadInfo =
-      static_cast<mozilla::LoadInfo*>(mLoadInfo.get())->Clone();
-
-    nsContentPolicyType contentPolicyType = mLoadInfo->GetExternalContentPolicyType();
-    if (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT ||
-        contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT) {
-      nsCOMPtr<nsIPrincipal> nullPrincipalToInherit = NullPrincipal::Create();
-      newLoadInfo->SetPrincipalToInherit(nullPrincipalToInherit);
-    }
-
-    // re-compute the origin attributes of the loadInfo if it's top-level load.
-    bool isTopLevelDoc =
-      newLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_DOCUMENT;
-
-    if (isTopLevelDoc) {
-      nsCOMPtr<nsILoadContext> loadContext;
-      NS_QueryNotificationCallbacks(this, loadContext);
-      OriginAttributes docShellAttrs;
-      if (loadContext) {
-        loadContext->GetOriginAttributes(docShellAttrs);
-      }
-
-      OriginAttributes attrs = newLoadInfo->GetOriginAttributes();
-
-      MOZ_ASSERT(docShellAttrs.mUserContextId == attrs.mUserContextId,
-                "docshell and necko should have the same userContextId attribute.");
-      MOZ_ASSERT(docShellAttrs.mInIsolatedMozBrowser == attrs.mInIsolatedMozBrowser,
-                "docshell and necko should have the same inIsolatedMozBrowser attribute.");
-      MOZ_ASSERT(docShellAttrs.mPrivateBrowsingId == attrs.mPrivateBrowsingId,
-                 "docshell and necko should have the same privateBrowsingId attribute.");
-
-      attrs = docShellAttrs;
-      attrs.SetFirstPartyDomain(true, newURI);
-      newLoadInfo->SetOriginAttributes(attrs);
-    }
-
-    bool isInternalRedirect =
-      (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
-                        nsIChannelEventSink::REDIRECT_STS_UPGRADE));
-    newLoadInfo->AppendRedirectedPrincipal(GetURIPrincipal(), isInternalRedirect);
-    newChannel->SetLoadInfo(newLoadInfo);
-  }
-  else {
-    // the newChannel was created with a dummy loadInfo, we should clear
-    // it in case the original channel does not have a loadInfo
-    newChannel->SetLoadInfo(nullptr);
-  }
-
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
   if (!httpChannel)
     return NS_OK; // no other options to set
 
   // Preserve the CORS preflight information.
   nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(newChannel);
   if (mRequireCORSPreflight && httpInternal) {
     httpInternal->SetCorsPreflightParameters(mUnsafeHeaders);
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -419,16 +419,18 @@ protected:
   // the new mUploadStream.
   void EnsureUploadStreamIsCloneableComplete(nsresult aStatus);
 
 #ifdef DEBUG
   // Check if mPrivateBrowsingId matches between LoadInfo and LoadContext.
   void AssertPrivateBrowsingId();
 #endif
 
+  already_AddRefed<nsILoadInfo> CloneLoadInfoForRedirect(nsIURI *newURI, uint32_t redirectFlags);
+
   friend class PrivateBrowsingChannel<HttpBaseChannel>;
   friend class InterceptFailedOnStop;
 
 protected:
   // this section is for main-thread-only object
   // all the references need to be proxy released on main thread.
   nsCOMPtr<nsIURI> mURI;
   nsCOMPtr<nsIURI> mOriginalURI;
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -1488,19 +1488,20 @@ HttpChannelChild::SetupRedirect(nsIURI* 
   LOG(("HttpChannelChild::SetupRedirect [this=%p]\n", this));
 
   nsresult rv;
   nsCOMPtr<nsIIOService> ioService;
   rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIChannel> newChannel;
+  nsCOMPtr<nsILoadInfo> redirectLoadInfo = CloneLoadInfoForRedirect(uri, redirectFlags);
   rv = NS_NewChannelInternal(getter_AddRefs(newChannel),
                              uri,
-                             mLoadInfo,
+                             redirectLoadInfo,
                              nullptr, // aLoadGroup
                              nullptr, // aCallbacks
                              nsIRequest::LOAD_NORMAL,
                              ioService);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // We won't get OnStartRequest, set cookies here.
   mResponseHead = new nsHttpResponseHead(*responseHead);
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -2612,24 +2612,26 @@ nsHttpChannel::HandleAsyncAPIRedirect()
 
 nsresult
 nsHttpChannel::StartRedirectChannelToURI(nsIURI *upgradedURI, uint32_t flags)
 {
     nsresult rv = NS_OK;
     LOG(("nsHttpChannel::StartRedirectChannelToURI()\n"));
 
     nsCOMPtr<nsIChannel> newChannel;
+    nsCOMPtr<nsILoadInfo> redirectLoadInfo = CloneLoadInfoForRedirect(upgradedURI,
+                                                                      flags);
 
     nsCOMPtr<nsIIOService> ioService;
     rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = NS_NewChannelInternal(getter_AddRefs(newChannel),
                                upgradedURI,
-                               mLoadInfo,
+                               redirectLoadInfo,
                                nullptr, // aLoadGroup
                                nullptr, // aCallbacks
                                nsIRequest::LOAD_NORMAL,
                                ioService);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = SetupReplacementChannel(upgradedURI, newChannel, true, flags);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -5469,32 +5471,33 @@ nsHttpChannel::ContinueProcessRedirectio
         rv = PromptTempRedirect();
         if (NS_FAILED(rv)) return rv;
     }
 
     nsCOMPtr<nsIIOService> ioService;
     rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
     if (NS_FAILED(rv)) return rv;
 
+    uint32_t redirectFlags;
+    if (nsHttp::IsPermanentRedirect(mRedirectType))
+        redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
+    else
+        redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
+
     nsCOMPtr<nsIChannel> newChannel;
+    nsCOMPtr<nsILoadInfo> redirectLoadInfo = CloneLoadInfoForRedirect(mRedirectURI, redirectFlags);
     rv = NS_NewChannelInternal(getter_AddRefs(newChannel),
                                mRedirectURI,
-                               mLoadInfo,
+                               redirectLoadInfo,
                                nullptr, // aLoadGroup
                                nullptr, // aCallbacks
                                nsIRequest::LOAD_NORMAL,
                                ioService);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    uint32_t redirectFlags;
-    if (nsHttp::IsPermanentRedirect(mRedirectType))
-        redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
-    else
-        redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
-
     rv = SetupReplacementChannel(mRedirectURI, newChannel,
                                  !rewriteToGET, redirectFlags);
     if (NS_FAILED(rv)) return rv;
 
     // verify that this is a legal redirect
     mRedirectChannel = newChannel;
 
     PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
--- a/netwerk/protocol/res/ExtensionProtocolHandler.h
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.h
@@ -28,16 +28,20 @@ public:
 protected:
   ~ExtensionProtocolHandler() {}
 
   MOZ_MUST_USE bool ResolveSpecialCases(const nsACString& aHost,
                                         const nsACString& aPath,
                                         const nsACString& aPathname,
                                         nsACString& aResult) override;
 
+  // |result| is an inout param.  On entry to this function, *result
+  // is expected to be non-null and already addrefed.  This function
+  // may release the object stored in *result on entry and write
+  // a new pointer to an already addrefed channel to *result.
   virtual MOZ_MUST_USE nsresult SubstituteChannel(nsIURI* uri,
                                                   nsILoadInfo* aLoadInfo,
                                                   nsIChannel** result) override;
 };
 
 } // namespace net
 } // namespace mozilla
 
--- a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
@@ -252,22 +252,31 @@ SubstitutingProtocolHandler::NewChannel2
 
   nsCOMPtr<nsIURI> newURI;
   rv = NS_NewURI(getter_AddRefs(newURI), spec);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = NS_NewChannelInternal(result, newURI, aLoadInfo);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsLoadFlags loadFlags = 0;
-  (*result)->GetLoadFlags(&loadFlags);
-  (*result)->SetLoadFlags(loadFlags & ~nsIChannel::LOAD_REPLACE);
   rv = (*result)->SetOriginalURI(uri);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // We must set result principal URL prior to calling SubstituteChannel
+  // because it may call NS_GetFinalChannelURI and the correct result
+  // (in this case |uri|) is already expected.
+  //
+  // We don't want to reset the result principal URL to |uri| after calling
+  // SubstituteChannel since whatever substituting protocol handler(s) set
+  // has to override anything we set here.
+  if (aLoadInfo) {
+    rv = aLoadInfo->SetResultPrincipalURI(uri);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   return SubstituteChannel(uri, aLoadInfo, result);
 }
 
 nsresult
 SubstitutingProtocolHandler::NewChannel(nsIURI* uri, nsIChannel* *result)
 {
   return NewChannel2(uri, nullptr, result);
 }
--- a/netwerk/protocol/viewsource/nsViewSourceHandler.cpp
+++ b/netwerk/protocol/viewsource/nsViewSourceHandler.cpp
@@ -111,16 +111,22 @@ nsViewSourceHandler::NewChannel2(nsIURI*
 
     // set the loadInfo on the new channel
     rv = channel->SetLoadInfo(aLoadInfo);
     if (NS_FAILED(rv)) {
         NS_RELEASE(channel);
         return rv;
     }
 
+    if (aLoadInfo) {
+        // The underlying channel is created for a different URI.
+        // Original URI on the resulting channel has been set to |uri|.
+        aLoadInfo->SetResultPrincipalURI(uri);
+    }
+
     *result = static_cast<nsIViewSourceChannel*>(channel);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsViewSourceHandler::NewChannel(nsIURI* uri, nsIChannel* *result)
 {
     return NewChannel2(uri, nullptr, result);
@@ -136,16 +142,22 @@ nsViewSourceHandler::NewSrcdocChannel(ns
     NS_ENSURE_ARG_POINTER(aURI);
     RefPtr<nsViewSourceChannel> channel = new nsViewSourceChannel();
 
     nsresult rv = channel->InitSrcdoc(aURI, aBaseURI, aSrcdoc, aLoadInfo);
     if (NS_FAILED(rv)) {
         return rv;
     }
 
+    if (aLoadInfo) {
+        // The underlying channel is created for a different URI.
+        // Original URI on the resulting channel has been set to |aURI|.
+        aLoadInfo->SetResultPrincipalURI(aURI);
+    }
+
     *outChannel = static_cast<nsIViewSourceChannel*>(channel.forget().take());
     return NS_OK;
 }
 
 NS_IMETHODIMP 
 nsViewSourceHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
 {
     // don't override anything.  
--- a/netwerk/protocol/wyciwyg/WyciwygChannelParent.cpp
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelParent.cpp
@@ -212,16 +212,21 @@ WyciwygChannelParent::RecvAsyncOpen(cons
   rv = mChannel->SetOriginalURI(original);
   if (NS_FAILED(rv)) {
     if (!SendCancelEarly(rv)) {
       return IPC_FAIL_NO_REASON(this);
     }
     return IPC_OK();
   }
 
+  nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+  if (loadInfo) {
+    loadInfo->SetResultPrincipalURI(original);
+  }
+
   rv = mChannel->SetLoadFlags(aLoadFlags);
   if (NS_FAILED(rv)) {
     if (!SendCancelEarly(rv)) {
       return IPC_FAIL_NO_REASON(this);
     }
     return IPC_OK();
   }
 
@@ -232,17 +237,16 @@ WyciwygChannelParent::RecvAsyncOpen(cons
   rv = mChannel->SetNotificationCallbacks(this);
   if (NS_FAILED(rv)) {
     if (!SendCancelEarly(rv)) {
       return IPC_FAIL_NO_REASON(this);
     }
     return IPC_OK();
   }
 
-  nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
   if (loadInfo && loadInfo->GetEnforceSecurity()) {
     rv = mChannel->AsyncOpen2(this);
   }
   else {
     rv = mChannel->AsyncOpen(this, nullptr);
   }
 
   if (NS_FAILED(rv)) {
--- a/netwerk/streamconv/converters/nsMultiMixedConv.cpp
+++ b/netwerk/streamconv/converters/nsMultiMixedConv.cpp
@@ -872,16 +872,20 @@ nsMultiMixedConv::SendStart()
     rv = mPartChannel->SetContentType(mContentType);
     if (NS_FAILED(rv)) return rv;
 
     rv = mPartChannel->SetContentLength(mContentLength);
     if (NS_FAILED(rv)) return rv;
 
     mPartChannel->SetContentDisposition(mContentDisposition);
 
+    // Each part of a multipart/replace response can be used
+    // for the top level document.  We must inform upper layers
+    // about this by setting the LOAD_REPLACE flag so that certain
+    // state assertions are evaluated as positive.
     nsLoadFlags loadFlags = 0;
     mPartChannel->GetLoadFlags(&loadFlags);
     loadFlags |= nsIChannel::LOAD_REPLACE;
     mPartChannel->SetLoadFlags(loadFlags);
 
     nsCOMPtr<nsILoadGroup> loadGroup;
     (void)mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup));
 
--- a/netwerk/test/unit/test_file_protocol.js
+++ b/netwerk/test/unit/test_file_protocol.js
@@ -227,19 +227,16 @@ function test_upload_file() {
 
 function test_load_replace() {
   // lnk files should resolve to their targets
   if (mozinfo.os == "win") {
     dump("*** test_load_replace\n");
     file = do_get_file("data/system_root.lnk", false);
     var chan = new_file_channel(file);
 
-    // The LOAD_REPLACE flag should be set
-    do_check_eq(chan.loadFlags & chan.LOAD_REPLACE, chan.LOAD_REPLACE);
-
     // The original URI path should differ from the URI path
     do_check_neq(chan.URI.path, chan.originalURI.path);
 
     // The original URI path should be the same as the lnk file path
     var ios = Cc["@mozilla.org/network/io-service;1"].
               getService(Ci.nsIIOService);
     do_check_eq(chan.originalURI.path, ios.newFileURI(file).path);
   }
--- a/services/sync/Weave.js
+++ b/services/sync/Weave.js
@@ -154,16 +154,17 @@ AboutWeaveLog.prototype = {
   },
 
   newChannel(aURI, aLoadInfo) {
     let dir = FileUtils.getDir("ProfD", ["weave", "logs"], true);
     let uri = Services.io.newFileURI(dir);
     let channel = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
 
     channel.originalURI = aURI;
+    aLoadInfo.resultPrincipalURI = aURI;
 
     // Ensure that the about page has the same privileges as a regular directory
     // view. That way links to files can be opened. make sure we use the correct
     // origin attributes when creating the principal for accessing the
     // about:sync-log data.
     let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
                 .getService(Ci.nsIScriptSecurityManager);
     let principal = ssm.createCodebasePrincipal(uri, aLoadInfo.originAttributes);
--- a/testing/mochitest/BrowserTestUtils/content/content-about-page-utils.js
+++ b/testing/mochitest/BrowserTestUtils/content/content-about-page-utils.js
@@ -21,16 +21,17 @@ AboutPage.prototype = {
     return this.uriFlags;
   },
 
   newChannel(aURI, aLoadInfo) {
     let newURI = Services.io.newURI(this.chromeURL);
     let channel = Services.io.newChannelFromURIWithLoadInfo(newURI,
                                                             aLoadInfo);
     channel.originalURI = aURI;
+    aLoadInfo.resultPrincipalURI = aURI;
 
     if (this.uriFlags & Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT) {
       channel.owner = null;
     }
     return channel;
   },
 
   createInstance(outer, iid) {
--- a/testing/talos/talos/tests/tabswitch/content/tabswitch-content-process.js
+++ b/testing/talos/talos/tests/tabswitch/content/tabswitch-content-process.js
@@ -9,16 +9,17 @@ class TabSwitchAboutModule {
   constructor() {
     this.QueryInterface = XPCOMUtils.generateQI([Ci.nsIAboutModule]);
   }
 
   newChannel(aURI, aLoadInfo) {
     let uri = Services.io.newURI(CHROME_URI);
     let chan = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
     chan.originalURI = aURI;
+    aLoadInfo.resultPrincipalURI = aURI;
     return chan;
   }
 
   getURIFlags(aURI) {
     return Ci.nsIAboutModule.ALLOW_SCRIPT |
            Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD;
   }
 }
--- a/toolkit/components/mozprotocol/mozProtocolHandler.js
+++ b/toolkit/components/mozprotocol/mozProtocolHandler.js
@@ -27,17 +27,16 @@ mozProtocolHandler.prototype = {
       uri.spec = spec;
     }
     return uri;
   },
 
   newChannel2(uri, loadInfo) {
     let realURL = NetUtil.newURI(this.urlToLoad);
     let channel = Services.io.newChannelFromURIWithLoadInfo(realURL, loadInfo)
-    channel.loadFlags |= Ci.nsIChannel.LOAD_REPLACE;
     return channel;
   },
 
   newChannel(uri) {
     return this.newChannel2(uri, null);
   },
 
   classID: Components.ID("{47a45e5f-691e-4799-8686-14f8d3fc0f8c}"),
--- a/toolkit/components/passwordmgr/test/browser/browser_hasInsecureLoginForms_streamConverter.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_hasInsecureLoginForms_streamConverter.js
@@ -33,16 +33,17 @@ function* registerConverter() {
 
     // nsIRequestObserver
     onStartRequest(aRequest, aContext) {
       let channel = NetUtil.newChannel({
         uri: "resource://testing-common/form_basic.html",
         loadUsingSystemPrincipal: true,
       });
       channel.originalURI = aRequest.QueryInterface(Ci.nsIChannel).URI;
+      channel.loadInfo.resultPrincipalURI = channel.originalURI;
       channel.loadGroup = aRequest.loadGroup;
       channel.owner = Services.scriptSecurityManager
                               .createCodebasePrincipal(channel.URI, {});
       // In this test, we pass the new channel to the listener but don't fire a
       // redirect notification, even if it would be required. This keeps the
       // test code simpler and doesn't impact the principal check we're testing.
       channel.asyncOpen2(this.listener);
     },
--- a/toolkit/components/places/PageIconProtocolHandler.js
+++ b/toolkit/components/places/PageIconProtocolHandler.js
@@ -12,16 +12,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 
 function makeDefaultFaviconChannel(uri, loadInfo) {
   let channel = Services.io.newChannelFromURIWithLoadInfo(
     PlacesUtils.favicons.defaultFavicon, loadInfo);
   channel.originalURI = uri;
+  loadInfo.resultPrincipalURI = uri;
   return channel;
 }
 
 function streamDefaultFavicon(uri, loadInfo, outputStream) {
   try {
     // Open up a new channel to get that data, and push it to our output stream.
     // Create a listener to hand data to the pipe's output stream.
     let listener = Cc["@mozilla.org/network/simple-stream-listener;1"]
--- a/toolkit/components/thumbnails/PageThumbsProtocol.js
+++ b/toolkit/components/thumbnails/PageThumbsProtocol.js
@@ -83,16 +83,17 @@ Protocol.prototype = {
    * @param aLoadInfo The Loadinfo which to use on the channel.
    * @return The newly created channel.
    */
   newChannel2: function Proto_newChannel2(aURI, aLoadInfo) {
     let {file} = aURI.QueryInterface(Ci.nsIFileURL);
     let fileuri = Services.io.newFileURI(file);
     let channel = Services.io.newChannelFromURIWithLoadInfo(fileuri, aLoadInfo);
     channel.originalURI = aURI;
+    aLoadInfo.resultPrincipalURI = aURI;
     return channel;
   },
 
   newChannel: function Proto_newChannel(aURI) {
     return this.newChannel2(aURI, null);
   },
 
   /**
--- a/toolkit/mozapps/extensions/test/browser/browser_openDialog.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_openDialog.js
@@ -19,16 +19,17 @@ var CustomChromeProtocol = {
     uri.spec = aSpec;
     return uri;
   },
 
   newChannel2: function CCP_newChannel2(aURI, aLoadInfo) {
     let url = Services.io.newURI("chrome:" + aURI.path);
     let ch = Services.io.newChannelFromURIWithLoadInfo(url, aLoadInfo);
     ch.originalURI = aURI;
+    aLoadInfo.resultPrincipalURI = aURI;
     return ch;
   },
 
   newChannel: function CCP_newChannel(aURI) {
     return this.newChannel2(aURI, null);
   },
 
   allowPort: function CCP_allowPort(aPort, aScheme) {