|
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
|
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/. */ |
|
5 |
|
6 #ifdef MOZ_LOGGING |
|
7 #define FORCE_PR_LOG |
|
8 #endif |
|
9 |
|
10 #include "prlog.h" |
|
11 #include "nsAsyncRedirectVerifyHelper.h" |
|
12 #include "nsThreadUtils.h" |
|
13 #include "nsNetUtil.h" |
|
14 |
|
15 #include "nsIOService.h" |
|
16 #include "nsIChannel.h" |
|
17 #include "nsIHttpChannelInternal.h" |
|
18 #include "nsIAsyncVerifyRedirectCallback.h" |
|
19 |
|
20 #undef LOG |
|
21 #ifdef PR_LOGGING |
|
22 static PRLogModuleInfo * |
|
23 GetRedirectLog() |
|
24 { |
|
25 static PRLogModuleInfo *sLog; |
|
26 if (!sLog) |
|
27 sLog = PR_NewLogModule("nsRedirect"); |
|
28 return sLog; |
|
29 } |
|
30 #define LOG(args) PR_LOG(GetRedirectLog(), PR_LOG_DEBUG, args) |
|
31 #else |
|
32 #define LOG(args) |
|
33 #endif |
|
34 |
|
35 NS_IMPL_ISUPPORTS(nsAsyncRedirectVerifyHelper, |
|
36 nsIAsyncVerifyRedirectCallback, |
|
37 nsIRunnable) |
|
38 |
|
39 class nsAsyncVerifyRedirectCallbackEvent : public nsRunnable { |
|
40 public: |
|
41 nsAsyncVerifyRedirectCallbackEvent(nsIAsyncVerifyRedirectCallback *cb, |
|
42 nsresult result) |
|
43 : mCallback(cb), mResult(result) { |
|
44 } |
|
45 |
|
46 NS_IMETHOD Run() |
|
47 { |
|
48 LOG(("nsAsyncVerifyRedirectCallbackEvent::Run() " |
|
49 "callback to %p with result %x", |
|
50 mCallback.get(), mResult)); |
|
51 (void) mCallback->OnRedirectVerifyCallback(mResult); |
|
52 return NS_OK; |
|
53 } |
|
54 private: |
|
55 nsCOMPtr<nsIAsyncVerifyRedirectCallback> mCallback; |
|
56 nsresult mResult; |
|
57 }; |
|
58 |
|
59 nsAsyncRedirectVerifyHelper::nsAsyncRedirectVerifyHelper() |
|
60 : mCallbackInitiated(false), |
|
61 mExpectedCallbacks(0), |
|
62 mResult(NS_OK) |
|
63 { |
|
64 } |
|
65 |
|
66 nsAsyncRedirectVerifyHelper::~nsAsyncRedirectVerifyHelper() |
|
67 { |
|
68 NS_ASSERTION(NS_FAILED(mResult) || mExpectedCallbacks == 0, |
|
69 "Did not receive all required callbacks!"); |
|
70 } |
|
71 |
|
72 nsresult |
|
73 nsAsyncRedirectVerifyHelper::Init(nsIChannel* oldChan, nsIChannel* newChan, |
|
74 uint32_t flags, bool synchronize) |
|
75 { |
|
76 LOG(("nsAsyncRedirectVerifyHelper::Init() " |
|
77 "oldChan=%p newChan=%p", oldChan, newChan)); |
|
78 mOldChan = oldChan; |
|
79 mNewChan = newChan; |
|
80 mFlags = flags; |
|
81 mCallbackThread = do_GetCurrentThread(); |
|
82 |
|
83 if (synchronize) |
|
84 mWaitingForRedirectCallback = true; |
|
85 |
|
86 nsresult rv; |
|
87 rv = NS_DispatchToMainThread(this); |
|
88 NS_ENSURE_SUCCESS(rv, rv); |
|
89 |
|
90 if (synchronize) { |
|
91 nsIThread *thread = NS_GetCurrentThread(); |
|
92 while (mWaitingForRedirectCallback) { |
|
93 if (!NS_ProcessNextEvent(thread)) { |
|
94 return NS_ERROR_UNEXPECTED; |
|
95 } |
|
96 } |
|
97 } |
|
98 |
|
99 return NS_OK; |
|
100 } |
|
101 |
|
102 NS_IMETHODIMP |
|
103 nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback(nsresult result) |
|
104 { |
|
105 LOG(("nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback() " |
|
106 "result=%x expectedCBs=%u mResult=%x", |
|
107 result, mExpectedCallbacks, mResult)); |
|
108 |
|
109 --mExpectedCallbacks; |
|
110 |
|
111 // If response indicates failure we may call back immediately |
|
112 if (NS_FAILED(result)) { |
|
113 // We chose to store the first failure-value (as opposed to the last) |
|
114 if (NS_SUCCEEDED(mResult)) |
|
115 mResult = result; |
|
116 |
|
117 // If InitCallback() has been called, just invoke the callback and |
|
118 // return. Otherwise it will be invoked from InitCallback() |
|
119 if (mCallbackInitiated) { |
|
120 ExplicitCallback(mResult); |
|
121 return NS_OK; |
|
122 } |
|
123 } |
|
124 |
|
125 // If the expected-counter is in balance and InitCallback() was called, all |
|
126 // sinks have agreed that the redirect is ok and we can invoke our callback |
|
127 if (mCallbackInitiated && mExpectedCallbacks == 0) { |
|
128 ExplicitCallback(mResult); |
|
129 } |
|
130 |
|
131 return NS_OK; |
|
132 } |
|
133 |
|
134 nsresult |
|
135 nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect(nsIChannelEventSink *sink, |
|
136 nsIChannel *oldChannel, |
|
137 nsIChannel *newChannel, |
|
138 uint32_t flags) |
|
139 { |
|
140 LOG(("nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect() " |
|
141 "sink=%p expectedCBs=%u mResult=%x", |
|
142 sink, mExpectedCallbacks, mResult)); |
|
143 |
|
144 ++mExpectedCallbacks; |
|
145 |
|
146 if (IsOldChannelCanceled()) { |
|
147 LOG((" old channel has been canceled, cancel the redirect by " |
|
148 "emulating OnRedirectVerifyCallback...")); |
|
149 (void) OnRedirectVerifyCallback(NS_BINDING_ABORTED); |
|
150 return NS_BINDING_ABORTED; |
|
151 } |
|
152 |
|
153 nsresult rv = |
|
154 sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this); |
|
155 |
|
156 LOG((" result=%x expectedCBs=%u", rv, mExpectedCallbacks)); |
|
157 |
|
158 // If the sink returns failure from this call the redirect is vetoed. We |
|
159 // emulate a callback from the sink in this case in order to perform all |
|
160 // the necessary logic. |
|
161 if (NS_FAILED(rv)) { |
|
162 LOG((" emulating OnRedirectVerifyCallback...")); |
|
163 (void) OnRedirectVerifyCallback(rv); |
|
164 } |
|
165 |
|
166 return rv; // Return the actual status since our caller may need it |
|
167 } |
|
168 |
|
169 void |
|
170 nsAsyncRedirectVerifyHelper::ExplicitCallback(nsresult result) |
|
171 { |
|
172 LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() " |
|
173 "result=%x expectedCBs=%u mCallbackInitiated=%u mResult=%x", |
|
174 result, mExpectedCallbacks, mCallbackInitiated, mResult)); |
|
175 |
|
176 nsCOMPtr<nsIAsyncVerifyRedirectCallback> |
|
177 callback(do_QueryInterface(mOldChan)); |
|
178 |
|
179 if (!callback || !mCallbackThread) { |
|
180 LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() " |
|
181 "callback=%p mCallbackThread=%p", callback.get(), mCallbackThread.get())); |
|
182 return; |
|
183 } |
|
184 |
|
185 mCallbackInitiated = false; // reset to ensure only one callback |
|
186 mWaitingForRedirectCallback = false; |
|
187 |
|
188 // Now, dispatch the callback on the event-target which called Init() |
|
189 nsRefPtr<nsIRunnable> event = |
|
190 new nsAsyncVerifyRedirectCallbackEvent(callback, result); |
|
191 if (!event) { |
|
192 NS_WARNING("nsAsyncRedirectVerifyHelper::ExplicitCallback() " |
|
193 "failed creating callback event!"); |
|
194 return; |
|
195 } |
|
196 nsresult rv = mCallbackThread->Dispatch(event, NS_DISPATCH_NORMAL); |
|
197 if (NS_FAILED(rv)) { |
|
198 NS_WARNING("nsAsyncRedirectVerifyHelper::ExplicitCallback() " |
|
199 "failed dispatching callback event!"); |
|
200 } else { |
|
201 LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() " |
|
202 "dispatched callback event=%p", event.get())); |
|
203 } |
|
204 |
|
205 } |
|
206 |
|
207 void |
|
208 nsAsyncRedirectVerifyHelper::InitCallback() |
|
209 { |
|
210 LOG(("nsAsyncRedirectVerifyHelper::InitCallback() " |
|
211 "expectedCBs=%d mResult=%x", mExpectedCallbacks, mResult)); |
|
212 |
|
213 mCallbackInitiated = true; |
|
214 |
|
215 // Invoke the callback if we are done |
|
216 if (mExpectedCallbacks == 0) |
|
217 ExplicitCallback(mResult); |
|
218 } |
|
219 |
|
220 NS_IMETHODIMP |
|
221 nsAsyncRedirectVerifyHelper::Run() |
|
222 { |
|
223 /* If the channel got canceled after it fired AsyncOnChannelRedirect |
|
224 * and before we got here, mostly because docloader load has been canceled, |
|
225 * we must completely ignore this notification and prevent any further |
|
226 * notification. |
|
227 */ |
|
228 if (IsOldChannelCanceled()) { |
|
229 ExplicitCallback(NS_BINDING_ABORTED); |
|
230 return NS_OK; |
|
231 } |
|
232 |
|
233 // First, the global observer |
|
234 NS_ASSERTION(gIOService, "Must have an IO service at this point"); |
|
235 LOG(("nsAsyncRedirectVerifyHelper::Run() calling gIOService...")); |
|
236 nsresult rv = gIOService->AsyncOnChannelRedirect(mOldChan, mNewChan, |
|
237 mFlags, this); |
|
238 if (NS_FAILED(rv)) { |
|
239 ExplicitCallback(rv); |
|
240 return NS_OK; |
|
241 } |
|
242 |
|
243 // Now, the per-channel observers |
|
244 nsCOMPtr<nsIChannelEventSink> sink; |
|
245 NS_QueryNotificationCallbacks(mOldChan, sink); |
|
246 if (sink) { |
|
247 LOG(("nsAsyncRedirectVerifyHelper::Run() calling sink...")); |
|
248 rv = DelegateOnChannelRedirect(sink, mOldChan, mNewChan, mFlags); |
|
249 } |
|
250 |
|
251 // All invocations to AsyncOnChannelRedirect has been done - call |
|
252 // InitCallback() to flag this |
|
253 InitCallback(); |
|
254 return NS_OK; |
|
255 } |
|
256 |
|
257 bool |
|
258 nsAsyncRedirectVerifyHelper::IsOldChannelCanceled() |
|
259 { |
|
260 bool canceled; |
|
261 nsCOMPtr<nsIHttpChannelInternal> oldChannelInternal = |
|
262 do_QueryInterface(mOldChan); |
|
263 if (oldChannelInternal) { |
|
264 oldChannelInternal->GetCanceled(&canceled); |
|
265 if (canceled) |
|
266 return true; |
|
267 } |
|
268 |
|
269 return false; |
|
270 } |