|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 // vim: ft=cpp tw=78 sw=2 et ts=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 /* |
|
8 * A base class which implements nsIImageLoadingContent and can be |
|
9 * subclassed by various content nodes that want to provide image |
|
10 * loading functionality (eg <img>, <object>, etc). |
|
11 */ |
|
12 |
|
13 #include "nsImageLoadingContent.h" |
|
14 #include "nsAutoPtr.h" |
|
15 #include "nsError.h" |
|
16 #include "nsIContent.h" |
|
17 #include "nsIDocument.h" |
|
18 #include "nsIScriptGlobalObject.h" |
|
19 #include "nsIDOMWindow.h" |
|
20 #include "nsServiceManagerUtils.h" |
|
21 #include "nsContentPolicyUtils.h" |
|
22 #include "nsIURI.h" |
|
23 #include "nsILoadGroup.h" |
|
24 #include "imgIContainer.h" |
|
25 #include "imgLoader.h" |
|
26 #include "imgRequestProxy.h" |
|
27 #include "nsThreadUtils.h" |
|
28 #include "nsNetUtil.h" |
|
29 #include "nsImageFrame.h" |
|
30 |
|
31 #include "nsIPresShell.h" |
|
32 |
|
33 #include "nsIChannel.h" |
|
34 #include "nsIStreamListener.h" |
|
35 |
|
36 #include "nsIFrame.h" |
|
37 #include "nsIDOMNode.h" |
|
38 |
|
39 #include "nsContentUtils.h" |
|
40 #include "nsLayoutUtils.h" |
|
41 #include "nsIContentPolicy.h" |
|
42 #include "nsSVGEffects.h" |
|
43 |
|
44 #include "mozAutoDocUpdate.h" |
|
45 #include "mozilla/AsyncEventDispatcher.h" |
|
46 #include "mozilla/EventStates.h" |
|
47 #include "mozilla/dom/Element.h" |
|
48 #include "mozilla/dom/ScriptSettings.h" |
|
49 |
|
50 #ifdef LoadImage |
|
51 // Undefine LoadImage to prevent naming conflict with Windows. |
|
52 #undef LoadImage |
|
53 #endif |
|
54 |
|
55 using namespace mozilla; |
|
56 |
|
57 #ifdef DEBUG_chb |
|
58 static void PrintReqURL(imgIRequest* req) { |
|
59 if (!req) { |
|
60 printf("(null req)\n"); |
|
61 return; |
|
62 } |
|
63 |
|
64 nsCOMPtr<nsIURI> uri; |
|
65 req->GetURI(getter_AddRefs(uri)); |
|
66 if (!uri) { |
|
67 printf("(null uri)\n"); |
|
68 return; |
|
69 } |
|
70 |
|
71 nsAutoCString spec; |
|
72 uri->GetSpec(spec); |
|
73 printf("spec='%s'\n", spec.get()); |
|
74 } |
|
75 #endif /* DEBUG_chb */ |
|
76 |
|
77 |
|
78 nsImageLoadingContent::nsImageLoadingContent() |
|
79 : mCurrentRequestFlags(0), |
|
80 mPendingRequestFlags(0), |
|
81 mObserverList(nullptr), |
|
82 mImageBlockingStatus(nsIContentPolicy::ACCEPT), |
|
83 mLoadingEnabled(true), |
|
84 mIsImageStateForced(false), |
|
85 mLoading(false), |
|
86 // mBroken starts out true, since an image without a URI is broken.... |
|
87 mBroken(true), |
|
88 mUserDisabled(false), |
|
89 mSuppressed(false), |
|
90 mFireEventsOnDecode(false), |
|
91 mNewRequestsWillNeedAnimationReset(false), |
|
92 mStateChangerDepth(0), |
|
93 mCurrentRequestRegistered(false), |
|
94 mPendingRequestRegistered(false), |
|
95 mFrameCreateCalled(false), |
|
96 mVisibleCount(0) |
|
97 { |
|
98 if (!nsContentUtils::GetImgLoaderForChannel(nullptr)) { |
|
99 mLoadingEnabled = false; |
|
100 } |
|
101 } |
|
102 |
|
103 void |
|
104 nsImageLoadingContent::DestroyImageLoadingContent() |
|
105 { |
|
106 // Cancel our requests so they won't hold stale refs to us |
|
107 // NB: Don't ask to discard the images here. |
|
108 ClearCurrentRequest(NS_BINDING_ABORTED, 0); |
|
109 ClearPendingRequest(NS_BINDING_ABORTED, 0); |
|
110 } |
|
111 |
|
112 nsImageLoadingContent::~nsImageLoadingContent() |
|
113 { |
|
114 NS_ASSERTION(!mCurrentRequest && !mPendingRequest, |
|
115 "DestroyImageLoadingContent not called"); |
|
116 NS_ASSERTION(!mObserverList.mObserver && !mObserverList.mNext, |
|
117 "Observers still registered?"); |
|
118 } |
|
119 |
|
120 /* |
|
121 * imgINotificationObserver impl |
|
122 */ |
|
123 NS_IMETHODIMP |
|
124 nsImageLoadingContent::Notify(imgIRequest* aRequest, |
|
125 int32_t aType, |
|
126 const nsIntRect* aData) |
|
127 { |
|
128 if (aType == imgINotificationObserver::IS_ANIMATED) { |
|
129 return OnImageIsAnimated(aRequest); |
|
130 } |
|
131 |
|
132 if (aType == imgINotificationObserver::UNLOCKED_DRAW) { |
|
133 OnUnlockedDraw(); |
|
134 return NS_OK; |
|
135 } |
|
136 |
|
137 if (aType == imgINotificationObserver::LOAD_COMPLETE) { |
|
138 // We should definitely have a request here |
|
139 NS_ABORT_IF_FALSE(aRequest, "no request?"); |
|
140 |
|
141 NS_PRECONDITION(aRequest == mCurrentRequest || aRequest == mPendingRequest, |
|
142 "Unknown request"); |
|
143 } |
|
144 |
|
145 { |
|
146 nsAutoScriptBlocker scriptBlocker; |
|
147 |
|
148 for (ImageObserver* observer = &mObserverList, *next; observer; |
|
149 observer = next) { |
|
150 next = observer->mNext; |
|
151 if (observer->mObserver) { |
|
152 observer->mObserver->Notify(aRequest, aType, aData); |
|
153 } |
|
154 } |
|
155 } |
|
156 |
|
157 if (aType == imgINotificationObserver::SIZE_AVAILABLE) { |
|
158 // Have to check for state changes here, since we might have been in |
|
159 // the LOADING state before. |
|
160 UpdateImageState(true); |
|
161 } |
|
162 |
|
163 if (aType == imgINotificationObserver::LOAD_COMPLETE) { |
|
164 uint32_t reqStatus; |
|
165 aRequest->GetImageStatus(&reqStatus); |
|
166 nsresult status = |
|
167 reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK; |
|
168 return OnStopRequest(aRequest, status); |
|
169 } |
|
170 |
|
171 if (aType == imgINotificationObserver::DECODE_COMPLETE && mFireEventsOnDecode) { |
|
172 mFireEventsOnDecode = false; |
|
173 |
|
174 uint32_t reqStatus; |
|
175 aRequest->GetImageStatus(&reqStatus); |
|
176 if (reqStatus & imgIRequest::STATUS_ERROR) { |
|
177 FireEvent(NS_LITERAL_STRING("error")); |
|
178 } else { |
|
179 FireEvent(NS_LITERAL_STRING("load")); |
|
180 } |
|
181 |
|
182 UpdateImageState(true); |
|
183 } |
|
184 |
|
185 return NS_OK; |
|
186 } |
|
187 |
|
188 nsresult |
|
189 nsImageLoadingContent::OnStopRequest(imgIRequest* aRequest, |
|
190 nsresult aStatus) |
|
191 { |
|
192 uint32_t oldStatus; |
|
193 aRequest->GetImageStatus(&oldStatus); |
|
194 |
|
195 //XXXjdm This occurs when we have a pending request created, then another |
|
196 // pending request replaces it before the first one is finished. |
|
197 // This begs the question of what the correct behaviour is; we used |
|
198 // to not have to care because we ran this code in OnStopDecode which |
|
199 // wasn't called when the first request was cancelled. For now, I choose |
|
200 // to punt when the given request doesn't appear to have terminated in |
|
201 // an expected state. |
|
202 if (!(oldStatus & (imgIRequest::STATUS_ERROR | imgIRequest::STATUS_LOAD_COMPLETE))) |
|
203 return NS_OK; |
|
204 |
|
205 // Our state may change. Watch it. |
|
206 AutoStateChanger changer(this, true); |
|
207 |
|
208 // If the pending request is loaded, switch to it. |
|
209 if (aRequest == mPendingRequest) { |
|
210 MakePendingRequestCurrent(); |
|
211 } |
|
212 NS_ABORT_IF_FALSE(aRequest == mCurrentRequest, |
|
213 "One way or another, we should be current by now"); |
|
214 |
|
215 // We just loaded all the data we're going to get. If we're visible and |
|
216 // haven't done an initial paint (*), we want to make sure the image starts |
|
217 // decoding immediately, for two reasons: |
|
218 // |
|
219 // 1) This image is sitting idle but might need to be decoded as soon as we |
|
220 // start painting, in which case we've wasted time. |
|
221 // |
|
222 // 2) We want to block onload until all visible images are decoded. We do this |
|
223 // by blocking onload until all in-progress decodes get at least one frame |
|
224 // decoded. However, if all the data comes in while painting is suppressed |
|
225 // (ie, before the initial paint delay is finished), we fire onload without |
|
226 // doing a paint first. This means that decode-on-draw images don't start |
|
227 // decoding, so we can't wait for them to finish. See bug 512435. |
|
228 // |
|
229 // (*) IsPaintingSuppressed returns false if we haven't gotten the initial |
|
230 // reflow yet, so we have to test !DidInitialize || IsPaintingSuppressed. |
|
231 // It's possible for painting to be suppressed for reasons other than the |
|
232 // initial paint delay (for example, being in the bfcache), but we probably |
|
233 // aren't loading images in those situations. |
|
234 |
|
235 // XXXkhuey should this be GetOurCurrentDoc? Decoding if we're not in |
|
236 // the document seems silly. |
|
237 bool startedDecoding = false; |
|
238 nsIDocument* doc = GetOurOwnerDoc(); |
|
239 nsIPresShell* shell = doc ? doc->GetShell() : nullptr; |
|
240 if (shell && shell->IsVisible() && |
|
241 (!shell->DidInitialize() || shell->IsPaintingSuppressed())) { |
|
242 |
|
243 // If we've gotten a frame and that frame has called FrameCreate and that |
|
244 // frame has been reflowed then we know that it checked it's own visibility |
|
245 // so we can trust our visible count and we don't start decode if we are not |
|
246 // visible. |
|
247 nsIFrame* f = GetOurPrimaryFrame(); |
|
248 if (!mFrameCreateCalled || !f || (f->GetStateBits() & NS_FRAME_FIRST_REFLOW) || |
|
249 mVisibleCount > 0 || shell->AssumeAllImagesVisible()) { |
|
250 if (NS_SUCCEEDED(mCurrentRequest->StartDecoding())) { |
|
251 startedDecoding = true; |
|
252 } |
|
253 } |
|
254 } |
|
255 |
|
256 // We want to give the decoder a chance to find errors. If we haven't found |
|
257 // an error yet and we've started decoding, either from the above |
|
258 // StartDecoding or from some other place, we must only fire these events |
|
259 // after we finish decoding. |
|
260 uint32_t reqStatus; |
|
261 aRequest->GetImageStatus(&reqStatus); |
|
262 if (NS_SUCCEEDED(aStatus) && !(reqStatus & imgIRequest::STATUS_ERROR) && |
|
263 (reqStatus & imgIRequest::STATUS_DECODE_STARTED || |
|
264 (startedDecoding && !(reqStatus & imgIRequest::STATUS_DECODE_COMPLETE)))) { |
|
265 mFireEventsOnDecode = true; |
|
266 } else { |
|
267 // Fire the appropriate DOM event. |
|
268 if (NS_SUCCEEDED(aStatus)) { |
|
269 FireEvent(NS_LITERAL_STRING("load")); |
|
270 } else { |
|
271 FireEvent(NS_LITERAL_STRING("error")); |
|
272 } |
|
273 } |
|
274 |
|
275 nsCOMPtr<nsINode> thisNode = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this)); |
|
276 nsSVGEffects::InvalidateDirectRenderingObservers(thisNode->AsElement()); |
|
277 |
|
278 return NS_OK; |
|
279 } |
|
280 |
|
281 void |
|
282 nsImageLoadingContent::OnUnlockedDraw() |
|
283 { |
|
284 if (mVisibleCount > 0) { |
|
285 // We should already be marked as visible, there is nothing more we can do. |
|
286 return; |
|
287 } |
|
288 |
|
289 nsPresContext* presContext = GetFramePresContext(); |
|
290 if (!presContext) |
|
291 return; |
|
292 |
|
293 nsIPresShell* presShell = presContext->PresShell(); |
|
294 if (!presShell) |
|
295 return; |
|
296 |
|
297 presShell->EnsureImageInVisibleList(this); |
|
298 } |
|
299 |
|
300 nsresult |
|
301 nsImageLoadingContent::OnImageIsAnimated(imgIRequest *aRequest) |
|
302 { |
|
303 bool* requestFlag = GetRegisteredFlagForRequest(aRequest); |
|
304 if (requestFlag) { |
|
305 nsLayoutUtils::RegisterImageRequest(GetFramePresContext(), |
|
306 aRequest, requestFlag); |
|
307 } |
|
308 |
|
309 return NS_OK; |
|
310 } |
|
311 |
|
312 /* |
|
313 * nsIImageLoadingContent impl |
|
314 */ |
|
315 |
|
316 NS_IMETHODIMP |
|
317 nsImageLoadingContent::GetLoadingEnabled(bool *aLoadingEnabled) |
|
318 { |
|
319 *aLoadingEnabled = mLoadingEnabled; |
|
320 return NS_OK; |
|
321 } |
|
322 |
|
323 NS_IMETHODIMP |
|
324 nsImageLoadingContent::SetLoadingEnabled(bool aLoadingEnabled) |
|
325 { |
|
326 if (nsContentUtils::GetImgLoaderForChannel(nullptr)) { |
|
327 mLoadingEnabled = aLoadingEnabled; |
|
328 } |
|
329 return NS_OK; |
|
330 } |
|
331 |
|
332 NS_IMETHODIMP |
|
333 nsImageLoadingContent::GetImageBlockingStatus(int16_t* aStatus) |
|
334 { |
|
335 NS_PRECONDITION(aStatus, "Null out param"); |
|
336 *aStatus = ImageBlockingStatus(); |
|
337 return NS_OK; |
|
338 } |
|
339 |
|
340 NS_IMETHODIMP |
|
341 nsImageLoadingContent::AddObserver(imgINotificationObserver* aObserver) |
|
342 { |
|
343 NS_ENSURE_ARG_POINTER(aObserver); |
|
344 |
|
345 if (!mObserverList.mObserver) { |
|
346 mObserverList.mObserver = aObserver; |
|
347 // Don't touch the linking of the list! |
|
348 return NS_OK; |
|
349 } |
|
350 |
|
351 // otherwise we have to create a new entry |
|
352 |
|
353 ImageObserver* observer = &mObserverList; |
|
354 while (observer->mNext) { |
|
355 observer = observer->mNext; |
|
356 } |
|
357 |
|
358 observer->mNext = new ImageObserver(aObserver); |
|
359 if (! observer->mNext) { |
|
360 return NS_ERROR_OUT_OF_MEMORY; |
|
361 } |
|
362 |
|
363 return NS_OK; |
|
364 } |
|
365 |
|
366 NS_IMETHODIMP |
|
367 nsImageLoadingContent::RemoveObserver(imgINotificationObserver* aObserver) |
|
368 { |
|
369 NS_ENSURE_ARG_POINTER(aObserver); |
|
370 |
|
371 if (mObserverList.mObserver == aObserver) { |
|
372 mObserverList.mObserver = nullptr; |
|
373 // Don't touch the linking of the list! |
|
374 return NS_OK; |
|
375 } |
|
376 |
|
377 // otherwise have to find it and splice it out |
|
378 ImageObserver* observer = &mObserverList; |
|
379 while (observer->mNext && observer->mNext->mObserver != aObserver) { |
|
380 observer = observer->mNext; |
|
381 } |
|
382 |
|
383 // At this point, we are pointing to the list element whose mNext is |
|
384 // the right observer (assuming of course that mNext is not null) |
|
385 if (observer->mNext) { |
|
386 // splice it out |
|
387 ImageObserver* oldObserver = observer->mNext; |
|
388 observer->mNext = oldObserver->mNext; |
|
389 oldObserver->mNext = nullptr; // so we don't destroy them all |
|
390 delete oldObserver; |
|
391 } |
|
392 #ifdef DEBUG |
|
393 else { |
|
394 NS_WARNING("Asked to remove nonexistent observer"); |
|
395 } |
|
396 #endif |
|
397 return NS_OK; |
|
398 } |
|
399 |
|
400 already_AddRefed<imgIRequest> |
|
401 nsImageLoadingContent::GetRequest(int32_t aRequestType, |
|
402 ErrorResult& aError) |
|
403 { |
|
404 nsCOMPtr<imgIRequest> request; |
|
405 switch(aRequestType) { |
|
406 case CURRENT_REQUEST: |
|
407 request = mCurrentRequest; |
|
408 break; |
|
409 case PENDING_REQUEST: |
|
410 request = mPendingRequest; |
|
411 break; |
|
412 default: |
|
413 NS_ERROR("Unknown request type"); |
|
414 aError.Throw(NS_ERROR_UNEXPECTED); |
|
415 } |
|
416 |
|
417 return request.forget(); |
|
418 } |
|
419 |
|
420 NS_IMETHODIMP |
|
421 nsImageLoadingContent::GetRequest(int32_t aRequestType, |
|
422 imgIRequest** aRequest) |
|
423 { |
|
424 NS_ENSURE_ARG_POINTER(aRequest); |
|
425 |
|
426 ErrorResult result; |
|
427 *aRequest = GetRequest(aRequestType, result).take(); |
|
428 |
|
429 return result.ErrorCode(); |
|
430 } |
|
431 |
|
432 NS_IMETHODIMP_(void) |
|
433 nsImageLoadingContent::FrameCreated(nsIFrame* aFrame) |
|
434 { |
|
435 NS_ASSERTION(aFrame, "aFrame is null"); |
|
436 |
|
437 mFrameCreateCalled = true; |
|
438 |
|
439 if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) { |
|
440 // Assume all images in popups are visible. |
|
441 IncrementVisibleCount(); |
|
442 } |
|
443 |
|
444 TrackImage(mCurrentRequest); |
|
445 TrackImage(mPendingRequest); |
|
446 |
|
447 // We need to make sure that our image request is registered, if it should |
|
448 // be registered. |
|
449 nsPresContext* presContext = aFrame->PresContext(); |
|
450 if (mCurrentRequest) { |
|
451 nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, mCurrentRequest, |
|
452 &mCurrentRequestRegistered); |
|
453 } |
|
454 |
|
455 if (mPendingRequest) { |
|
456 nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, mPendingRequest, |
|
457 &mPendingRequestRegistered); |
|
458 } |
|
459 } |
|
460 |
|
461 NS_IMETHODIMP_(void) |
|
462 nsImageLoadingContent::FrameDestroyed(nsIFrame* aFrame) |
|
463 { |
|
464 NS_ASSERTION(aFrame, "aFrame is null"); |
|
465 |
|
466 mFrameCreateCalled = false; |
|
467 |
|
468 // We need to make sure that our image request is deregistered. |
|
469 nsPresContext* presContext = GetFramePresContext(); |
|
470 if (mCurrentRequest) { |
|
471 nsLayoutUtils::DeregisterImageRequest(presContext, |
|
472 mCurrentRequest, |
|
473 &mCurrentRequestRegistered); |
|
474 } |
|
475 |
|
476 if (mPendingRequest) { |
|
477 nsLayoutUtils::DeregisterImageRequest(presContext, |
|
478 mPendingRequest, |
|
479 &mPendingRequestRegistered); |
|
480 } |
|
481 |
|
482 UntrackImage(mCurrentRequest); |
|
483 UntrackImage(mPendingRequest); |
|
484 |
|
485 nsIPresShell* presShell = presContext ? presContext->GetPresShell() : nullptr; |
|
486 if (presShell) { |
|
487 presShell->RemoveImageFromVisibleList(this); |
|
488 } |
|
489 |
|
490 if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) { |
|
491 // We assume all images in popups are visible, so this decrement balances |
|
492 // out the increment in FrameCreated above. |
|
493 DecrementVisibleCount(); |
|
494 } |
|
495 } |
|
496 |
|
497 int32_t |
|
498 nsImageLoadingContent::GetRequestType(imgIRequest* aRequest, |
|
499 ErrorResult& aError) |
|
500 { |
|
501 if (aRequest == mCurrentRequest) { |
|
502 return CURRENT_REQUEST; |
|
503 } |
|
504 |
|
505 if (aRequest == mPendingRequest) { |
|
506 return PENDING_REQUEST; |
|
507 } |
|
508 |
|
509 NS_ERROR("Unknown request"); |
|
510 aError.Throw(NS_ERROR_UNEXPECTED); |
|
511 return UNKNOWN_REQUEST; |
|
512 } |
|
513 |
|
514 NS_IMETHODIMP |
|
515 nsImageLoadingContent::GetRequestType(imgIRequest* aRequest, |
|
516 int32_t* aRequestType) |
|
517 { |
|
518 NS_PRECONDITION(aRequestType, "Null out param"); |
|
519 |
|
520 ErrorResult result; |
|
521 *aRequestType = GetRequestType(aRequest, result); |
|
522 return result.ErrorCode(); |
|
523 } |
|
524 |
|
525 already_AddRefed<nsIURI> |
|
526 nsImageLoadingContent::GetCurrentURI(ErrorResult& aError) |
|
527 { |
|
528 nsCOMPtr<nsIURI> uri; |
|
529 if (mCurrentRequest) { |
|
530 mCurrentRequest->GetURI(getter_AddRefs(uri)); |
|
531 } else if (mCurrentURI) { |
|
532 nsresult rv = NS_EnsureSafeToReturn(mCurrentURI, getter_AddRefs(uri)); |
|
533 if (NS_FAILED(rv)) { |
|
534 aError.Throw(rv); |
|
535 } |
|
536 } |
|
537 |
|
538 return uri.forget(); |
|
539 } |
|
540 |
|
541 NS_IMETHODIMP |
|
542 nsImageLoadingContent::GetCurrentURI(nsIURI** aURI) |
|
543 { |
|
544 NS_ENSURE_ARG_POINTER(aURI); |
|
545 |
|
546 ErrorResult result; |
|
547 *aURI = GetCurrentURI(result).take(); |
|
548 return result.ErrorCode(); |
|
549 } |
|
550 |
|
551 already_AddRefed<nsIStreamListener> |
|
552 nsImageLoadingContent::LoadImageWithChannel(nsIChannel* aChannel, |
|
553 ErrorResult& aError) |
|
554 { |
|
555 if (!nsContentUtils::GetImgLoaderForChannel(aChannel)) { |
|
556 aError.Throw(NS_ERROR_NULL_POINTER); |
|
557 return nullptr; |
|
558 } |
|
559 |
|
560 nsCOMPtr<nsIDocument> doc = GetOurOwnerDoc(); |
|
561 if (!doc) { |
|
562 // Don't bother |
|
563 return nullptr; |
|
564 } |
|
565 |
|
566 // XXX what should we do with content policies here, if anything? |
|
567 // Shouldn't that be done before the start of the load? |
|
568 // XXX what about shouldProcess? |
|
569 |
|
570 // Our state might change. Watch it. |
|
571 AutoStateChanger changer(this, true); |
|
572 |
|
573 // Do the load. |
|
574 nsCOMPtr<nsIStreamListener> listener; |
|
575 nsRefPtr<imgRequestProxy>& req = PrepareNextRequest(); |
|
576 nsresult rv = nsContentUtils::GetImgLoaderForChannel(aChannel)-> |
|
577 LoadImageWithChannel(aChannel, this, doc, |
|
578 getter_AddRefs(listener), |
|
579 getter_AddRefs(req)); |
|
580 if (NS_SUCCEEDED(rv)) { |
|
581 TrackImage(req); |
|
582 ResetAnimationIfNeeded(); |
|
583 } else { |
|
584 MOZ_ASSERT(!req, "Shouldn't have non-null request here"); |
|
585 // If we don't have a current URI, we might as well store this URI so people |
|
586 // know what we tried (and failed) to load. |
|
587 if (!mCurrentRequest) |
|
588 aChannel->GetURI(getter_AddRefs(mCurrentURI)); |
|
589 FireEvent(NS_LITERAL_STRING("error")); |
|
590 aError.Throw(rv); |
|
591 } |
|
592 return listener.forget(); |
|
593 } |
|
594 |
|
595 NS_IMETHODIMP |
|
596 nsImageLoadingContent::LoadImageWithChannel(nsIChannel* aChannel, |
|
597 nsIStreamListener** aListener) |
|
598 { |
|
599 NS_ENSURE_ARG_POINTER(aListener); |
|
600 |
|
601 ErrorResult result; |
|
602 *aListener = LoadImageWithChannel(aChannel, result).take(); |
|
603 return result.ErrorCode(); |
|
604 } |
|
605 |
|
606 void |
|
607 nsImageLoadingContent::ForceReload(ErrorResult& aError) |
|
608 { |
|
609 nsCOMPtr<nsIURI> currentURI; |
|
610 GetCurrentURI(getter_AddRefs(currentURI)); |
|
611 if (!currentURI) { |
|
612 aError.Throw(NS_ERROR_NOT_AVAILABLE); |
|
613 return; |
|
614 } |
|
615 |
|
616 nsresult rv = LoadImage(currentURI, true, true, nullptr, nsIRequest::VALIDATE_ALWAYS); |
|
617 if (NS_FAILED(rv)) { |
|
618 aError.Throw(rv); |
|
619 } |
|
620 } |
|
621 |
|
622 NS_IMETHODIMP nsImageLoadingContent::ForceReload() |
|
623 { |
|
624 ErrorResult result; |
|
625 ForceReload(result); |
|
626 return result.ErrorCode(); |
|
627 } |
|
628 |
|
629 NS_IMETHODIMP |
|
630 nsImageLoadingContent::BlockOnload(imgIRequest* aRequest) |
|
631 { |
|
632 if (aRequest == mCurrentRequest) { |
|
633 NS_ASSERTION(!(mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD), |
|
634 "Double BlockOnload!?"); |
|
635 mCurrentRequestFlags |= REQUEST_BLOCKS_ONLOAD; |
|
636 } else if (aRequest == mPendingRequest) { |
|
637 NS_ASSERTION(!(mPendingRequestFlags & REQUEST_BLOCKS_ONLOAD), |
|
638 "Double BlockOnload!?"); |
|
639 mPendingRequestFlags |= REQUEST_BLOCKS_ONLOAD; |
|
640 } else { |
|
641 return NS_OK; |
|
642 } |
|
643 |
|
644 nsIDocument* doc = GetOurCurrentDoc(); |
|
645 if (doc) { |
|
646 doc->BlockOnload(); |
|
647 } |
|
648 |
|
649 return NS_OK; |
|
650 } |
|
651 |
|
652 NS_IMETHODIMP |
|
653 nsImageLoadingContent::UnblockOnload(imgIRequest* aRequest) |
|
654 { |
|
655 if (aRequest == mCurrentRequest) { |
|
656 NS_ASSERTION(mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD, |
|
657 "Double UnblockOnload!?"); |
|
658 mCurrentRequestFlags &= ~REQUEST_BLOCKS_ONLOAD; |
|
659 } else if (aRequest == mPendingRequest) { |
|
660 NS_ASSERTION(mPendingRequestFlags & REQUEST_BLOCKS_ONLOAD, |
|
661 "Double UnblockOnload!?"); |
|
662 mPendingRequestFlags &= ~REQUEST_BLOCKS_ONLOAD; |
|
663 } else { |
|
664 return NS_OK; |
|
665 } |
|
666 |
|
667 nsIDocument* doc = GetOurCurrentDoc(); |
|
668 if (doc) { |
|
669 doc->UnblockOnload(false); |
|
670 } |
|
671 |
|
672 return NS_OK; |
|
673 } |
|
674 |
|
675 void |
|
676 nsImageLoadingContent::IncrementVisibleCount() |
|
677 { |
|
678 mVisibleCount++; |
|
679 if (mVisibleCount == 1) { |
|
680 TrackImage(mCurrentRequest); |
|
681 TrackImage(mPendingRequest); |
|
682 } |
|
683 } |
|
684 |
|
685 void |
|
686 nsImageLoadingContent::DecrementVisibleCount() |
|
687 { |
|
688 NS_ASSERTION(mVisibleCount > 0, "visible count should be positive here"); |
|
689 mVisibleCount--; |
|
690 |
|
691 if (mVisibleCount == 0) { |
|
692 UntrackImage(mCurrentRequest); |
|
693 UntrackImage(mPendingRequest); |
|
694 } |
|
695 } |
|
696 |
|
697 uint32_t |
|
698 nsImageLoadingContent::GetVisibleCount() |
|
699 { |
|
700 return mVisibleCount; |
|
701 } |
|
702 |
|
703 /* |
|
704 * Non-interface methods |
|
705 */ |
|
706 |
|
707 nsresult |
|
708 nsImageLoadingContent::LoadImage(const nsAString& aNewURI, |
|
709 bool aForce, |
|
710 bool aNotify) |
|
711 { |
|
712 // First, get a document (needed for security checks and the like) |
|
713 nsIDocument* doc = GetOurOwnerDoc(); |
|
714 if (!doc) { |
|
715 // No reason to bother, I think... |
|
716 return NS_OK; |
|
717 } |
|
718 |
|
719 nsCOMPtr<nsIURI> imageURI; |
|
720 nsresult rv = StringToURI(aNewURI, doc, getter_AddRefs(imageURI)); |
|
721 NS_ENSURE_SUCCESS(rv, rv); |
|
722 // XXXbiesi fire onerror if that failed? |
|
723 |
|
724 bool equal; |
|
725 |
|
726 if (aNewURI.IsEmpty() && |
|
727 doc->GetDocumentURI() && |
|
728 NS_SUCCEEDED(doc->GetDocumentURI()->EqualsExceptRef(imageURI, &equal)) && |
|
729 equal) { |
|
730 |
|
731 // Loading an embedded img from the same URI as the document URI will not work |
|
732 // as a resource cannot recursively embed itself. Attempting to do so generally |
|
733 // results in having to pre-emptively close down an in-flight HTTP transaction |
|
734 // and then incurring the significant cost of establishing a new TCP channel. |
|
735 // This is generally triggered from <img src=""> |
|
736 // In light of that, just skip loading it.. |
|
737 // Do make sure to drop our existing image, if any |
|
738 CancelImageRequests(aNotify); |
|
739 return NS_OK; |
|
740 } |
|
741 |
|
742 NS_TryToSetImmutable(imageURI); |
|
743 |
|
744 return LoadImage(imageURI, aForce, aNotify, doc); |
|
745 } |
|
746 |
|
747 nsresult |
|
748 nsImageLoadingContent::LoadImage(nsIURI* aNewURI, |
|
749 bool aForce, |
|
750 bool aNotify, |
|
751 nsIDocument* aDocument, |
|
752 nsLoadFlags aLoadFlags) |
|
753 { |
|
754 if (!mLoadingEnabled) { |
|
755 // XXX Why fire an error here? seems like the callers to SetLoadingEnabled |
|
756 // don't want/need it. |
|
757 FireEvent(NS_LITERAL_STRING("error")); |
|
758 return NS_OK; |
|
759 } |
|
760 |
|
761 NS_ASSERTION(!aDocument || aDocument == GetOurOwnerDoc(), |
|
762 "Bogus document passed in"); |
|
763 // First, get a document (needed for security checks and the like) |
|
764 if (!aDocument) { |
|
765 aDocument = GetOurOwnerDoc(); |
|
766 if (!aDocument) { |
|
767 // No reason to bother, I think... |
|
768 return NS_OK; |
|
769 } |
|
770 } |
|
771 |
|
772 // URI equality check. |
|
773 // |
|
774 // We skip the equality check if our current image was blocked, since in that |
|
775 // case we really do want to try loading again. |
|
776 if (!aForce && NS_CP_ACCEPTED(mImageBlockingStatus)) { |
|
777 nsCOMPtr<nsIURI> currentURI; |
|
778 GetCurrentURI(getter_AddRefs(currentURI)); |
|
779 bool equal; |
|
780 if (currentURI && |
|
781 NS_SUCCEEDED(currentURI->Equals(aNewURI, &equal)) && |
|
782 equal) { |
|
783 // Nothing to do here. |
|
784 return NS_OK; |
|
785 } |
|
786 } |
|
787 |
|
788 // From this point on, our image state could change. Watch it. |
|
789 AutoStateChanger changer(this, aNotify); |
|
790 |
|
791 // Sanity check. |
|
792 // |
|
793 // We use the principal of aDocument to avoid having to QI |this| an extra |
|
794 // time. It should always be the same as the principal of this node. |
|
795 #ifdef DEBUG |
|
796 nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this)); |
|
797 NS_ABORT_IF_FALSE(thisContent && |
|
798 thisContent->NodePrincipal() == aDocument->NodePrincipal(), |
|
799 "Principal mismatch?"); |
|
800 #endif |
|
801 |
|
802 // Are we blocked? |
|
803 int16_t cpDecision = nsIContentPolicy::REJECT_REQUEST; |
|
804 nsContentUtils::CanLoadImage(aNewURI, |
|
805 static_cast<nsIImageLoadingContent*>(this), |
|
806 aDocument, |
|
807 aDocument->NodePrincipal(), |
|
808 &cpDecision); |
|
809 if (!NS_CP_ACCEPTED(cpDecision)) { |
|
810 FireEvent(NS_LITERAL_STRING("error")); |
|
811 SetBlockedRequest(aNewURI, cpDecision); |
|
812 return NS_OK; |
|
813 } |
|
814 |
|
815 nsLoadFlags loadFlags = aLoadFlags; |
|
816 int32_t corsmode = GetCORSMode(); |
|
817 if (corsmode == CORS_ANONYMOUS) { |
|
818 loadFlags |= imgILoader::LOAD_CORS_ANONYMOUS; |
|
819 } else if (corsmode == CORS_USE_CREDENTIALS) { |
|
820 loadFlags |= imgILoader::LOAD_CORS_USE_CREDENTIALS; |
|
821 } |
|
822 |
|
823 // Not blocked. Do the load. |
|
824 nsRefPtr<imgRequestProxy>& req = PrepareNextRequest(); |
|
825 nsCOMPtr<nsIContent> content = |
|
826 do_QueryInterface(static_cast<nsIImageLoadingContent*>(this)); |
|
827 nsresult rv; |
|
828 rv = nsContentUtils::LoadImage(aNewURI, aDocument, |
|
829 aDocument->NodePrincipal(), |
|
830 aDocument->GetDocumentURI(), |
|
831 this, loadFlags, |
|
832 content->LocalName(), |
|
833 getter_AddRefs(req)); |
|
834 |
|
835 if (NS_SUCCEEDED(rv)) { |
|
836 TrackImage(req); |
|
837 ResetAnimationIfNeeded(); |
|
838 |
|
839 // Handle cases when we just ended up with a pending request but it's |
|
840 // already done. In that situation we have to synchronously switch that |
|
841 // request to being the current request, because websites depend on that |
|
842 // behavior. |
|
843 if (req == mPendingRequest) { |
|
844 uint32_t pendingLoadStatus; |
|
845 rv = req->GetImageStatus(&pendingLoadStatus); |
|
846 if (NS_SUCCEEDED(rv) && |
|
847 (pendingLoadStatus & imgIRequest::STATUS_LOAD_COMPLETE)) { |
|
848 MakePendingRequestCurrent(); |
|
849 MOZ_ASSERT(mCurrentRequest, |
|
850 "How could we not have a current request here?"); |
|
851 |
|
852 nsImageFrame *f = do_QueryFrame(GetOurPrimaryFrame()); |
|
853 if (f) { |
|
854 f->NotifyNewCurrentRequest(mCurrentRequest, NS_OK); |
|
855 } |
|
856 } |
|
857 } |
|
858 } else { |
|
859 MOZ_ASSERT(!req, "Shouldn't have non-null request here"); |
|
860 // If we don't have a current URI, we might as well store this URI so people |
|
861 // know what we tried (and failed) to load. |
|
862 if (!mCurrentRequest) |
|
863 mCurrentURI = aNewURI; |
|
864 FireEvent(NS_LITERAL_STRING("error")); |
|
865 return NS_OK; |
|
866 } |
|
867 |
|
868 return NS_OK; |
|
869 } |
|
870 |
|
871 nsresult |
|
872 nsImageLoadingContent::ForceImageState(bool aForce, |
|
873 EventStates::InternalType aState) |
|
874 { |
|
875 mIsImageStateForced = aForce; |
|
876 mForcedImageState = EventStates(aState); |
|
877 return NS_OK; |
|
878 } |
|
879 |
|
880 EventStates |
|
881 nsImageLoadingContent::ImageState() const |
|
882 { |
|
883 if (mIsImageStateForced) { |
|
884 return mForcedImageState; |
|
885 } |
|
886 |
|
887 EventStates states; |
|
888 |
|
889 if (mBroken) { |
|
890 states |= NS_EVENT_STATE_BROKEN; |
|
891 } |
|
892 if (mUserDisabled) { |
|
893 states |= NS_EVENT_STATE_USERDISABLED; |
|
894 } |
|
895 if (mSuppressed) { |
|
896 states |= NS_EVENT_STATE_SUPPRESSED; |
|
897 } |
|
898 if (mLoading) { |
|
899 states |= NS_EVENT_STATE_LOADING; |
|
900 } |
|
901 |
|
902 return states; |
|
903 } |
|
904 |
|
905 void |
|
906 nsImageLoadingContent::UpdateImageState(bool aNotify) |
|
907 { |
|
908 if (mStateChangerDepth > 0) { |
|
909 // Ignore this call; we'll update our state when the outermost state |
|
910 // changer is destroyed. Need this to work around the fact that some libpr0n |
|
911 // stuff is actually sync and hence we can get OnStopDecode called while |
|
912 // we're still under LoadImage, and OnStopDecode doesn't know anything about |
|
913 // aNotify. |
|
914 // XXX - This machinery should be removed after bug 521604. |
|
915 return; |
|
916 } |
|
917 |
|
918 nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this)); |
|
919 if (!thisContent) { |
|
920 return; |
|
921 } |
|
922 |
|
923 mLoading = mBroken = mUserDisabled = mSuppressed = false; |
|
924 |
|
925 // If we were blocked by server-based content policy, we claim to be |
|
926 // suppressed. If we were blocked by type-based content policy, we claim to |
|
927 // be user-disabled. Otherwise, claim to be broken. |
|
928 if (mImageBlockingStatus == nsIContentPolicy::REJECT_SERVER) { |
|
929 mSuppressed = true; |
|
930 } else if (mImageBlockingStatus == nsIContentPolicy::REJECT_TYPE) { |
|
931 mUserDisabled = true; |
|
932 } else if (!mCurrentRequest) { |
|
933 // No current request means error, since we weren't disabled or suppressed |
|
934 mBroken = true; |
|
935 } else { |
|
936 uint32_t currentLoadStatus; |
|
937 nsresult rv = mCurrentRequest->GetImageStatus(¤tLoadStatus); |
|
938 if (NS_FAILED(rv) || (currentLoadStatus & imgIRequest::STATUS_ERROR)) { |
|
939 mBroken = true; |
|
940 } else if (!(currentLoadStatus & imgIRequest::STATUS_SIZE_AVAILABLE)) { |
|
941 mLoading = true; |
|
942 } |
|
943 } |
|
944 |
|
945 NS_ASSERTION(thisContent->IsElement(), "Not an element?"); |
|
946 thisContent->AsElement()->UpdateState(aNotify); |
|
947 } |
|
948 |
|
949 void |
|
950 nsImageLoadingContent::CancelImageRequests(bool aNotify) |
|
951 { |
|
952 AutoStateChanger changer(this, aNotify); |
|
953 ClearPendingRequest(NS_BINDING_ABORTED, REQUEST_DISCARD); |
|
954 ClearCurrentRequest(NS_BINDING_ABORTED, REQUEST_DISCARD); |
|
955 } |
|
956 |
|
957 nsresult |
|
958 nsImageLoadingContent::UseAsPrimaryRequest(imgRequestProxy* aRequest, |
|
959 bool aNotify) |
|
960 { |
|
961 // Our state will change. Watch it. |
|
962 AutoStateChanger changer(this, aNotify); |
|
963 |
|
964 // Get rid if our existing images |
|
965 ClearPendingRequest(NS_BINDING_ABORTED, REQUEST_DISCARD); |
|
966 ClearCurrentRequest(NS_BINDING_ABORTED, REQUEST_DISCARD); |
|
967 |
|
968 // Clone the request we were given. |
|
969 nsRefPtr<imgRequestProxy>& req = PrepareNextRequest(); |
|
970 nsresult rv = aRequest->Clone(this, getter_AddRefs(req)); |
|
971 if (NS_SUCCEEDED(rv)) { |
|
972 TrackImage(req); |
|
973 } else { |
|
974 MOZ_ASSERT(!req, "Shouldn't have non-null request here"); |
|
975 return rv; |
|
976 } |
|
977 |
|
978 return NS_OK; |
|
979 } |
|
980 |
|
981 nsIDocument* |
|
982 nsImageLoadingContent::GetOurOwnerDoc() |
|
983 { |
|
984 nsCOMPtr<nsIContent> thisContent = |
|
985 do_QueryInterface(static_cast<nsIImageLoadingContent*>(this)); |
|
986 NS_ENSURE_TRUE(thisContent, nullptr); |
|
987 |
|
988 return thisContent->OwnerDoc(); |
|
989 } |
|
990 |
|
991 nsIDocument* |
|
992 nsImageLoadingContent::GetOurCurrentDoc() |
|
993 { |
|
994 nsCOMPtr<nsIContent> thisContent = |
|
995 do_QueryInterface(static_cast<nsIImageLoadingContent*>(this)); |
|
996 NS_ENSURE_TRUE(thisContent, nullptr); |
|
997 |
|
998 return thisContent->GetCurrentDoc(); |
|
999 } |
|
1000 |
|
1001 nsIFrame* |
|
1002 nsImageLoadingContent::GetOurPrimaryFrame() |
|
1003 { |
|
1004 nsCOMPtr<nsIContent> thisContent = |
|
1005 do_QueryInterface(static_cast<nsIImageLoadingContent*>(this)); |
|
1006 return thisContent->GetPrimaryFrame(); |
|
1007 } |
|
1008 |
|
1009 nsPresContext* nsImageLoadingContent::GetFramePresContext() |
|
1010 { |
|
1011 nsIFrame* frame = GetOurPrimaryFrame(); |
|
1012 if (!frame) { |
|
1013 return nullptr; |
|
1014 } |
|
1015 |
|
1016 return frame->PresContext(); |
|
1017 } |
|
1018 |
|
1019 nsresult |
|
1020 nsImageLoadingContent::StringToURI(const nsAString& aSpec, |
|
1021 nsIDocument* aDocument, |
|
1022 nsIURI** aURI) |
|
1023 { |
|
1024 NS_PRECONDITION(aDocument, "Must have a document"); |
|
1025 NS_PRECONDITION(aURI, "Null out param"); |
|
1026 |
|
1027 // (1) Get the base URI |
|
1028 nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this)); |
|
1029 NS_ASSERTION(thisContent, "An image loading content must be an nsIContent"); |
|
1030 nsCOMPtr<nsIURI> baseURL = thisContent->GetBaseURI(); |
|
1031 |
|
1032 // (2) Get the charset |
|
1033 const nsAFlatCString &charset = aDocument->GetDocumentCharacterSet(); |
|
1034 |
|
1035 // (3) Construct the silly thing |
|
1036 return NS_NewURI(aURI, |
|
1037 aSpec, |
|
1038 charset.IsEmpty() ? nullptr : charset.get(), |
|
1039 baseURL, |
|
1040 nsContentUtils::GetIOService()); |
|
1041 } |
|
1042 |
|
1043 nsresult |
|
1044 nsImageLoadingContent::FireEvent(const nsAString& aEventType) |
|
1045 { |
|
1046 // We have to fire the event asynchronously so that we won't go into infinite |
|
1047 // loops in cases when onLoad handlers reset the src and the new src is in |
|
1048 // cache. |
|
1049 |
|
1050 nsCOMPtr<nsINode> thisNode = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this)); |
|
1051 |
|
1052 nsRefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher = |
|
1053 new LoadBlockingAsyncEventDispatcher(thisNode, aEventType, false, false); |
|
1054 loadBlockingAsyncDispatcher->PostDOMEvent(); |
|
1055 |
|
1056 return NS_OK; |
|
1057 } |
|
1058 |
|
1059 nsRefPtr<imgRequestProxy>& |
|
1060 nsImageLoadingContent::PrepareNextRequest() |
|
1061 { |
|
1062 // If we don't have a usable current request, get rid of any half-baked |
|
1063 // request that might be sitting there and make this one current. |
|
1064 if (!HaveSize(mCurrentRequest)) |
|
1065 return PrepareCurrentRequest(); |
|
1066 |
|
1067 // Otherwise, make it pending. |
|
1068 return PreparePendingRequest(); |
|
1069 } |
|
1070 |
|
1071 void |
|
1072 nsImageLoadingContent::SetBlockedRequest(nsIURI* aURI, int16_t aContentDecision) |
|
1073 { |
|
1074 // Sanity |
|
1075 NS_ABORT_IF_FALSE(!NS_CP_ACCEPTED(aContentDecision), "Blocked but not?"); |
|
1076 |
|
1077 // We do some slightly illogical stuff here to maintain consistency with |
|
1078 // old behavior that people probably depend on. Even in the case where the |
|
1079 // new image is blocked, the old one should really be canceled with the |
|
1080 // reason "image source changed". However, apparently there's some abuse |
|
1081 // over in nsImageFrame where the displaying of the "broken" icon for the |
|
1082 // next image depends on the cancel reason of the previous image. ugh. |
|
1083 ClearPendingRequest(NS_ERROR_IMAGE_BLOCKED, REQUEST_DISCARD); |
|
1084 |
|
1085 // For the blocked case, we only want to cancel the existing current request |
|
1086 // if size is not available. bz says the web depends on this behavior. |
|
1087 if (!HaveSize(mCurrentRequest)) { |
|
1088 |
|
1089 mImageBlockingStatus = aContentDecision; |
|
1090 ClearCurrentRequest(NS_ERROR_IMAGE_BLOCKED, REQUEST_DISCARD); |
|
1091 |
|
1092 // We still want to remember what URI we were despite not having an actual |
|
1093 // request. |
|
1094 mCurrentURI = aURI; |
|
1095 } |
|
1096 } |
|
1097 |
|
1098 nsRefPtr<imgRequestProxy>& |
|
1099 nsImageLoadingContent::PrepareCurrentRequest() |
|
1100 { |
|
1101 // Blocked images go through SetBlockedRequest, which is a separate path. For |
|
1102 // everything else, we're unblocked. |
|
1103 mImageBlockingStatus = nsIContentPolicy::ACCEPT; |
|
1104 |
|
1105 // Get rid of anything that was there previously. |
|
1106 ClearCurrentRequest(NS_ERROR_IMAGE_SRC_CHANGED, REQUEST_DISCARD); |
|
1107 |
|
1108 if (mNewRequestsWillNeedAnimationReset) { |
|
1109 mCurrentRequestFlags |= REQUEST_NEEDS_ANIMATION_RESET; |
|
1110 } |
|
1111 |
|
1112 // Return a reference. |
|
1113 return mCurrentRequest; |
|
1114 } |
|
1115 |
|
1116 nsRefPtr<imgRequestProxy>& |
|
1117 nsImageLoadingContent::PreparePendingRequest() |
|
1118 { |
|
1119 // Get rid of anything that was there previously. |
|
1120 ClearPendingRequest(NS_ERROR_IMAGE_SRC_CHANGED, REQUEST_DISCARD); |
|
1121 |
|
1122 if (mNewRequestsWillNeedAnimationReset) { |
|
1123 mPendingRequestFlags |= REQUEST_NEEDS_ANIMATION_RESET; |
|
1124 } |
|
1125 |
|
1126 // Return a reference. |
|
1127 return mPendingRequest; |
|
1128 } |
|
1129 |
|
1130 namespace { |
|
1131 |
|
1132 class ImageRequestAutoLock |
|
1133 { |
|
1134 public: |
|
1135 ImageRequestAutoLock(imgIRequest* aRequest) |
|
1136 : mRequest(aRequest) |
|
1137 { |
|
1138 if (mRequest) { |
|
1139 mRequest->LockImage(); |
|
1140 } |
|
1141 } |
|
1142 |
|
1143 ~ImageRequestAutoLock() |
|
1144 { |
|
1145 if (mRequest) { |
|
1146 mRequest->UnlockImage(); |
|
1147 } |
|
1148 } |
|
1149 |
|
1150 private: |
|
1151 nsCOMPtr<imgIRequest> mRequest; |
|
1152 }; |
|
1153 |
|
1154 } // anonymous namespace |
|
1155 |
|
1156 void |
|
1157 nsImageLoadingContent::MakePendingRequestCurrent() |
|
1158 { |
|
1159 MOZ_ASSERT(mPendingRequest); |
|
1160 |
|
1161 // Lock mCurrentRequest for the duration of this method. We do this because |
|
1162 // PrepareCurrentRequest() might unlock mCurrentRequest. If mCurrentRequest |
|
1163 // and mPendingRequest are both requests for the same image, unlocking |
|
1164 // mCurrentRequest before we lock mPendingRequest can cause the lock count |
|
1165 // to go to 0 and the image to be discarded! |
|
1166 ImageRequestAutoLock autoLock(mCurrentRequest); |
|
1167 |
|
1168 PrepareCurrentRequest() = mPendingRequest; |
|
1169 mPendingRequest = nullptr; |
|
1170 mCurrentRequestFlags = mPendingRequestFlags; |
|
1171 mPendingRequestFlags = 0; |
|
1172 ResetAnimationIfNeeded(); |
|
1173 } |
|
1174 |
|
1175 void |
|
1176 nsImageLoadingContent::ClearCurrentRequest(nsresult aReason, |
|
1177 uint32_t aFlags) |
|
1178 { |
|
1179 if (!mCurrentRequest) { |
|
1180 // Even if we didn't have a current request, we might have been keeping |
|
1181 // a URI as a placeholder for a failed load. Clear that now. |
|
1182 mCurrentURI = nullptr; |
|
1183 return; |
|
1184 } |
|
1185 NS_ABORT_IF_FALSE(!mCurrentURI, |
|
1186 "Shouldn't have both mCurrentRequest and mCurrentURI!"); |
|
1187 |
|
1188 // Deregister this image from the refresh driver so it no longer receives |
|
1189 // notifications. |
|
1190 nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mCurrentRequest, |
|
1191 &mCurrentRequestRegistered); |
|
1192 |
|
1193 // Clean up the request. |
|
1194 UntrackImage(mCurrentRequest, aFlags); |
|
1195 mCurrentRequest->CancelAndForgetObserver(aReason); |
|
1196 mCurrentRequest = nullptr; |
|
1197 mCurrentRequestFlags = 0; |
|
1198 } |
|
1199 |
|
1200 void |
|
1201 nsImageLoadingContent::ClearPendingRequest(nsresult aReason, |
|
1202 uint32_t aFlags) |
|
1203 { |
|
1204 if (!mPendingRequest) |
|
1205 return; |
|
1206 |
|
1207 // Deregister this image from the refresh driver so it no longer receives |
|
1208 // notifications. |
|
1209 nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mPendingRequest, |
|
1210 &mPendingRequestRegistered); |
|
1211 |
|
1212 UntrackImage(mPendingRequest, aFlags); |
|
1213 mPendingRequest->CancelAndForgetObserver(aReason); |
|
1214 mPendingRequest = nullptr; |
|
1215 mPendingRequestFlags = 0; |
|
1216 } |
|
1217 |
|
1218 bool* |
|
1219 nsImageLoadingContent::GetRegisteredFlagForRequest(imgIRequest* aRequest) |
|
1220 { |
|
1221 if (aRequest == mCurrentRequest) { |
|
1222 return &mCurrentRequestRegistered; |
|
1223 } else if (aRequest == mPendingRequest) { |
|
1224 return &mPendingRequestRegistered; |
|
1225 } else { |
|
1226 return nullptr; |
|
1227 } |
|
1228 } |
|
1229 |
|
1230 void |
|
1231 nsImageLoadingContent::ResetAnimationIfNeeded() |
|
1232 { |
|
1233 if (mCurrentRequest && |
|
1234 (mCurrentRequestFlags & REQUEST_NEEDS_ANIMATION_RESET)) { |
|
1235 nsCOMPtr<imgIContainer> container; |
|
1236 mCurrentRequest->GetImage(getter_AddRefs(container)); |
|
1237 if (container) |
|
1238 container->ResetAnimation(); |
|
1239 mCurrentRequestFlags &= ~REQUEST_NEEDS_ANIMATION_RESET; |
|
1240 } |
|
1241 } |
|
1242 |
|
1243 bool |
|
1244 nsImageLoadingContent::HaveSize(imgIRequest *aImage) |
|
1245 { |
|
1246 // Handle the null case |
|
1247 if (!aImage) |
|
1248 return false; |
|
1249 |
|
1250 // Query the image |
|
1251 uint32_t status; |
|
1252 nsresult rv = aImage->GetImageStatus(&status); |
|
1253 return (NS_SUCCEEDED(rv) && (status & imgIRequest::STATUS_SIZE_AVAILABLE)); |
|
1254 } |
|
1255 |
|
1256 void |
|
1257 nsImageLoadingContent::BindToTree(nsIDocument* aDocument, nsIContent* aParent, |
|
1258 nsIContent* aBindingParent, |
|
1259 bool aCompileEventHandlers) |
|
1260 { |
|
1261 // We may be entering the document, so if our image should be tracked, |
|
1262 // track it. |
|
1263 if (!aDocument) |
|
1264 return; |
|
1265 |
|
1266 TrackImage(mCurrentRequest); |
|
1267 TrackImage(mPendingRequest); |
|
1268 |
|
1269 if (mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD) |
|
1270 aDocument->BlockOnload(); |
|
1271 } |
|
1272 |
|
1273 void |
|
1274 nsImageLoadingContent::UnbindFromTree(bool aDeep, bool aNullParent) |
|
1275 { |
|
1276 // We may be leaving the document, so if our image is tracked, untrack it. |
|
1277 nsCOMPtr<nsIDocument> doc = GetOurCurrentDoc(); |
|
1278 if (!doc) |
|
1279 return; |
|
1280 |
|
1281 UntrackImage(mCurrentRequest); |
|
1282 UntrackImage(mPendingRequest); |
|
1283 |
|
1284 if (mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD) |
|
1285 doc->UnblockOnload(false); |
|
1286 } |
|
1287 |
|
1288 void |
|
1289 nsImageLoadingContent::TrackImage(imgIRequest* aImage) |
|
1290 { |
|
1291 if (!aImage) |
|
1292 return; |
|
1293 |
|
1294 MOZ_ASSERT(aImage == mCurrentRequest || aImage == mPendingRequest, |
|
1295 "Why haven't we heard of this request?"); |
|
1296 |
|
1297 nsIDocument* doc = GetOurCurrentDoc(); |
|
1298 if (doc && (mFrameCreateCalled || GetOurPrimaryFrame()) && |
|
1299 (mVisibleCount > 0)) { |
|
1300 if (aImage == mCurrentRequest && !(mCurrentRequestFlags & REQUEST_IS_TRACKED)) { |
|
1301 mCurrentRequestFlags |= REQUEST_IS_TRACKED; |
|
1302 doc->AddImage(mCurrentRequest); |
|
1303 } |
|
1304 if (aImage == mPendingRequest && !(mPendingRequestFlags & REQUEST_IS_TRACKED)) { |
|
1305 mPendingRequestFlags |= REQUEST_IS_TRACKED; |
|
1306 doc->AddImage(mPendingRequest); |
|
1307 } |
|
1308 } |
|
1309 } |
|
1310 |
|
1311 void |
|
1312 nsImageLoadingContent::UntrackImage(imgIRequest* aImage, uint32_t aFlags /* = 0 */) |
|
1313 { |
|
1314 if (!aImage) |
|
1315 return; |
|
1316 |
|
1317 MOZ_ASSERT(aImage == mCurrentRequest || aImage == mPendingRequest, |
|
1318 "Why haven't we heard of this request?"); |
|
1319 |
|
1320 // We may not be in the document. If we outlived our document that's fine, |
|
1321 // because the document empties out the tracker and unlocks all locked images |
|
1322 // on destruction. But if we were never in the document we may need to force |
|
1323 // discarding the image here, since this is the only chance we have. |
|
1324 nsIDocument* doc = GetOurCurrentDoc(); |
|
1325 if (aImage == mCurrentRequest) { |
|
1326 if (doc && (mCurrentRequestFlags & REQUEST_IS_TRACKED)) { |
|
1327 mCurrentRequestFlags &= ~REQUEST_IS_TRACKED; |
|
1328 doc->RemoveImage(mCurrentRequest, |
|
1329 (aFlags & REQUEST_DISCARD) ? nsIDocument::REQUEST_DISCARD : 0); |
|
1330 } |
|
1331 else if (aFlags & REQUEST_DISCARD) { |
|
1332 // If we're not in the document we may still need to be discarded. |
|
1333 aImage->RequestDiscard(); |
|
1334 } |
|
1335 } |
|
1336 if (aImage == mPendingRequest) { |
|
1337 if (doc && (mPendingRequestFlags & REQUEST_IS_TRACKED)) { |
|
1338 mPendingRequestFlags &= ~REQUEST_IS_TRACKED; |
|
1339 doc->RemoveImage(mPendingRequest, |
|
1340 (aFlags & REQUEST_DISCARD) ? nsIDocument::REQUEST_DISCARD : 0); |
|
1341 } |
|
1342 else if (aFlags & REQUEST_DISCARD) { |
|
1343 // If we're not in the document we may still need to be discarded. |
|
1344 aImage->RequestDiscard(); |
|
1345 } |
|
1346 } |
|
1347 } |
|
1348 |
|
1349 |
|
1350 void |
|
1351 nsImageLoadingContent::CreateStaticImageClone(nsImageLoadingContent* aDest) const |
|
1352 { |
|
1353 aDest->mCurrentRequest = nsContentUtils::GetStaticRequest(mCurrentRequest); |
|
1354 aDest->TrackImage(aDest->mCurrentRequest); |
|
1355 aDest->mForcedImageState = mForcedImageState; |
|
1356 aDest->mImageBlockingStatus = mImageBlockingStatus; |
|
1357 aDest->mLoadingEnabled = mLoadingEnabled; |
|
1358 aDest->mStateChangerDepth = mStateChangerDepth; |
|
1359 aDest->mIsImageStateForced = mIsImageStateForced; |
|
1360 aDest->mLoading = mLoading; |
|
1361 aDest->mBroken = mBroken; |
|
1362 aDest->mUserDisabled = mUserDisabled; |
|
1363 aDest->mSuppressed = mSuppressed; |
|
1364 } |
|
1365 |
|
1366 CORSMode |
|
1367 nsImageLoadingContent::GetCORSMode() |
|
1368 { |
|
1369 return CORS_NONE; |
|
1370 } |
|
1371 |
|
1372 nsImageLoadingContent::ImageObserver::ImageObserver(imgINotificationObserver* aObserver) |
|
1373 : mObserver(aObserver) |
|
1374 , mNext(nullptr) |
|
1375 { |
|
1376 MOZ_COUNT_CTOR(ImageObserver); |
|
1377 } |
|
1378 |
|
1379 nsImageLoadingContent::ImageObserver::~ImageObserver() |
|
1380 { |
|
1381 MOZ_COUNT_DTOR(ImageObserver); |
|
1382 NS_CONTENT_DELETE_LIST_MEMBER(ImageObserver, this, mNext); |
|
1383 } |