1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/Logging.h"
7 #include "mozilla/SpinEventLoopUntil.h"
8 #include "nsAsyncRedirectVerifyHelper.h"
9 #include "nsThreadUtils.h"
10 #include "nsNetUtil.h"
12 #include "nsIOService.h"
13 #include "nsIChannel.h"
14 #include "nsIHttpChannelInternal.h"
15 #include "nsIAsyncVerifyRedirectCallback.h"
16 #include "nsILoadInfo.h"
21 static LazyLogModule
gRedirectLog("nsRedirect");
23 #define LOG(args) MOZ_LOG(gRedirectLog, LogLevel::Debug, args)
25 NS_IMPL_ISUPPORTS(nsAsyncRedirectVerifyHelper
, nsIAsyncVerifyRedirectCallback
,
26 nsIRunnable
, nsINamed
)
28 class nsAsyncVerifyRedirectCallbackEvent
: public Runnable
{
30 nsAsyncVerifyRedirectCallbackEvent(nsIAsyncVerifyRedirectCallback
* cb
,
32 : Runnable("nsAsyncVerifyRedirectCallbackEvent"),
36 NS_IMETHOD
Run() override
{
38 ("nsAsyncVerifyRedirectCallbackEvent::Run() "
39 "callback to %p with result %" PRIx32
,
40 mCallback
.get(), static_cast<uint32_t>(mResult
)));
41 (void)mCallback
->OnRedirectVerifyCallback(mResult
);
46 nsCOMPtr
<nsIAsyncVerifyRedirectCallback
> mCallback
;
50 nsAsyncRedirectVerifyHelper::~nsAsyncRedirectVerifyHelper() {
51 NS_ASSERTION(NS_FAILED(mResult
) || mExpectedCallbacks
== 0,
52 "Did not receive all required callbacks!");
55 nsresult
nsAsyncRedirectVerifyHelper::Init(
56 nsIChannel
* oldChan
, nsIChannel
* newChan
, uint32_t flags
,
57 nsIEventTarget
* mainThreadEventTarget
, bool synchronize
) {
59 ("nsAsyncRedirectVerifyHelper::Init() "
60 "oldChan=%p newChan=%p",
65 mCallbackEventTarget
= NS_IsMainThread() && mainThreadEventTarget
66 ? mainThreadEventTarget
67 : GetCurrentSerialEventTarget();
69 if (!(flags
& (nsIChannelEventSink::REDIRECT_INTERNAL
|
70 nsIChannelEventSink::REDIRECT_STS_UPGRADE
))) {
71 nsCOMPtr
<nsILoadInfo
> loadInfo
= oldChan
->LoadInfo();
72 if (loadInfo
->GetDontFollowRedirects()) {
73 ExplicitCallback(NS_BINDING_ABORTED
);
78 if (synchronize
) mWaitingForRedirectCallback
= true;
80 nsCOMPtr
<nsIRunnable
> runnable
= this;
82 rv
= mainThreadEventTarget
83 ? mainThreadEventTarget
->Dispatch(runnable
.forget())
84 : GetMainThreadSerialEventTarget()->Dispatch(runnable
.forget());
85 NS_ENSURE_SUCCESS(rv
, rv
);
88 if (!SpinEventLoopUntil("nsAsyncRedirectVerifyHelper::Init"_ns
,
89 [&]() { return !mWaitingForRedirectCallback
; })) {
90 return NS_ERROR_UNEXPECTED
;
98 nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback(nsresult result
) {
100 ("nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback() "
101 "result=%" PRIx32
" expectedCBs=%u mResult=%" PRIx32
,
102 static_cast<uint32_t>(result
), mExpectedCallbacks
,
103 static_cast<uint32_t>(mResult
)));
105 MOZ_DIAGNOSTIC_ASSERT(
106 mExpectedCallbacks
> 0,
107 "OnRedirectVerifyCallback called more times than expected");
108 if (mExpectedCallbacks
<= 0) {
109 return NS_ERROR_UNEXPECTED
;
112 --mExpectedCallbacks
;
114 // If response indicates failure we may call back immediately
115 if (NS_FAILED(result
)) {
116 // We chose to store the first failure-value (as opposed to the last)
117 if (NS_SUCCEEDED(mResult
)) mResult
= result
;
119 // If InitCallback() has been called, just invoke the callback and
120 // return. Otherwise it will be invoked from InitCallback()
121 if (mCallbackInitiated
) {
122 ExplicitCallback(mResult
);
127 // If the expected-counter is in balance and InitCallback() was called, all
128 // sinks have agreed that the redirect is ok and we can invoke our callback
129 if (mCallbackInitiated
&& mExpectedCallbacks
== 0) {
130 ExplicitCallback(mResult
);
136 nsresult
nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect(
137 nsIChannelEventSink
* sink
, nsIChannel
* oldChannel
, nsIChannel
* newChannel
,
140 ("nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect() "
141 "sink=%p expectedCBs=%u mResult=%" PRIx32
,
142 sink
, mExpectedCallbacks
, static_cast<uint32_t>(mResult
)));
144 ++mExpectedCallbacks
;
146 if (IsOldChannelCanceled()) {
148 (" old channel has been canceled, cancel the redirect by "
149 "emulating OnRedirectVerifyCallback..."));
150 (void)OnRedirectVerifyCallback(NS_BINDING_ABORTED
);
151 return NS_BINDING_ABORTED
;
155 sink
->AsyncOnChannelRedirect(oldChannel
, newChannel
, flags
, this);
157 LOG((" result=%" PRIx32
" expectedCBs=%u", static_cast<uint32_t>(rv
),
158 mExpectedCallbacks
));
160 // If the sink returns failure from this call the redirect is vetoed. We
161 // emulate a callback from the sink in this case in order to perform all
162 // the necessary logic.
164 LOG((" emulating OnRedirectVerifyCallback..."));
165 (void)OnRedirectVerifyCallback(rv
);
168 return rv
; // Return the actual status since our caller may need it
171 void nsAsyncRedirectVerifyHelper::ExplicitCallback(nsresult result
) {
173 ("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
175 " expectedCBs=%u mCallbackInitiated=%u mResult=%" PRIx32
,
176 static_cast<uint32_t>(result
), mExpectedCallbacks
, mCallbackInitiated
,
177 static_cast<uint32_t>(mResult
)));
179 nsCOMPtr
<nsIAsyncVerifyRedirectCallback
> callback(
180 do_QueryInterface(mOldChan
));
182 if (!callback
|| !mCallbackEventTarget
) {
184 ("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
185 "callback=%p mCallbackEventTarget=%p",
186 callback
.get(), mCallbackEventTarget
.get()));
190 mCallbackInitiated
= false; // reset to ensure only one callback
191 mWaitingForRedirectCallback
= false;
193 // Now, dispatch the callback on the event-target which called Init()
194 nsCOMPtr
<nsIRunnable
> event
=
195 new nsAsyncVerifyRedirectCallbackEvent(callback
, result
);
198 "nsAsyncRedirectVerifyHelper::ExplicitCallback() "
199 "failed creating callback event!");
202 nsresult rv
= mCallbackEventTarget
->Dispatch(event
, NS_DISPATCH_NORMAL
);
205 "nsAsyncRedirectVerifyHelper::ExplicitCallback() "
206 "failed dispatching callback event!");
209 ("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
210 "dispatched callback event=%p",
215 void nsAsyncRedirectVerifyHelper::InitCallback() {
217 ("nsAsyncRedirectVerifyHelper::InitCallback() "
218 "expectedCBs=%d mResult=%" PRIx32
,
219 mExpectedCallbacks
, static_cast<uint32_t>(mResult
)));
221 mCallbackInitiated
= true;
223 // Invoke the callback if we are done
224 if (mExpectedCallbacks
== 0) ExplicitCallback(mResult
);
228 nsAsyncRedirectVerifyHelper::GetName(nsACString
& aName
) {
229 aName
.AssignLiteral("nsAsyncRedirectVerifyHelper");
234 nsAsyncRedirectVerifyHelper::Run() {
235 /* If the channel got canceled after it fired AsyncOnChannelRedirect
236 * and before we got here, mostly because docloader load has been canceled,
237 * we must completely ignore this notification and prevent any further
240 if (IsOldChannelCanceled()) {
241 ExplicitCallback(NS_BINDING_ABORTED
);
245 // First, the global observer
246 NS_ASSERTION(gIOService
, "Must have an IO service at this point");
247 LOG(("nsAsyncRedirectVerifyHelper::Run() calling gIOService..."));
249 gIOService
->AsyncOnChannelRedirect(mOldChan
, mNewChan
, mFlags
, this);
251 ExplicitCallback(rv
);
255 // Now, the per-channel observers
256 nsCOMPtr
<nsIChannelEventSink
> sink
;
257 NS_QueryNotificationCallbacks(mOldChan
, sink
);
259 LOG(("nsAsyncRedirectVerifyHelper::Run() calling sink..."));
260 rv
= DelegateOnChannelRedirect(sink
, mOldChan
, mNewChan
, mFlags
);
263 // All invocations to AsyncOnChannelRedirect has been done - call
264 // InitCallback() to flag this
269 bool nsAsyncRedirectVerifyHelper::IsOldChannelCanceled() {
274 nsresult rv
= mOldChan
->GetCanceled(&canceled
);
275 return NS_SUCCEEDED(rv
) && canceled
;
279 } // namespace mozilla