|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
|
2 * |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "imgRequest.h" |
|
8 #include "ImageLogging.h" |
|
9 |
|
10 #include "imgLoader.h" |
|
11 #include "imgRequestProxy.h" |
|
12 #include "imgStatusTracker.h" |
|
13 #include "ImageFactory.h" |
|
14 #include "Image.h" |
|
15 #include "RasterImage.h" |
|
16 |
|
17 #include "nsIChannel.h" |
|
18 #include "nsICachingChannel.h" |
|
19 #include "nsIThreadRetargetableRequest.h" |
|
20 #include "nsIInputStream.h" |
|
21 #include "nsIMultiPartChannel.h" |
|
22 #include "nsIHttpChannel.h" |
|
23 #include "nsIApplicationCache.h" |
|
24 #include "nsIApplicationCacheChannel.h" |
|
25 #include "nsMimeTypes.h" |
|
26 |
|
27 #include "nsIInterfaceRequestorUtils.h" |
|
28 #include "nsISupportsPrimitives.h" |
|
29 #include "nsIScriptSecurityManager.h" |
|
30 #include "nsContentUtils.h" |
|
31 |
|
32 #include "nsICacheEntry.h" |
|
33 |
|
34 #include "plstr.h" // PL_strcasestr(...) |
|
35 #include "nsNetUtil.h" |
|
36 #include "nsIProtocolHandler.h" |
|
37 #include "imgIRequest.h" |
|
38 |
|
39 using namespace mozilla; |
|
40 using namespace mozilla::image; |
|
41 |
|
42 #if defined(PR_LOGGING) |
|
43 PRLogModuleInfo * |
|
44 GetImgLog() |
|
45 { |
|
46 static PRLogModuleInfo *sImgLog; |
|
47 if (!sImgLog) |
|
48 sImgLog = PR_NewLogModule("imgRequest"); |
|
49 return sImgLog; |
|
50 } |
|
51 #endif |
|
52 |
|
53 NS_IMPL_ISUPPORTS(imgRequest, |
|
54 nsIStreamListener, nsIRequestObserver, |
|
55 nsIThreadRetargetableStreamListener, |
|
56 nsIChannelEventSink, |
|
57 nsIInterfaceRequestor, |
|
58 nsIAsyncVerifyRedirectCallback) |
|
59 |
|
60 imgRequest::imgRequest(imgLoader* aLoader) |
|
61 : mLoader(aLoader) |
|
62 , mStatusTracker(new imgStatusTracker(nullptr)) |
|
63 , mValidator(nullptr) |
|
64 , mInnerWindowId(0) |
|
65 , mCORSMode(imgIRequest::CORS_NONE) |
|
66 , mDecodeRequested(false) |
|
67 , mIsMultiPartChannel(false) |
|
68 , mGotData(false) |
|
69 , mIsInCache(false) |
|
70 , mResniffMimeType(false) |
|
71 { } |
|
72 |
|
73 imgRequest::~imgRequest() |
|
74 { |
|
75 if (mURI) { |
|
76 nsAutoCString spec; |
|
77 mURI->GetSpec(spec); |
|
78 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequest::~imgRequest()", "keyuri", spec.get()); |
|
79 } else |
|
80 LOG_FUNC(GetImgLog(), "imgRequest::~imgRequest()"); |
|
81 } |
|
82 |
|
83 nsresult imgRequest::Init(nsIURI *aURI, |
|
84 nsIURI *aCurrentURI, |
|
85 nsIURI *aFirstPartyIsolationURI, |
|
86 nsIRequest *aRequest, |
|
87 nsIChannel *aChannel, |
|
88 imgCacheEntry *aCacheEntry, |
|
89 void *aLoadId, |
|
90 nsIPrincipal* aLoadingPrincipal, |
|
91 int32_t aCORSMode) |
|
92 { |
|
93 MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!"); |
|
94 |
|
95 LOG_FUNC(GetImgLog(), "imgRequest::Init"); |
|
96 |
|
97 NS_ABORT_IF_FALSE(!mImage, "Multiple calls to init"); |
|
98 NS_ABORT_IF_FALSE(aURI, "No uri"); |
|
99 NS_ABORT_IF_FALSE(aCurrentURI, "No current uri"); |
|
100 NS_ABORT_IF_FALSE(aRequest, "No request"); |
|
101 NS_ABORT_IF_FALSE(aChannel, "No channel"); |
|
102 |
|
103 mProperties = do_CreateInstance("@mozilla.org/properties;1"); |
|
104 |
|
105 // Use ImageURL to ensure access to URI data off main thread. |
|
106 mURI = new ImageURL(aURI); |
|
107 mCurrentURI = aCurrentURI; |
|
108 mFirstPartyIsolationURI = aFirstPartyIsolationURI; |
|
109 mRequest = aRequest; |
|
110 mChannel = aChannel; |
|
111 mTimedChannel = do_QueryInterface(mChannel); |
|
112 |
|
113 mLoadingPrincipal = aLoadingPrincipal; |
|
114 mCORSMode = aCORSMode; |
|
115 |
|
116 mChannel->GetNotificationCallbacks(getter_AddRefs(mPrevChannelSink)); |
|
117 |
|
118 NS_ASSERTION(mPrevChannelSink != this, |
|
119 "Initializing with a channel that already calls back to us!"); |
|
120 |
|
121 mChannel->SetNotificationCallbacks(this); |
|
122 |
|
123 mCacheEntry = aCacheEntry; |
|
124 |
|
125 SetLoadId(aLoadId); |
|
126 |
|
127 return NS_OK; |
|
128 } |
|
129 |
|
130 already_AddRefed<imgStatusTracker> |
|
131 imgRequest::GetStatusTracker() |
|
132 { |
|
133 if (mImage && mGotData) { |
|
134 NS_ABORT_IF_FALSE(!mStatusTracker, |
|
135 "Should have given mStatusTracker to mImage"); |
|
136 return mImage->GetStatusTracker(); |
|
137 } else { |
|
138 NS_ABORT_IF_FALSE(mStatusTracker, |
|
139 "Should have mStatusTracker until we create mImage"); |
|
140 nsRefPtr<imgStatusTracker> statusTracker = mStatusTracker; |
|
141 MOZ_ASSERT(statusTracker); |
|
142 return statusTracker.forget(); |
|
143 } |
|
144 } |
|
145 |
|
146 void imgRequest::SetCacheEntry(imgCacheEntry *entry) |
|
147 { |
|
148 mCacheEntry = entry; |
|
149 } |
|
150 |
|
151 bool imgRequest::HasCacheEntry() const |
|
152 { |
|
153 return mCacheEntry != nullptr; |
|
154 } |
|
155 |
|
156 void imgRequest::ResetCacheEntry() |
|
157 { |
|
158 if (HasCacheEntry()) { |
|
159 mCacheEntry->SetDataSize(0); |
|
160 } |
|
161 } |
|
162 |
|
163 void imgRequest::AddProxy(imgRequestProxy *proxy) |
|
164 { |
|
165 NS_PRECONDITION(proxy, "null imgRequestProxy passed in"); |
|
166 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::AddProxy", "proxy", proxy); |
|
167 |
|
168 // If we're empty before adding, we have to tell the loader we now have |
|
169 // proxies. |
|
170 nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker(); |
|
171 if (statusTracker->ConsumerCount() == 0) { |
|
172 NS_ABORT_IF_FALSE(mURI, "Trying to SetHasProxies without key uri."); |
|
173 mLoader->SetHasProxies(mFirstPartyIsolationURI, mURI); |
|
174 } |
|
175 |
|
176 statusTracker->AddConsumer(proxy); |
|
177 } |
|
178 |
|
179 nsresult imgRequest::RemoveProxy(imgRequestProxy *proxy, nsresult aStatus) |
|
180 { |
|
181 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::RemoveProxy", "proxy", proxy); |
|
182 |
|
183 // This will remove our animation consumers, so after removing |
|
184 // this proxy, we don't end up without proxies with observers, but still |
|
185 // have animation consumers. |
|
186 proxy->ClearAnimationConsumers(); |
|
187 |
|
188 // Let the status tracker do its thing before we potentially call Cancel() |
|
189 // below, because Cancel() may result in OnStopRequest being called back |
|
190 // before Cancel() returns, leaving the image in a different state then the |
|
191 // one it was in at this point. |
|
192 nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker(); |
|
193 if (!statusTracker->RemoveConsumer(proxy, aStatus)) |
|
194 return NS_OK; |
|
195 |
|
196 if (statusTracker->ConsumerCount() == 0) { |
|
197 // If we have no observers, there's nothing holding us alive. If we haven't |
|
198 // been cancelled and thus removed from the cache, tell the image loader so |
|
199 // we can be evicted from the cache. |
|
200 if (mCacheEntry) { |
|
201 NS_ABORT_IF_FALSE(mURI, "Removing last observer without key uri."); |
|
202 |
|
203 mLoader->SetHasNoProxies(mURI, mCacheEntry); |
|
204 } |
|
205 #if defined(PR_LOGGING) |
|
206 else { |
|
207 nsAutoCString spec; |
|
208 mURI->GetSpec(spec); |
|
209 LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::RemoveProxy no cache entry", "uri", spec.get()); |
|
210 } |
|
211 #endif |
|
212 |
|
213 /* If |aStatus| is a failure code, then cancel the load if it is still in progress. |
|
214 Otherwise, let the load continue, keeping 'this' in the cache with no observers. |
|
215 This way, if a proxy is destroyed without calling cancel on it, it won't leak |
|
216 and won't leave a bad pointer in the observer list. |
|
217 */ |
|
218 if (statusTracker->IsLoading() && NS_FAILED(aStatus)) { |
|
219 LOG_MSG(GetImgLog(), "imgRequest::RemoveProxy", "load in progress. canceling"); |
|
220 |
|
221 this->Cancel(NS_BINDING_ABORTED); |
|
222 } |
|
223 |
|
224 /* break the cycle from the cache entry. */ |
|
225 mCacheEntry = nullptr; |
|
226 } |
|
227 |
|
228 // If a proxy is removed for a reason other than its owner being |
|
229 // changed, remove the proxy from the loadgroup. |
|
230 if (aStatus != NS_IMAGELIB_CHANGING_OWNER) |
|
231 proxy->RemoveFromLoadGroup(true); |
|
232 |
|
233 return NS_OK; |
|
234 } |
|
235 |
|
236 void imgRequest::CancelAndAbort(nsresult aStatus) |
|
237 { |
|
238 LOG_SCOPE(GetImgLog(), "imgRequest::CancelAndAbort"); |
|
239 |
|
240 Cancel(aStatus); |
|
241 |
|
242 // It's possible for the channel to fail to open after we've set our |
|
243 // notification callbacks. In that case, make sure to break the cycle between |
|
244 // the channel and us, because it won't. |
|
245 if (mChannel) { |
|
246 mChannel->SetNotificationCallbacks(mPrevChannelSink); |
|
247 mPrevChannelSink = nullptr; |
|
248 } |
|
249 } |
|
250 |
|
251 class imgRequestMainThreadCancel : public nsRunnable |
|
252 { |
|
253 public: |
|
254 imgRequestMainThreadCancel(imgRequest *aImgRequest, nsresult aStatus) |
|
255 : mImgRequest(aImgRequest) |
|
256 , mStatus(aStatus) |
|
257 { |
|
258 MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!"); |
|
259 MOZ_ASSERT(aImgRequest); |
|
260 } |
|
261 |
|
262 NS_IMETHOD Run() |
|
263 { |
|
264 MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!"); |
|
265 mImgRequest->ContinueCancel(mStatus); |
|
266 return NS_OK; |
|
267 } |
|
268 private: |
|
269 nsRefPtr<imgRequest> mImgRequest; |
|
270 nsresult mStatus; |
|
271 }; |
|
272 |
|
273 void imgRequest::Cancel(nsresult aStatus) |
|
274 { |
|
275 /* The Cancel() method here should only be called by this class. */ |
|
276 |
|
277 LOG_SCOPE(GetImgLog(), "imgRequest::Cancel"); |
|
278 |
|
279 nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker(); |
|
280 |
|
281 statusTracker->MaybeUnblockOnload(); |
|
282 |
|
283 statusTracker->RecordCancel(); |
|
284 |
|
285 if (NS_IsMainThread()) { |
|
286 ContinueCancel(aStatus); |
|
287 } else { |
|
288 NS_DispatchToMainThread(new imgRequestMainThreadCancel(this, aStatus)); |
|
289 } |
|
290 } |
|
291 |
|
292 void imgRequest::ContinueCancel(nsresult aStatus) |
|
293 { |
|
294 MOZ_ASSERT(NS_IsMainThread()); |
|
295 |
|
296 RemoveFromCache(); |
|
297 |
|
298 nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker(); |
|
299 if (mRequest && statusTracker->IsLoading()) { |
|
300 mRequest->Cancel(aStatus); |
|
301 } |
|
302 } |
|
303 |
|
304 nsresult imgRequest::GetURI(ImageURL **aURI) |
|
305 { |
|
306 MOZ_ASSERT(aURI); |
|
307 |
|
308 LOG_FUNC(GetImgLog(), "imgRequest::GetURI"); |
|
309 |
|
310 if (mURI) { |
|
311 *aURI = mURI; |
|
312 NS_ADDREF(*aURI); |
|
313 return NS_OK; |
|
314 } |
|
315 |
|
316 return NS_ERROR_FAILURE; |
|
317 } |
|
318 |
|
319 nsresult imgRequest::GetSecurityInfo(nsISupports **aSecurityInfo) |
|
320 { |
|
321 LOG_FUNC(GetImgLog(), "imgRequest::GetSecurityInfo"); |
|
322 |
|
323 // Missing security info means this is not a security load |
|
324 // i.e. it is not an error when security info is missing |
|
325 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo); |
|
326 return NS_OK; |
|
327 } |
|
328 |
|
329 void imgRequest::RemoveFromCache() |
|
330 { |
|
331 LOG_SCOPE(GetImgLog(), "imgRequest::RemoveFromCache"); |
|
332 |
|
333 if (mIsInCache) { |
|
334 // mCacheEntry is nulled out when we have no more observers. |
|
335 if (mCacheEntry) |
|
336 mLoader->RemoveFromCache(mCacheEntry); |
|
337 else { |
|
338 mLoader->RemoveFromCache(mLoader->GetCacheKey(mFirstPartyIsolationURI, mURI, nullptr), |
|
339 mLoader->GetCache(mURI), |
|
340 mLoader->GetCacheQueue(mURI)); |
|
341 } |
|
342 } |
|
343 |
|
344 mCacheEntry = nullptr; |
|
345 } |
|
346 |
|
347 int32_t imgRequest::Priority() const |
|
348 { |
|
349 int32_t priority = nsISupportsPriority::PRIORITY_NORMAL; |
|
350 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mRequest); |
|
351 if (p) |
|
352 p->GetPriority(&priority); |
|
353 return priority; |
|
354 } |
|
355 |
|
356 void imgRequest::AdjustPriority(imgRequestProxy *proxy, int32_t delta) |
|
357 { |
|
358 // only the first proxy is allowed to modify the priority of this image load. |
|
359 // |
|
360 // XXX(darin): this is probably not the most optimal algorithm as we may want |
|
361 // to increase the priority of requests that have a lot of proxies. the key |
|
362 // concern though is that image loads remain lower priority than other pieces |
|
363 // of content such as link clicks, CSS, and JS. |
|
364 // |
|
365 nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker(); |
|
366 if (!statusTracker->FirstConsumerIs(proxy)) |
|
367 return; |
|
368 |
|
369 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel); |
|
370 if (p) |
|
371 p->AdjustPriority(delta); |
|
372 } |
|
373 |
|
374 void imgRequest::SetIsInCache(bool incache) |
|
375 { |
|
376 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequest::SetIsCacheable", "incache", incache); |
|
377 mIsInCache = incache; |
|
378 } |
|
379 |
|
380 void imgRequest::UpdateCacheEntrySize() |
|
381 { |
|
382 if (mCacheEntry) |
|
383 mCacheEntry->SetDataSize(mImage->SizeOfData()); |
|
384 } |
|
385 |
|
386 void imgRequest::SetCacheValidation(imgCacheEntry* aCacheEntry, nsIRequest* aRequest) |
|
387 { |
|
388 /* get the expires info */ |
|
389 if (aCacheEntry) { |
|
390 nsCOMPtr<nsICachingChannel> cacheChannel(do_QueryInterface(aRequest)); |
|
391 if (cacheChannel) { |
|
392 nsCOMPtr<nsISupports> cacheToken; |
|
393 cacheChannel->GetCacheToken(getter_AddRefs(cacheToken)); |
|
394 if (cacheToken) { |
|
395 nsCOMPtr<nsICacheEntry> entryDesc(do_QueryInterface(cacheToken)); |
|
396 if (entryDesc) { |
|
397 uint32_t expiration; |
|
398 /* get the expiration time from the caching channel's token */ |
|
399 entryDesc->GetExpirationTime(&expiration); |
|
400 |
|
401 // Expiration time defaults to 0. We set the expiration time on our |
|
402 // entry if it hasn't been set yet. |
|
403 if (aCacheEntry->GetExpiryTime() == 0) |
|
404 aCacheEntry->SetExpiryTime(expiration); |
|
405 } |
|
406 } |
|
407 } |
|
408 |
|
409 // Determine whether the cache entry must be revalidated when we try to use it. |
|
410 // Currently, only HTTP specifies this information... |
|
411 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest)); |
|
412 if (httpChannel) { |
|
413 bool bMustRevalidate = false; |
|
414 |
|
415 httpChannel->IsNoStoreResponse(&bMustRevalidate); |
|
416 |
|
417 if (!bMustRevalidate) { |
|
418 httpChannel->IsNoCacheResponse(&bMustRevalidate); |
|
419 } |
|
420 |
|
421 if (!bMustRevalidate) { |
|
422 nsAutoCString cacheHeader; |
|
423 |
|
424 httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Cache-Control"), |
|
425 cacheHeader); |
|
426 if (PL_strcasestr(cacheHeader.get(), "must-revalidate")) { |
|
427 bMustRevalidate = true; |
|
428 } |
|
429 } |
|
430 |
|
431 // Cache entries default to not needing to validate. We ensure that |
|
432 // multiple calls to this function don't override an earlier decision to |
|
433 // validate by making validation a one-way decision. |
|
434 if (bMustRevalidate) |
|
435 aCacheEntry->SetMustValidate(bMustRevalidate); |
|
436 } |
|
437 |
|
438 // We always need to validate file URIs. |
|
439 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); |
|
440 if (channel) { |
|
441 nsCOMPtr<nsIURI> uri; |
|
442 channel->GetURI(getter_AddRefs(uri)); |
|
443 bool isfile = false; |
|
444 uri->SchemeIs("file", &isfile); |
|
445 if (isfile) |
|
446 aCacheEntry->SetMustValidate(isfile); |
|
447 } |
|
448 } |
|
449 } |
|
450 |
|
451 namespace { // anon |
|
452 |
|
453 already_AddRefed<nsIApplicationCache> |
|
454 GetApplicationCache(nsIRequest* aRequest) |
|
455 { |
|
456 nsresult rv; |
|
457 |
|
458 nsCOMPtr<nsIApplicationCacheChannel> appCacheChan = do_QueryInterface(aRequest); |
|
459 if (!appCacheChan) { |
|
460 return nullptr; |
|
461 } |
|
462 |
|
463 bool fromAppCache; |
|
464 rv = appCacheChan->GetLoadedFromApplicationCache(&fromAppCache); |
|
465 NS_ENSURE_SUCCESS(rv, nullptr); |
|
466 |
|
467 if (!fromAppCache) { |
|
468 return nullptr; |
|
469 } |
|
470 |
|
471 nsCOMPtr<nsIApplicationCache> appCache; |
|
472 rv = appCacheChan->GetApplicationCache(getter_AddRefs(appCache)); |
|
473 NS_ENSURE_SUCCESS(rv, nullptr); |
|
474 |
|
475 return appCache.forget(); |
|
476 } |
|
477 |
|
478 } // anon |
|
479 |
|
480 bool |
|
481 imgRequest::CacheChanged(nsIRequest* aNewRequest) |
|
482 { |
|
483 nsCOMPtr<nsIApplicationCache> newAppCache = GetApplicationCache(aNewRequest); |
|
484 |
|
485 // Application cache not involved at all or the same app cache involved |
|
486 // in both of the loads (original and new). |
|
487 if (newAppCache == mApplicationCache) |
|
488 return false; |
|
489 |
|
490 // In a rare case it may happen that two objects still refer |
|
491 // the same application cache version. |
|
492 if (newAppCache && mApplicationCache) { |
|
493 nsresult rv; |
|
494 |
|
495 nsAutoCString oldAppCacheClientId, newAppCacheClientId; |
|
496 rv = mApplicationCache->GetClientID(oldAppCacheClientId); |
|
497 NS_ENSURE_SUCCESS(rv, true); |
|
498 rv = newAppCache->GetClientID(newAppCacheClientId); |
|
499 NS_ENSURE_SUCCESS(rv, true); |
|
500 |
|
501 if (oldAppCacheClientId == newAppCacheClientId) |
|
502 return false; |
|
503 } |
|
504 |
|
505 // When we get here, app caches differ or app cache is involved |
|
506 // just in one of the loads what we also consider as a change |
|
507 // in a loading cache. |
|
508 return true; |
|
509 } |
|
510 |
|
511 nsresult |
|
512 imgRequest::LockImage() |
|
513 { |
|
514 return mImage->LockImage(); |
|
515 } |
|
516 |
|
517 nsresult |
|
518 imgRequest::UnlockImage() |
|
519 { |
|
520 return mImage->UnlockImage(); |
|
521 } |
|
522 |
|
523 nsresult |
|
524 imgRequest::RequestDecode() |
|
525 { |
|
526 // If we've initialized our image, we can request a decode. |
|
527 if (mImage) { |
|
528 return mImage->RequestDecode(); |
|
529 } |
|
530 |
|
531 // Otherwise, flag to do it when we get the image |
|
532 mDecodeRequested = true; |
|
533 |
|
534 return NS_OK; |
|
535 } |
|
536 |
|
537 nsresult |
|
538 imgRequest::StartDecoding() |
|
539 { |
|
540 // If we've initialized our image, we can request a decode. |
|
541 if (mImage) { |
|
542 return mImage->StartDecoding(); |
|
543 } |
|
544 |
|
545 // Otherwise, flag to do it when we get the image |
|
546 mDecodeRequested = true; |
|
547 |
|
548 return NS_OK; |
|
549 } |
|
550 |
|
551 /** nsIRequestObserver methods **/ |
|
552 |
|
553 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */ |
|
554 NS_IMETHODIMP imgRequest::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt) |
|
555 { |
|
556 LOG_SCOPE(GetImgLog(), "imgRequest::OnStartRequest"); |
|
557 |
|
558 // Figure out if we're multipart |
|
559 nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest)); |
|
560 nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker(); |
|
561 if (mpchan) { |
|
562 mIsMultiPartChannel = true; |
|
563 statusTracker->SetIsMultipart(); |
|
564 } |
|
565 |
|
566 // If we're not multipart, we shouldn't have an image yet |
|
567 NS_ABORT_IF_FALSE(mIsMultiPartChannel || !mImage, |
|
568 "Already have an image for non-multipart request"); |
|
569 |
|
570 // If we're multipart and about to load another image, signal so we can |
|
571 // detect the mime type in OnDataAvailable. |
|
572 if (mIsMultiPartChannel && mImage) { |
|
573 mResniffMimeType = true; |
|
574 |
|
575 // Tell the image to reinitialize itself. We have to do this in |
|
576 // OnStartRequest so that its state machine is always in a consistent |
|
577 // state. |
|
578 // Note that if our MIME type changes, mImage will be replaced with a |
|
579 // new object. |
|
580 mImage->OnNewSourceData(); |
|
581 } |
|
582 |
|
583 /* |
|
584 * If mRequest is null here, then we need to set it so that we'll be able to |
|
585 * cancel it if our Cancel() method is called. Note that this can only |
|
586 * happen for multipart channels. We could simply not null out mRequest for |
|
587 * non-last parts, if GetIsLastPart() were reliable, but it's not. See |
|
588 * https://bugzilla.mozilla.org/show_bug.cgi?id=339610 |
|
589 */ |
|
590 if (!mRequest) { |
|
591 NS_ASSERTION(mpchan, |
|
592 "We should have an mRequest here unless we're multipart"); |
|
593 nsCOMPtr<nsIChannel> chan; |
|
594 mpchan->GetBaseChannel(getter_AddRefs(chan)); |
|
595 mRequest = chan; |
|
596 } |
|
597 |
|
598 // Note: refreshing statusTracker in case OnNewSourceData changed it. |
|
599 statusTracker = GetStatusTracker(); |
|
600 statusTracker->OnStartRequest(); |
|
601 |
|
602 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); |
|
603 if (channel) |
|
604 channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); |
|
605 |
|
606 /* Get our principal */ |
|
607 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest)); |
|
608 if (chan) { |
|
609 nsCOMPtr<nsIScriptSecurityManager> secMan = nsContentUtils::GetSecurityManager(); |
|
610 if (secMan) { |
|
611 nsresult rv = secMan->GetChannelPrincipal(chan, |
|
612 getter_AddRefs(mPrincipal)); |
|
613 if (NS_FAILED(rv)) { |
|
614 return rv; |
|
615 } |
|
616 } |
|
617 } |
|
618 |
|
619 SetCacheValidation(mCacheEntry, aRequest); |
|
620 |
|
621 mApplicationCache = GetApplicationCache(aRequest); |
|
622 |
|
623 // Shouldn't we be dead already if this gets hit? Probably multipart/x-mixed-replace... |
|
624 if (statusTracker->ConsumerCount() == 0) { |
|
625 this->Cancel(NS_IMAGELIB_ERROR_FAILURE); |
|
626 } |
|
627 |
|
628 // Try to retarget OnDataAvailable to a decode thread. |
|
629 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); |
|
630 nsCOMPtr<nsIThreadRetargetableRequest> retargetable = |
|
631 do_QueryInterface(aRequest); |
|
632 if (httpChannel && retargetable && |
|
633 ImageFactory::CanRetargetOnDataAvailable(mURI, mIsMultiPartChannel)) { |
|
634 nsAutoCString mimeType; |
|
635 nsresult rv = httpChannel->GetContentType(mimeType); |
|
636 if (NS_SUCCEEDED(rv) && !mimeType.EqualsLiteral(IMAGE_SVG_XML)) { |
|
637 // Image object not created until OnDataAvailable, so forward to static |
|
638 // DecodePool directly. |
|
639 nsCOMPtr<nsIEventTarget> target = RasterImage::GetEventTarget(); |
|
640 rv = retargetable->RetargetDeliveryTo(target); |
|
641 } |
|
642 PR_LOG(GetImgLog(), PR_LOG_WARNING, |
|
643 ("[this=%p] imgRequest::OnStartRequest -- " |
|
644 "RetargetDeliveryTo rv %d=%s\n", |
|
645 this, rv, NS_SUCCEEDED(rv) ? "succeeded" : "failed")); |
|
646 } |
|
647 |
|
648 return NS_OK; |
|
649 } |
|
650 |
|
651 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */ |
|
652 NS_IMETHODIMP imgRequest::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status) |
|
653 { |
|
654 LOG_FUNC(GetImgLog(), "imgRequest::OnStopRequest"); |
|
655 |
|
656 // XXXldb What if this is a non-last part of a multipart request? |
|
657 // xxx before we release our reference to mRequest, lets |
|
658 // save the last status that we saw so that the |
|
659 // imgRequestProxy will have access to it. |
|
660 if (mRequest) { |
|
661 mRequest = nullptr; // we no longer need the request |
|
662 } |
|
663 |
|
664 // stop holding a ref to the channel, since we don't need it anymore |
|
665 if (mChannel) { |
|
666 mChannel->SetNotificationCallbacks(mPrevChannelSink); |
|
667 mPrevChannelSink = nullptr; |
|
668 mChannel = nullptr; |
|
669 } |
|
670 |
|
671 bool lastPart = true; |
|
672 nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest)); |
|
673 if (mpchan) |
|
674 mpchan->GetIsLastPart(&lastPart); |
|
675 |
|
676 // Tell the image that it has all of the source data. Note that this can |
|
677 // trigger a failure, since the image might be waiting for more non-optional |
|
678 // data and this is the point where we break the news that it's not coming. |
|
679 if (mImage) { |
|
680 nsresult rv = mImage->OnImageDataComplete(aRequest, ctxt, status, lastPart); |
|
681 |
|
682 // If we got an error in the OnImageDataComplete() call, we don't want to |
|
683 // proceed as if nothing bad happened. However, we also want to give |
|
684 // precedence to failure status codes from necko, since presumably they're |
|
685 // more meaningful. |
|
686 if (NS_FAILED(rv) && NS_SUCCEEDED(status)) |
|
687 status = rv; |
|
688 } |
|
689 |
|
690 // If the request went through, update the cache entry size. Otherwise, |
|
691 // cancel the request, which removes us from the cache. |
|
692 if (mImage && NS_SUCCEEDED(status)) { |
|
693 // We update the cache entry size here because this is where we finish |
|
694 // loading compressed source data, which is part of our size calculus. |
|
695 UpdateCacheEntrySize(); |
|
696 } |
|
697 else { |
|
698 // stops animations, removes from cache |
|
699 this->Cancel(status); |
|
700 } |
|
701 |
|
702 if (!mImage) { |
|
703 // We have to fire imgStatusTracker::OnStopRequest ourselves because there's |
|
704 // no image capable of doing so. |
|
705 nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker(); |
|
706 statusTracker->OnStopRequest(lastPart, status); |
|
707 } |
|
708 |
|
709 mTimedChannel = nullptr; |
|
710 return NS_OK; |
|
711 } |
|
712 |
|
713 struct mimetype_closure |
|
714 { |
|
715 nsACString* newType; |
|
716 }; |
|
717 |
|
718 /* prototype for these defined below */ |
|
719 static NS_METHOD sniff_mimetype_callback(nsIInputStream* in, void* closure, const char* fromRawSegment, |
|
720 uint32_t toOffset, uint32_t count, uint32_t *writeCount); |
|
721 |
|
722 /** nsThreadRetargetableStreamListener methods **/ |
|
723 NS_IMETHODIMP |
|
724 imgRequest::CheckListenerChain() |
|
725 { |
|
726 // TODO Might need more checking here. |
|
727 NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); |
|
728 return NS_OK; |
|
729 } |
|
730 |
|
731 /** nsIStreamListener methods **/ |
|
732 |
|
733 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */ |
|
734 NS_IMETHODIMP |
|
735 imgRequest::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, |
|
736 nsIInputStream *inStr, uint64_t sourceOffset, |
|
737 uint32_t count) |
|
738 { |
|
739 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "count", count); |
|
740 |
|
741 NS_ASSERTION(aRequest, "imgRequest::OnDataAvailable -- no request!"); |
|
742 |
|
743 nsresult rv; |
|
744 |
|
745 if (!mGotData || mResniffMimeType) { |
|
746 LOG_SCOPE(GetImgLog(), "imgRequest::OnDataAvailable |First time through... finding mimetype|"); |
|
747 |
|
748 mGotData = true; |
|
749 |
|
750 // Store and reset this for the invariant that it's always false after |
|
751 // calls to OnDataAvailable (see bug 907575) |
|
752 bool resniffMimeType = mResniffMimeType; |
|
753 mResniffMimeType = false; |
|
754 |
|
755 mimetype_closure closure; |
|
756 nsAutoCString newType; |
|
757 closure.newType = &newType; |
|
758 |
|
759 /* look at the first few bytes and see if we can tell what the data is from that |
|
760 * since servers tend to lie. :( |
|
761 */ |
|
762 uint32_t out; |
|
763 inStr->ReadSegments(sniff_mimetype_callback, &closure, count, &out); |
|
764 |
|
765 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest)); |
|
766 if (newType.IsEmpty()) { |
|
767 LOG_SCOPE(GetImgLog(), "imgRequest::OnDataAvailable |sniffing of mimetype failed|"); |
|
768 |
|
769 rv = NS_ERROR_FAILURE; |
|
770 if (chan) { |
|
771 rv = chan->GetContentType(newType); |
|
772 } |
|
773 |
|
774 if (NS_FAILED(rv)) { |
|
775 PR_LOG(GetImgLog(), PR_LOG_ERROR, |
|
776 ("[this=%p] imgRequest::OnDataAvailable -- Content type unavailable from the channel\n", |
|
777 this)); |
|
778 |
|
779 this->Cancel(NS_IMAGELIB_ERROR_FAILURE); |
|
780 |
|
781 return NS_BINDING_ABORTED; |
|
782 } |
|
783 |
|
784 LOG_MSG(GetImgLog(), "imgRequest::OnDataAvailable", "Got content type from the channel"); |
|
785 } |
|
786 |
|
787 // If we're a regular image and this is the first call to OnDataAvailable, |
|
788 // this will always be true. If we've resniffed our MIME type (i.e. we're a |
|
789 // multipart/x-mixed-replace image), we have to be able to switch our image |
|
790 // type and decoder. |
|
791 // We always reinitialize for SVGs, because they have no way of |
|
792 // reinitializing themselves. |
|
793 if (mContentType != newType || newType.EqualsLiteral(IMAGE_SVG_XML)) { |
|
794 mContentType = newType; |
|
795 |
|
796 // If we've resniffed our MIME type and it changed, we need to create a |
|
797 // new status tracker to give to the image, because we don't have one of |
|
798 // our own any more. |
|
799 if (resniffMimeType) { |
|
800 NS_ABORT_IF_FALSE(mIsMultiPartChannel, "Resniffing a non-multipart image"); |
|
801 |
|
802 nsRefPtr<imgStatusTracker> freshTracker = new imgStatusTracker(nullptr); |
|
803 nsRefPtr<imgStatusTracker> oldStatusTracker = GetStatusTracker(); |
|
804 freshTracker->AdoptConsumers(oldStatusTracker); |
|
805 mStatusTracker = freshTracker.forget(); |
|
806 } |
|
807 |
|
808 SetProperties(chan); |
|
809 |
|
810 LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "content type", mContentType.get()); |
|
811 |
|
812 // XXX If server lied about mimetype and it's SVG, we may need to copy |
|
813 // the data and dispatch back to the main thread, AND tell the channel to |
|
814 // dispatch there in the future. |
|
815 |
|
816 // Now we can create a new image to hold the data. If we don't have a decoder |
|
817 // for this mimetype we'll find out about it here. |
|
818 mImage = ImageFactory::CreateImage(aRequest, mStatusTracker, mContentType, |
|
819 mURI, mIsMultiPartChannel, |
|
820 static_cast<uint32_t>(mInnerWindowId)); |
|
821 |
|
822 // Release our copy of the status tracker since the image owns it now. |
|
823 mStatusTracker = nullptr; |
|
824 |
|
825 // Notify listeners that we have an image. |
|
826 // XXX(seth): The name of this notification method is pretty misleading. |
|
827 nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker(); |
|
828 statusTracker->OnDataAvailable(); |
|
829 |
|
830 if (mImage->HasError() && !mIsMultiPartChannel) { // Probably bad mimetype |
|
831 // We allow multipart images to fail to initialize without cancelling the |
|
832 // load because subsequent images might be fine; thus only single part |
|
833 // images end up here. |
|
834 this->Cancel(NS_ERROR_FAILURE); |
|
835 return NS_BINDING_ABORTED; |
|
836 } |
|
837 |
|
838 NS_ABORT_IF_FALSE(statusTracker->HasImage(), "Status tracker should have an image!"); |
|
839 NS_ABORT_IF_FALSE(mImage, "imgRequest should have an image!"); |
|
840 |
|
841 if (mDecodeRequested) |
|
842 mImage->StartDecoding(); |
|
843 } |
|
844 } |
|
845 |
|
846 // Notify the image that it has new data. |
|
847 rv = mImage->OnImageDataAvailable(aRequest, ctxt, inStr, sourceOffset, count); |
|
848 |
|
849 if (NS_FAILED(rv)) { |
|
850 PR_LOG(GetImgLog(), PR_LOG_WARNING, |
|
851 ("[this=%p] imgRequest::OnDataAvailable -- " |
|
852 "copy to RasterImage failed\n", this)); |
|
853 this->Cancel(NS_IMAGELIB_ERROR_FAILURE); |
|
854 return NS_BINDING_ABORTED; |
|
855 } |
|
856 |
|
857 return NS_OK; |
|
858 } |
|
859 |
|
860 class SetPropertiesEvent : public nsRunnable |
|
861 { |
|
862 public: |
|
863 SetPropertiesEvent(imgRequest* aImgRequest, nsIChannel* aChan) |
|
864 : mImgRequest(aImgRequest) |
|
865 , mChan(aChan) |
|
866 { |
|
867 MOZ_ASSERT(!NS_IsMainThread(), "Should be created off the main thread"); |
|
868 MOZ_ASSERT(aImgRequest, "aImgRequest cannot be null"); |
|
869 } |
|
870 NS_IMETHOD Run() |
|
871 { |
|
872 MOZ_ASSERT(NS_IsMainThread(), "Should run on the main thread only"); |
|
873 MOZ_ASSERT(mImgRequest, "mImgRequest cannot be null"); |
|
874 mImgRequest->SetProperties(mChan); |
|
875 return NS_OK; |
|
876 } |
|
877 private: |
|
878 nsRefPtr<imgRequest> mImgRequest; |
|
879 nsCOMPtr<nsIChannel> mChan; |
|
880 }; |
|
881 |
|
882 void |
|
883 imgRequest::SetProperties(nsIChannel* aChan) |
|
884 { |
|
885 // Force execution on main thread since some property objects are non |
|
886 // threadsafe. |
|
887 if (!NS_IsMainThread()) { |
|
888 NS_DispatchToMainThread(new SetPropertiesEvent(this, aChan)); |
|
889 return; |
|
890 } |
|
891 /* set our mimetype as a property */ |
|
892 nsCOMPtr<nsISupportsCString> contentType(do_CreateInstance("@mozilla.org/supports-cstring;1")); |
|
893 if (contentType) { |
|
894 contentType->SetData(mContentType); |
|
895 mProperties->Set("type", contentType); |
|
896 } |
|
897 |
|
898 /* set our content disposition as a property */ |
|
899 nsAutoCString disposition; |
|
900 if (aChan) { |
|
901 aChan->GetContentDispositionHeader(disposition); |
|
902 } |
|
903 if (!disposition.IsEmpty()) { |
|
904 nsCOMPtr<nsISupportsCString> contentDisposition(do_CreateInstance("@mozilla.org/supports-cstring;1")); |
|
905 if (contentDisposition) { |
|
906 contentDisposition->SetData(disposition); |
|
907 mProperties->Set("content-disposition", contentDisposition); |
|
908 } |
|
909 } |
|
910 } |
|
911 |
|
912 static NS_METHOD sniff_mimetype_callback(nsIInputStream* in, |
|
913 void* data, |
|
914 const char* fromRawSegment, |
|
915 uint32_t toOffset, |
|
916 uint32_t count, |
|
917 uint32_t *writeCount) |
|
918 { |
|
919 mimetype_closure* closure = static_cast<mimetype_closure*>(data); |
|
920 |
|
921 NS_ASSERTION(closure, "closure is null!"); |
|
922 |
|
923 if (count > 0) |
|
924 imgLoader::GetMimeTypeFromContent(fromRawSegment, count, *closure->newType); |
|
925 |
|
926 *writeCount = 0; |
|
927 return NS_ERROR_FAILURE; |
|
928 } |
|
929 |
|
930 |
|
931 /** nsIInterfaceRequestor methods **/ |
|
932 |
|
933 NS_IMETHODIMP |
|
934 imgRequest::GetInterface(const nsIID & aIID, void **aResult) |
|
935 { |
|
936 if (!mPrevChannelSink || aIID.Equals(NS_GET_IID(nsIChannelEventSink))) |
|
937 return QueryInterface(aIID, aResult); |
|
938 |
|
939 NS_ASSERTION(mPrevChannelSink != this, |
|
940 "Infinite recursion - don't keep track of channel sinks that are us!"); |
|
941 return mPrevChannelSink->GetInterface(aIID, aResult); |
|
942 } |
|
943 |
|
944 /** nsIChannelEventSink methods **/ |
|
945 NS_IMETHODIMP |
|
946 imgRequest::AsyncOnChannelRedirect(nsIChannel *oldChannel, |
|
947 nsIChannel *newChannel, uint32_t flags, |
|
948 nsIAsyncVerifyRedirectCallback *callback) |
|
949 { |
|
950 NS_ASSERTION(mRequest && mChannel, "Got a channel redirect after we nulled out mRequest!"); |
|
951 NS_ASSERTION(mChannel == oldChannel, "Got a channel redirect for an unknown channel!"); |
|
952 NS_ASSERTION(newChannel, "Got a redirect to a NULL channel!"); |
|
953 |
|
954 SetCacheValidation(mCacheEntry, oldChannel); |
|
955 |
|
956 // Prepare for callback |
|
957 mRedirectCallback = callback; |
|
958 mNewRedirectChannel = newChannel; |
|
959 |
|
960 nsCOMPtr<nsIChannelEventSink> sink(do_GetInterface(mPrevChannelSink)); |
|
961 if (sink) { |
|
962 nsresult rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, |
|
963 this); |
|
964 if (NS_FAILED(rv)) { |
|
965 mRedirectCallback = nullptr; |
|
966 mNewRedirectChannel = nullptr; |
|
967 } |
|
968 return rv; |
|
969 } |
|
970 |
|
971 (void) OnRedirectVerifyCallback(NS_OK); |
|
972 return NS_OK; |
|
973 } |
|
974 |
|
975 NS_IMETHODIMP |
|
976 imgRequest::OnRedirectVerifyCallback(nsresult result) |
|
977 { |
|
978 NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback"); |
|
979 NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback"); |
|
980 |
|
981 if (NS_FAILED(result)) { |
|
982 mRedirectCallback->OnRedirectVerifyCallback(result); |
|
983 mRedirectCallback = nullptr; |
|
984 mNewRedirectChannel = nullptr; |
|
985 return NS_OK; |
|
986 } |
|
987 |
|
988 mChannel = mNewRedirectChannel; |
|
989 mTimedChannel = do_QueryInterface(mChannel); |
|
990 mNewRedirectChannel = nullptr; |
|
991 |
|
992 #if defined(PR_LOGGING) |
|
993 nsAutoCString oldspec; |
|
994 if (mCurrentURI) |
|
995 mCurrentURI->GetSpec(oldspec); |
|
996 LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnChannelRedirect", "old", oldspec.get()); |
|
997 #endif |
|
998 |
|
999 // make sure we have a protocol that returns data rather than opens |
|
1000 // an external application, e.g. mailto: |
|
1001 mChannel->GetURI(getter_AddRefs(mCurrentURI)); |
|
1002 bool doesNotReturnData = false; |
|
1003 nsresult rv = |
|
1004 NS_URIChainHasFlags(mCurrentURI, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, |
|
1005 &doesNotReturnData); |
|
1006 |
|
1007 if (NS_SUCCEEDED(rv) && doesNotReturnData) |
|
1008 rv = NS_ERROR_ABORT; |
|
1009 |
|
1010 if (NS_FAILED(rv)) { |
|
1011 mRedirectCallback->OnRedirectVerifyCallback(rv); |
|
1012 mRedirectCallback = nullptr; |
|
1013 return NS_OK; |
|
1014 } |
|
1015 |
|
1016 mRedirectCallback->OnRedirectVerifyCallback(NS_OK); |
|
1017 mRedirectCallback = nullptr; |
|
1018 return NS_OK; |
|
1019 } |