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 nsIChannelEventSink::REDIRECT_TRANSPARENT
))) {
72 nsCOMPtr
<nsILoadInfo
> loadInfo
= oldChan
->LoadInfo();
73 if (loadInfo
->GetDontFollowRedirects()) {
74 ExplicitCallback(NS_BINDING_ABORTED
);
79 if (synchronize
) mWaitingForRedirectCallback
= true;
81 nsCOMPtr
<nsIRunnable
> runnable
= this;
83 rv
= mainThreadEventTarget
84 ? mainThreadEventTarget
->Dispatch(runnable
.forget())
85 : GetMainThreadSerialEventTarget()->Dispatch(runnable
.forget());
86 NS_ENSURE_SUCCESS(rv
, rv
);
89 if (!SpinEventLoopUntil("nsAsyncRedirectVerifyHelper::Init"_ns
,
90 [&]() { return !mWaitingForRedirectCallback
; })) {
91 return NS_ERROR_UNEXPECTED
;
99 nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback(nsresult result
) {
101 ("nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback() "
102 "result=%" PRIx32
" expectedCBs=%u mResult=%" PRIx32
,
103 static_cast<uint32_t>(result
), mExpectedCallbacks
,
104 static_cast<uint32_t>(mResult
)));
106 MOZ_DIAGNOSTIC_ASSERT(
107 mExpectedCallbacks
> 0,
108 "OnRedirectVerifyCallback called more times than expected");
109 if (mExpectedCallbacks
<= 0) {
110 return NS_ERROR_UNEXPECTED
;
113 --mExpectedCallbacks
;
115 // If response indicates failure we may call back immediately
116 if (NS_FAILED(result
)) {
117 // We chose to store the first failure-value (as opposed to the last)
118 if (NS_SUCCEEDED(mResult
)) mResult
= result
;
120 // If InitCallback() has been called, just invoke the callback and
121 // return. Otherwise it will be invoked from InitCallback()
122 if (mCallbackInitiated
) {
123 ExplicitCallback(mResult
);
128 // If the expected-counter is in balance and InitCallback() was called, all
129 // sinks have agreed that the redirect is ok and we can invoke our callback
130 if (mCallbackInitiated
&& mExpectedCallbacks
== 0) {
131 ExplicitCallback(mResult
);
137 nsresult
nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect(
138 nsIChannelEventSink
* sink
, nsIChannel
* oldChannel
, nsIChannel
* newChannel
,
141 ("nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect() "
142 "sink=%p expectedCBs=%u mResult=%" PRIx32
,
143 sink
, mExpectedCallbacks
, static_cast<uint32_t>(mResult
)));
145 ++mExpectedCallbacks
;
147 if (IsOldChannelCanceled()) {
149 (" old channel has been canceled, cancel the redirect by "
150 "emulating OnRedirectVerifyCallback..."));
151 (void)OnRedirectVerifyCallback(NS_BINDING_ABORTED
);
152 return NS_BINDING_ABORTED
;
156 sink
->AsyncOnChannelRedirect(oldChannel
, newChannel
, flags
, this);
158 LOG((" result=%" PRIx32
" expectedCBs=%u", static_cast<uint32_t>(rv
),
159 mExpectedCallbacks
));
161 // If the sink returns failure from this call the redirect is vetoed. We
162 // emulate a callback from the sink in this case in order to perform all
163 // the necessary logic.
165 LOG((" emulating OnRedirectVerifyCallback..."));
166 (void)OnRedirectVerifyCallback(rv
);
169 return rv
; // Return the actual status since our caller may need it
172 void nsAsyncRedirectVerifyHelper::ExplicitCallback(nsresult result
) {
174 ("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
176 " expectedCBs=%u mCallbackInitiated=%u mResult=%" PRIx32
,
177 static_cast<uint32_t>(result
), mExpectedCallbacks
, mCallbackInitiated
,
178 static_cast<uint32_t>(mResult
)));
180 nsCOMPtr
<nsIAsyncVerifyRedirectCallback
> callback(
181 do_QueryInterface(mOldChan
));
183 if (!callback
|| !mCallbackEventTarget
) {
185 ("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
186 "callback=%p mCallbackEventTarget=%p",
187 callback
.get(), mCallbackEventTarget
.get()));
191 mCallbackInitiated
= false; // reset to ensure only one callback
192 mWaitingForRedirectCallback
= false;
194 // Now, dispatch the callback on the event-target which called Init()
195 nsCOMPtr
<nsIRunnable
> event
=
196 new nsAsyncVerifyRedirectCallbackEvent(callback
, result
);
199 "nsAsyncRedirectVerifyHelper::ExplicitCallback() "
200 "failed creating callback event!");
203 nsresult rv
= mCallbackEventTarget
->Dispatch(event
, NS_DISPATCH_NORMAL
);
206 "nsAsyncRedirectVerifyHelper::ExplicitCallback() "
207 "failed dispatching callback event!");
210 ("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
211 "dispatched callback event=%p",
216 void nsAsyncRedirectVerifyHelper::InitCallback() {
218 ("nsAsyncRedirectVerifyHelper::InitCallback() "
219 "expectedCBs=%d mResult=%" PRIx32
,
220 mExpectedCallbacks
, static_cast<uint32_t>(mResult
)));
222 mCallbackInitiated
= true;
224 // Invoke the callback if we are done
225 if (mExpectedCallbacks
== 0) ExplicitCallback(mResult
);
229 nsAsyncRedirectVerifyHelper::GetName(nsACString
& aName
) {
230 aName
.AssignLiteral("nsAsyncRedirectVerifyHelper");
235 nsAsyncRedirectVerifyHelper::Run() {
236 /* If the channel got canceled after it fired AsyncOnChannelRedirect
237 * and before we got here, mostly because docloader load has been canceled,
238 * we must completely ignore this notification and prevent any further
241 if (IsOldChannelCanceled()) {
242 ExplicitCallback(NS_BINDING_ABORTED
);
246 // If transparent, avoid notifying the observers.
247 if (mFlags
& nsIChannelEventSink::REDIRECT_TRANSPARENT
) {
248 ExplicitCallback(NS_OK
);
252 // First, the global observer
253 NS_ASSERTION(gIOService
, "Must have an IO service at this point");
254 LOG(("nsAsyncRedirectVerifyHelper::Run() calling gIOService..."));
256 gIOService
->AsyncOnChannelRedirect(mOldChan
, mNewChan
, mFlags
, this);
258 ExplicitCallback(rv
);
262 // Now, the per-channel observers
263 nsCOMPtr
<nsIChannelEventSink
> sink
;
264 NS_QueryNotificationCallbacks(mOldChan
, sink
);
266 LOG(("nsAsyncRedirectVerifyHelper::Run() calling sink..."));
267 rv
= DelegateOnChannelRedirect(sink
, mOldChan
, mNewChan
, mFlags
);
270 // All invocations to AsyncOnChannelRedirect has been done - call
271 // InitCallback() to flag this
276 bool nsAsyncRedirectVerifyHelper::IsOldChannelCanceled() {
281 nsresult rv
= mOldChan
->GetCanceled(&canceled
);
282 return NS_SUCCEEDED(rv
) && canceled
;
286 } // namespace mozilla