Bug 1478171 - [1.0] Forward channel redirect to nsILoadURIDelegate to allow external handling. r=smaug,snorp a=ritu
--- a/netwerk/base/nsIChannelEventSink.idl
+++ b/netwerk/base/nsIChannelEventSink.idl
@@ -48,16 +48,23 @@ interface nsIChannelEventSink : nsISuppo
/**
* This is a special-cased redirect coming from hitting HSTS upgrade
* redirect from http to https only. In some cases this type of redirect
* may be considered as safe despite not being the-same-origin redirect.
*/
const unsigned long REDIRECT_STS_UPGRADE = 1 << 3;
/**
+ * This redirect has already been presented to the nsILoadURIDelegate
+ * for possible handling; if this flag is set we may safely skip checking
+ * if the nsILoadURIDelegate will handle the redirect.
+ */
+ const unsigned long REDIRECT_DELEGATES_CHECKED = 1 << 4;
+
+ /**
* Called when a redirect occurs. This may happen due to an HTTP 3xx status
* code. The purpose of this method is to notify the sink that a redirect
* is about to happen, but also to give the sink the right to veto the
* redirect by throwing or passing a failure-code in the callback.
*
* Note that vetoing the redirect simply means that |newChannel| will not
* be opened. It is important to understand that |oldChannel| will continue
* loading as if it received a HTTP 200, which includes notifying observers
--- a/uriloader/base/nsDocLoader.cpp
+++ b/uriloader/base/nsDocLoader.cpp
@@ -1,16 +1,18 @@
/* -*- Mode: C++; tab-width: 2; 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 "nspr.h"
#include "mozilla/Logging.h"
#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
#include "nsDocLoader.h"
#include "nsCURILoader.h"
#include "nsNetUtil.h"
#include "nsIHttpChannel.h"
#include "nsIWebProgressListener2.h"
#include "nsIServiceManager.h"
@@ -29,16 +31,18 @@
#include "nsIScriptSecurityManager.h"
#include "nsITransport.h"
#include "nsISocketTransport.h"
#include "nsIDocShell.h"
#include "nsIDocument.h"
#include "nsPresContext.h"
#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsILoadURIDelegate.h"
+#include "nsIBrowserDOMWindow.h"
using mozilla::DebugOnly;
using mozilla::LogLevel;
//
// Log module for nsIDocumentLoader logging...
//
// To enable logging (see mozilla/Logging.h for full details):
@@ -1415,21 +1419,116 @@ int64_t nsDocLoader::CalculateMaxProgres
if (info->mMaxProgress < info->mCurrentProgress) {
return int64_t(-1);
}
max += info->mMaxProgress;
}
return max;
}
+class LoadURIDelegateRedirectHandler final : public mozilla::dom::PromiseNativeHandler
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(LoadURIDelegateRedirectHandler)
+
+ LoadURIDelegateRedirectHandler(nsDocLoader* aDocLoader,
+ nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* aCallback)
+ : mDocLoader(aDocLoader)
+ , mOldChannel(aOldChannel)
+ , mNewChannel(aNewChannel)
+ , mFlags(aFlags)
+ , mCallback(aCallback)
+ {}
+
+ void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ if (aValue.isBoolean() && aValue.toBoolean()) {
+ // The app handled the redirect, notify the callback
+ mCallback->OnRedirectVerifyCallback(NS_ERROR_ABORT);
+ } else {
+ UnhandledCallback();
+ }
+ }
+
+ void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+ {
+ UnhandledCallback();
+ }
+
+private:
+ ~LoadURIDelegateRedirectHandler()
+ {}
+
+ void UnhandledCallback()
+ {
+ // If the redirect wasn't handled by the nsILoadURIDelegate, let Gecko
+ // handle it.
+ mFlags |= nsIChannelEventSink::REDIRECT_DELEGATES_CHECKED;
+ mDocLoader->AsyncOnChannelRedirect(mOldChannel, mNewChannel, mFlags,
+ mCallback);
+ }
+
+ RefPtr<nsDocLoader> mDocLoader;
+ nsCOMPtr<nsIChannel> mOldChannel;
+ nsCOMPtr<nsIChannel> mNewChannel;
+ uint32_t mFlags;
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mCallback;
+};
+
+NS_IMPL_CYCLE_COLLECTION(LoadURIDelegateRedirectHandler, mDocLoader,
+ mOldChannel, mNewChannel, mCallback)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LoadURIDelegateRedirectHandler)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(LoadURIDelegateRedirectHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(LoadURIDelegateRedirectHandler)
+
NS_IMETHODIMP nsDocLoader::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
nsIChannel *aNewChannel,
uint32_t aFlags,
nsIAsyncVerifyRedirectCallback *cb)
{
+ if ((aFlags &
+ (nsIChannelEventSink::REDIRECT_TEMPORARY |
+ nsIChannelEventSink::REDIRECT_PERMANENT)) &&
+ !(aFlags & nsIChannelEventSink::REDIRECT_DELEGATES_CHECKED)) {
+ nsCOMPtr<nsIDocShell> docShell =
+ do_QueryInterface(static_cast<nsIRequestObserver*>(this));
+
+ nsCOMPtr<nsILoadURIDelegate> delegate;
+ docShell->GetLoadURIDelegate(getter_AddRefs(delegate));
+
+ nsCOMPtr<nsIURI> newURI;
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+
+ if (newURI && delegate) {
+ RefPtr<mozilla::dom::Promise> promise;
+ const int where = nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
+ nsresult rv = delegate->LoadURI(newURI, where, /* flags */ 0,
+ /* triggering principal */ nullptr,
+ getter_AddRefs(promise));
+ if (NS_SUCCEEDED(rv) && promise) {
+ RefPtr<LoadURIDelegateRedirectHandler> handler =
+ new LoadURIDelegateRedirectHandler(this, aOldChannel, aNewChannel,
+ aFlags, cb);
+
+ promise->AppendNativeHandler(handler);
+ return NS_OK;
+ }
+ }
+ }
+
if (aOldChannel)
{
nsLoadFlags loadFlags = 0;
int32_t stateFlags = nsIWebProgressListener::STATE_REDIRECTING |
nsIWebProgressListener::STATE_IS_REQUEST;
aOldChannel->GetLoadFlags(&loadFlags);
// If the document channel is being redirected, then indicate that the