Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 "nsError.h"
15 : #include "nsIContent.h"
16 : #include "nsIDocument.h"
17 : #include "nsIScriptGlobalObject.h"
18 : #include "nsIDOMWindow.h"
19 : #include "nsServiceManagerUtils.h"
20 : #include "nsContentPolicyUtils.h"
21 : #include "nsIURI.h"
22 : #include "nsILoadGroup.h"
23 : #include "imgIContainer.h"
24 : #include "imgLoader.h"
25 : #include "imgRequestProxy.h"
26 : #include "nsThreadUtils.h"
27 : #include "nsNetUtil.h"
28 : #include "nsImageFrame.h"
29 : #include "nsSVGImageFrame.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 "gfxPrefs.h"
45 :
46 : #include "mozAutoDocUpdate.h"
47 : #include "mozilla/AsyncEventDispatcher.h"
48 : #include "mozilla/AutoRestore.h"
49 : #include "mozilla/EventStateManager.h"
50 : #include "mozilla/EventStates.h"
51 : #include "mozilla/dom/Element.h"
52 : #include "mozilla/dom/ImageTracker.h"
53 : #include "mozilla/dom/ScriptSettings.h"
54 : #include "mozilla/Preferences.h"
55 :
56 : #ifdef LoadImage
57 : // Undefine LoadImage to prevent naming conflict with Windows.
58 : #undef LoadImage
59 : #endif
60 :
61 : using namespace mozilla;
62 : using namespace mozilla::dom;
63 :
64 : #ifdef DEBUG_chb
65 : static void PrintReqURL(imgIRequest* req) {
66 : if (!req) {
67 : printf("(null req)\n");
68 : return;
69 : }
70 :
71 : nsCOMPtr<nsIURI> uri;
72 : req->GetURI(getter_AddRefs(uri));
73 : if (!uri) {
74 : printf("(null uri)\n");
75 : return;
76 : }
77 :
78 : nsAutoCString spec;
79 : uri->GetSpec(spec);
80 : printf("spec='%s'\n", spec.get());
81 : }
82 : #endif /* DEBUG_chb */
83 :
84 :
85 11 : nsImageLoadingContent::nsImageLoadingContent()
86 : : mCurrentRequestFlags(0),
87 : mPendingRequestFlags(0),
88 : mObserverList(nullptr),
89 : mImageBlockingStatus(nsIContentPolicy::ACCEPT),
90 : mLoadingEnabled(true),
91 : mIsImageStateForced(false),
92 : mLoading(false),
93 : // mBroken starts out true, since an image without a URI is broken....
94 : mBroken(true),
95 : mUserDisabled(false),
96 : mSuppressed(false),
97 : mNewRequestsWillNeedAnimationReset(false),
98 : mUseUrgentStartForChannel(false),
99 : mStateChangerDepth(0),
100 : mCurrentRequestRegistered(false),
101 : mPendingRequestRegistered(false),
102 11 : mIsStartingImageLoad(false)
103 : {
104 11 : if (!nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) {
105 0 : mLoadingEnabled = false;
106 : }
107 :
108 11 : mMostRecentRequestChange = TimeStamp::ProcessCreation();
109 11 : }
110 :
111 : void
112 0 : nsImageLoadingContent::DestroyImageLoadingContent()
113 : {
114 : // Cancel our requests so they won't hold stale refs to us
115 : // NB: Don't ask to discard the images here.
116 0 : ClearCurrentRequest(NS_BINDING_ABORTED);
117 0 : ClearPendingRequest(NS_BINDING_ABORTED);
118 0 : }
119 :
120 0 : nsImageLoadingContent::~nsImageLoadingContent()
121 : {
122 0 : NS_ASSERTION(!mCurrentRequest && !mPendingRequest,
123 : "DestroyImageLoadingContent not called");
124 0 : NS_ASSERTION(!mObserverList.mObserver && !mObserverList.mNext,
125 : "Observers still registered?");
126 0 : }
127 :
128 : /*
129 : * imgINotificationObserver impl
130 : */
131 : NS_IMETHODIMP
132 27 : nsImageLoadingContent::Notify(imgIRequest* aRequest,
133 : int32_t aType,
134 : const nsIntRect* aData)
135 : {
136 27 : if (aType == imgINotificationObserver::IS_ANIMATED) {
137 0 : return OnImageIsAnimated(aRequest);
138 : }
139 :
140 27 : if (aType == imgINotificationObserver::UNLOCKED_DRAW) {
141 0 : OnUnlockedDraw();
142 0 : return NS_OK;
143 : }
144 :
145 27 : if (aType == imgINotificationObserver::LOAD_COMPLETE) {
146 : // We should definitely have a request here
147 4 : MOZ_ASSERT(aRequest, "no request?");
148 :
149 4 : NS_PRECONDITION(aRequest == mCurrentRequest || aRequest == mPendingRequest,
150 : "Unknown request");
151 : }
152 :
153 : {
154 : // Calling Notify on observers can modify the list of observers so make
155 : // a local copy.
156 54 : AutoTArray<nsCOMPtr<imgINotificationObserver>, 2> observers;
157 54 : for (ImageObserver* observer = &mObserverList, *next; observer;
158 27 : observer = next) {
159 27 : next = observer->mNext;
160 27 : if (observer->mObserver) {
161 0 : observers.AppendElement(observer->mObserver);
162 : }
163 : }
164 :
165 54 : nsAutoScriptBlocker scriptBlocker;
166 :
167 27 : for (auto& observer : observers) {
168 0 : observer->Notify(aRequest, aType, aData);
169 : }
170 : }
171 :
172 27 : if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
173 : // Have to check for state changes here, since we might have been in
174 : // the LOADING state before.
175 4 : UpdateImageState(true);
176 : }
177 :
178 27 : if (aType == imgINotificationObserver::LOAD_COMPLETE) {
179 : uint32_t reqStatus;
180 4 : aRequest->GetImageStatus(&reqStatus);
181 : /* triage STATUS_ERROR */
182 4 : if (reqStatus & imgIRequest::STATUS_ERROR) {
183 0 : nsresult errorCode = NS_OK;
184 0 : aRequest->GetImageErrorCode(&errorCode);
185 :
186 : /* Handle image not loading error because source was a tracking URL.
187 : * We make a note of this image node by including it in a dedicated
188 : * array of blocked tracking nodes under its parent document.
189 : */
190 0 : if (errorCode == NS_ERROR_TRACKING_URI) {
191 : nsCOMPtr<nsIContent> thisNode
192 0 : = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
193 :
194 0 : nsIDocument *doc = GetOurOwnerDoc();
195 0 : doc->AddBlockedTrackingNode(thisNode);
196 : }
197 : }
198 : nsresult status =
199 4 : reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
200 4 : return OnLoadComplete(aRequest, status);
201 : }
202 :
203 23 : if (aType == imgINotificationObserver::DECODE_COMPLETE) {
204 8 : nsCOMPtr<imgIContainer> container;
205 4 : aRequest->GetImage(getter_AddRefs(container));
206 4 : if (container) {
207 4 : container->PropagateUseCounters(GetOurOwnerDoc());
208 : }
209 :
210 4 : UpdateImageState(true);
211 : }
212 :
213 23 : return NS_OK;
214 : }
215 :
216 : nsresult
217 4 : nsImageLoadingContent::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus)
218 : {
219 : uint32_t oldStatus;
220 4 : aRequest->GetImageStatus(&oldStatus);
221 :
222 : //XXXjdm This occurs when we have a pending request created, then another
223 : // pending request replaces it before the first one is finished.
224 : // This begs the question of what the correct behaviour is; we used
225 : // to not have to care because we ran this code in OnStopDecode which
226 : // wasn't called when the first request was cancelled. For now, I choose
227 : // to punt when the given request doesn't appear to have terminated in
228 : // an expected state.
229 4 : if (!(oldStatus & (imgIRequest::STATUS_ERROR | imgIRequest::STATUS_LOAD_COMPLETE)))
230 0 : return NS_OK;
231 :
232 : // Our state may change. Watch it.
233 8 : AutoStateChanger changer(this, true);
234 :
235 : // If the pending request is loaded, switch to it.
236 4 : if (aRequest == mPendingRequest) {
237 0 : MakePendingRequestCurrent();
238 : }
239 4 : MOZ_ASSERT(aRequest == mCurrentRequest,
240 : "One way or another, we should be current by now");
241 :
242 : // Fire the appropriate DOM event.
243 4 : if (NS_SUCCEEDED(aStatus)) {
244 4 : FireEvent(NS_LITERAL_STRING("load"));
245 :
246 : // Do not fire loadend event for multipart/x-mixed-replace image streams.
247 : bool isMultipart;
248 4 : if (NS_FAILED(aRequest->GetMultipart(&isMultipart)) || !isMultipart) {
249 4 : FireEvent(NS_LITERAL_STRING("loadend"));
250 : }
251 : } else {
252 0 : FireEvent(NS_LITERAL_STRING("error"));
253 0 : FireEvent(NS_LITERAL_STRING("loadend"));
254 : }
255 :
256 8 : nsCOMPtr<nsINode> thisNode = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
257 4 : nsSVGEffects::InvalidateDirectRenderingObservers(thisNode->AsElement());
258 :
259 4 : return NS_OK;
260 : }
261 :
262 : static bool
263 0 : ImageIsAnimated(imgIRequest* aRequest)
264 : {
265 0 : if (!aRequest) {
266 0 : return false;
267 : }
268 :
269 0 : nsCOMPtr<imgIContainer> image;
270 0 : if (NS_SUCCEEDED(aRequest->GetImage(getter_AddRefs(image)))) {
271 0 : bool isAnimated = false;
272 0 : nsresult rv = image->GetAnimated(&isAnimated);
273 0 : if (NS_SUCCEEDED(rv) && isAnimated) {
274 0 : return true;
275 : }
276 : }
277 :
278 0 : return false;
279 : }
280 :
281 : void
282 0 : nsImageLoadingContent::OnUnlockedDraw()
283 : {
284 : // It's OK for non-animated images to wait until the next frame visibility
285 : // update to become locked. (And that's preferable, since in the case of
286 : // scrolling it keeps memory usage minimal.) For animated images, though, we
287 : // want to mark them visible right away so we can call
288 : // IncrementAnimationConsumers() on them and they'll start animating.
289 0 : if (!ImageIsAnimated(mCurrentRequest) && !ImageIsAnimated(mPendingRequest)) {
290 0 : return;
291 : }
292 :
293 0 : nsIFrame* frame = GetOurPrimaryFrame();
294 0 : if (!frame) {
295 0 : return;
296 : }
297 :
298 0 : if (frame->GetVisibility() == Visibility::APPROXIMATELY_VISIBLE) {
299 : // This frame is already marked visible; there's nothing to do.
300 0 : return;
301 : }
302 :
303 0 : nsPresContext* presContext = frame->PresContext();
304 0 : if (!presContext) {
305 0 : return;
306 : }
307 :
308 0 : nsIPresShell* presShell = presContext->PresShell();
309 0 : if (!presShell) {
310 0 : return;
311 : }
312 :
313 0 : presShell->EnsureFrameInApproximatelyVisibleList(frame);
314 : }
315 :
316 : nsresult
317 0 : nsImageLoadingContent::OnImageIsAnimated(imgIRequest *aRequest)
318 : {
319 0 : bool* requestFlag = GetRegisteredFlagForRequest(aRequest);
320 0 : if (requestFlag) {
321 0 : nsLayoutUtils::RegisterImageRequest(GetFramePresContext(),
322 0 : aRequest, requestFlag);
323 : }
324 :
325 0 : return NS_OK;
326 : }
327 :
328 : /*
329 : * nsIImageLoadingContent impl
330 : */
331 :
332 : NS_IMETHODIMP
333 0 : nsImageLoadingContent::GetLoadingEnabled(bool *aLoadingEnabled)
334 : {
335 0 : *aLoadingEnabled = mLoadingEnabled;
336 0 : return NS_OK;
337 : }
338 :
339 : NS_IMETHODIMP
340 0 : nsImageLoadingContent::SetLoadingEnabled(bool aLoadingEnabled)
341 : {
342 0 : if (nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) {
343 0 : mLoadingEnabled = aLoadingEnabled;
344 : }
345 0 : return NS_OK;
346 : }
347 :
348 : NS_IMETHODIMP
349 0 : nsImageLoadingContent::GetImageBlockingStatus(int16_t* aStatus)
350 : {
351 0 : NS_PRECONDITION(aStatus, "Null out param");
352 0 : *aStatus = ImageBlockingStatus();
353 0 : return NS_OK;
354 : }
355 :
356 : static void
357 0 : ReplayImageStatus(imgIRequest* aRequest, imgINotificationObserver* aObserver)
358 : {
359 0 : if (!aRequest) {
360 0 : return;
361 : }
362 :
363 0 : uint32_t status = 0;
364 0 : nsresult rv = aRequest->GetImageStatus(&status);
365 0 : if (NS_FAILED(rv)) {
366 0 : return;
367 : }
368 :
369 0 : if (status & imgIRequest::STATUS_SIZE_AVAILABLE) {
370 0 : aObserver->Notify(aRequest, imgINotificationObserver::SIZE_AVAILABLE, nullptr);
371 : }
372 0 : if (status & imgIRequest::STATUS_FRAME_COMPLETE) {
373 0 : aObserver->Notify(aRequest, imgINotificationObserver::FRAME_COMPLETE, nullptr);
374 : }
375 0 : if (status & imgIRequest::STATUS_HAS_TRANSPARENCY) {
376 0 : aObserver->Notify(aRequest, imgINotificationObserver::HAS_TRANSPARENCY, nullptr);
377 : }
378 0 : if (status & imgIRequest::STATUS_IS_ANIMATED) {
379 0 : aObserver->Notify(aRequest, imgINotificationObserver::IS_ANIMATED, nullptr);
380 : }
381 0 : if (status & imgIRequest::STATUS_DECODE_COMPLETE) {
382 0 : aObserver->Notify(aRequest, imgINotificationObserver::DECODE_COMPLETE, nullptr);
383 : }
384 0 : if (status & imgIRequest::STATUS_LOAD_COMPLETE) {
385 0 : aObserver->Notify(aRequest, imgINotificationObserver::LOAD_COMPLETE, nullptr);
386 : }
387 : }
388 :
389 : NS_IMETHODIMP
390 0 : nsImageLoadingContent::AddObserver(imgINotificationObserver* aObserver)
391 : {
392 0 : NS_ENSURE_ARG_POINTER(aObserver);
393 :
394 0 : if (!mObserverList.mObserver) {
395 : // Don't touch the linking of the list!
396 0 : mObserverList.mObserver = aObserver;
397 :
398 0 : ReplayImageStatus(mCurrentRequest, aObserver);
399 0 : ReplayImageStatus(mPendingRequest, aObserver);
400 :
401 0 : return NS_OK;
402 : }
403 :
404 : // otherwise we have to create a new entry
405 :
406 0 : ImageObserver* observer = &mObserverList;
407 0 : while (observer->mNext) {
408 0 : observer = observer->mNext;
409 : }
410 :
411 0 : observer->mNext = new ImageObserver(aObserver);
412 0 : ReplayImageStatus(mCurrentRequest, aObserver);
413 0 : ReplayImageStatus(mPendingRequest, aObserver);
414 :
415 0 : return NS_OK;
416 : }
417 :
418 : NS_IMETHODIMP
419 0 : nsImageLoadingContent::RemoveObserver(imgINotificationObserver* aObserver)
420 : {
421 0 : NS_ENSURE_ARG_POINTER(aObserver);
422 :
423 0 : if (mObserverList.mObserver == aObserver) {
424 0 : mObserverList.mObserver = nullptr;
425 : // Don't touch the linking of the list!
426 0 : return NS_OK;
427 : }
428 :
429 : // otherwise have to find it and splice it out
430 0 : ImageObserver* observer = &mObserverList;
431 0 : while (observer->mNext && observer->mNext->mObserver != aObserver) {
432 0 : observer = observer->mNext;
433 : }
434 :
435 : // At this point, we are pointing to the list element whose mNext is
436 : // the right observer (assuming of course that mNext is not null)
437 0 : if (observer->mNext) {
438 : // splice it out
439 0 : ImageObserver* oldObserver = observer->mNext;
440 0 : observer->mNext = oldObserver->mNext;
441 0 : oldObserver->mNext = nullptr; // so we don't destroy them all
442 0 : delete oldObserver;
443 : }
444 : #ifdef DEBUG
445 : else {
446 0 : NS_WARNING("Asked to remove nonexistent observer");
447 : }
448 : #endif
449 0 : return NS_OK;
450 : }
451 :
452 : already_AddRefed<imgIRequest>
453 0 : nsImageLoadingContent::GetRequest(int32_t aRequestType,
454 : ErrorResult& aError)
455 : {
456 0 : nsCOMPtr<imgIRequest> request;
457 0 : switch(aRequestType) {
458 : case CURRENT_REQUEST:
459 0 : request = mCurrentRequest;
460 0 : break;
461 : case PENDING_REQUEST:
462 0 : request = mPendingRequest;
463 0 : break;
464 : default:
465 0 : NS_ERROR("Unknown request type");
466 0 : aError.Throw(NS_ERROR_UNEXPECTED);
467 : }
468 :
469 0 : return request.forget();
470 : }
471 :
472 : NS_IMETHODIMP
473 0 : nsImageLoadingContent::GetRequest(int32_t aRequestType,
474 : imgIRequest** aRequest)
475 : {
476 0 : NS_ENSURE_ARG_POINTER(aRequest);
477 :
478 0 : ErrorResult result;
479 0 : *aRequest = GetRequest(aRequestType, result).take();
480 :
481 0 : return result.StealNSResult();
482 : }
483 :
484 : NS_IMETHODIMP_(bool)
485 0 : nsImageLoadingContent::CurrentRequestHasSize()
486 : {
487 0 : return HaveSize(mCurrentRequest);
488 : }
489 :
490 : NS_IMETHODIMP_(void)
491 0 : nsImageLoadingContent::FrameCreated(nsIFrame* aFrame)
492 : {
493 0 : NS_ASSERTION(aFrame, "aFrame is null");
494 :
495 0 : TrackImage(mCurrentRequest, aFrame);
496 0 : TrackImage(mPendingRequest, aFrame);
497 :
498 : // We need to make sure that our image request is registered, if it should
499 : // be registered.
500 0 : nsPresContext* presContext = aFrame->PresContext();
501 0 : if (mCurrentRequest) {
502 0 : nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, mCurrentRequest,
503 0 : &mCurrentRequestRegistered);
504 : }
505 :
506 0 : if (mPendingRequest) {
507 0 : nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, mPendingRequest,
508 0 : &mPendingRequestRegistered);
509 : }
510 0 : }
511 :
512 : NS_IMETHODIMP_(void)
513 0 : nsImageLoadingContent::FrameDestroyed(nsIFrame* aFrame)
514 : {
515 0 : NS_ASSERTION(aFrame, "aFrame is null");
516 :
517 : // We need to make sure that our image request is deregistered.
518 0 : nsPresContext* presContext = GetFramePresContext();
519 0 : if (mCurrentRequest) {
520 0 : nsLayoutUtils::DeregisterImageRequest(presContext,
521 : mCurrentRequest,
522 0 : &mCurrentRequestRegistered);
523 : }
524 :
525 0 : if (mPendingRequest) {
526 0 : nsLayoutUtils::DeregisterImageRequest(presContext,
527 : mPendingRequest,
528 0 : &mPendingRequestRegistered);
529 : }
530 :
531 0 : UntrackImage(mCurrentRequest);
532 0 : UntrackImage(mPendingRequest);
533 :
534 0 : nsIPresShell* presShell = presContext ? presContext->GetPresShell() : nullptr;
535 0 : if (presShell) {
536 0 : presShell->RemoveFrameFromApproximatelyVisibleList(aFrame);
537 : }
538 0 : }
539 :
540 : /* static */
541 : nsContentPolicyType
542 4 : nsImageLoadingContent::PolicyTypeForLoad(ImageLoadType aImageLoadType)
543 : {
544 4 : if (aImageLoadType == eImageLoadType_Imageset) {
545 0 : return nsIContentPolicy::TYPE_IMAGESET;
546 : }
547 :
548 4 : MOZ_ASSERT(aImageLoadType == eImageLoadType_Normal,
549 : "Unknown ImageLoadType type in PolicyTypeForLoad");
550 4 : return nsIContentPolicy::TYPE_INTERNAL_IMAGE;
551 : }
552 :
553 : int32_t
554 0 : nsImageLoadingContent::GetRequestType(imgIRequest* aRequest,
555 : ErrorResult& aError)
556 : {
557 0 : if (aRequest == mCurrentRequest) {
558 0 : return CURRENT_REQUEST;
559 : }
560 :
561 0 : if (aRequest == mPendingRequest) {
562 0 : return PENDING_REQUEST;
563 : }
564 :
565 0 : NS_ERROR("Unknown request");
566 0 : aError.Throw(NS_ERROR_UNEXPECTED);
567 0 : return UNKNOWN_REQUEST;
568 : }
569 :
570 : NS_IMETHODIMP
571 0 : nsImageLoadingContent::GetRequestType(imgIRequest* aRequest,
572 : int32_t* aRequestType)
573 : {
574 0 : NS_PRECONDITION(aRequestType, "Null out param");
575 :
576 0 : ErrorResult result;
577 0 : *aRequestType = GetRequestType(aRequest, result);
578 0 : return result.StealNSResult();
579 : }
580 :
581 : already_AddRefed<nsIURI>
582 4 : nsImageLoadingContent::GetCurrentURI(ErrorResult& aError)
583 : {
584 8 : nsCOMPtr<nsIURI> uri;
585 4 : if (mCurrentRequest) {
586 0 : mCurrentRequest->GetURI(getter_AddRefs(uri));
587 4 : } else if (mCurrentURI) {
588 0 : nsresult rv = NS_EnsureSafeToReturn(mCurrentURI, getter_AddRefs(uri));
589 0 : if (NS_FAILED(rv)) {
590 0 : aError.Throw(rv);
591 : }
592 : }
593 :
594 8 : return uri.forget();
595 : }
596 :
597 : NS_IMETHODIMP
598 4 : nsImageLoadingContent::GetCurrentURI(nsIURI** aURI)
599 : {
600 4 : NS_ENSURE_ARG_POINTER(aURI);
601 :
602 8 : ErrorResult result;
603 4 : *aURI = GetCurrentURI(result).take();
604 4 : return result.StealNSResult();
605 : }
606 :
607 : NS_IMETHODIMP
608 0 : nsImageLoadingContent::LoadImageWithChannel(nsIChannel* aChannel,
609 : nsIStreamListener** aListener)
610 : {
611 : imgLoader* loader =
612 0 : nsContentUtils::GetImgLoaderForChannel(aChannel, GetOurOwnerDoc());
613 0 : if (!loader) {
614 0 : return NS_ERROR_NULL_POINTER;
615 : }
616 :
617 0 : nsCOMPtr<nsIDocument> doc = GetOurOwnerDoc();
618 0 : if (!doc) {
619 : // Don't bother
620 0 : *aListener = nullptr;
621 0 : return NS_OK;
622 : }
623 :
624 : // XXX what should we do with content policies here, if anything?
625 : // Shouldn't that be done before the start of the load?
626 : // XXX what about shouldProcess?
627 :
628 : // Our state might change. Watch it.
629 0 : AutoStateChanger changer(this, true);
630 :
631 : // Do the load.
632 0 : RefPtr<imgRequestProxy>& req = PrepareNextRequest(eImageLoadType_Normal);
633 : nsresult rv = loader->
634 0 : LoadImageWithChannel(aChannel, this, doc, aListener, getter_AddRefs(req));
635 0 : if (NS_SUCCEEDED(rv)) {
636 0 : TrackImage(req);
637 0 : ResetAnimationIfNeeded();
638 0 : return NS_OK;
639 : }
640 :
641 0 : MOZ_ASSERT(!req, "Shouldn't have non-null request here");
642 : // If we don't have a current URI, we might as well store this URI so people
643 : // know what we tried (and failed) to load.
644 0 : if (!mCurrentRequest)
645 0 : aChannel->GetURI(getter_AddRefs(mCurrentURI));
646 :
647 0 : FireEvent(NS_LITERAL_STRING("error"));
648 0 : FireEvent(NS_LITERAL_STRING("loadend"));
649 0 : return rv;
650 : }
651 :
652 : void
653 0 : nsImageLoadingContent::ForceReload(const mozilla::dom::Optional<bool>& aNotify,
654 : mozilla::ErrorResult& aError)
655 : {
656 0 : nsCOMPtr<nsIURI> currentURI;
657 0 : GetCurrentURI(getter_AddRefs(currentURI));
658 0 : if (!currentURI) {
659 0 : aError.Throw(NS_ERROR_NOT_AVAILABLE);
660 0 : return;
661 : }
662 :
663 : // defaults to true
664 0 : bool notify = !aNotify.WasPassed() || aNotify.Value();
665 :
666 : // We keep this flag around along with the old URI even for failed requests
667 : // without a live request object
668 : ImageLoadType loadType = \
669 0 : (mCurrentRequestFlags & REQUEST_IS_IMAGESET) ? eImageLoadType_Imageset
670 0 : : eImageLoadType_Normal;
671 0 : nsresult rv = LoadImage(currentURI, true, notify, loadType, true, nullptr,
672 0 : nsIRequest::VALIDATE_ALWAYS);
673 0 : if (NS_FAILED(rv)) {
674 0 : aError.Throw(rv);
675 : }
676 : }
677 :
678 : NS_IMETHODIMP
679 0 : nsImageLoadingContent::ForceReload(bool aNotify /* = true */,
680 : uint8_t aArgc)
681 : {
682 0 : mozilla::dom::Optional<bool> notify;
683 0 : if (aArgc >= 1) {
684 0 : notify.Construct() = aNotify;
685 : }
686 :
687 0 : ErrorResult result;
688 0 : ForceReload(notify, result);
689 0 : return result.StealNSResult();
690 : }
691 :
692 : NS_IMETHODIMP
693 4 : nsImageLoadingContent::BlockOnload(imgIRequest* aRequest)
694 : {
695 4 : if (aRequest == mCurrentRequest) {
696 4 : NS_ASSERTION(!(mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD),
697 : "Double BlockOnload!?");
698 4 : mCurrentRequestFlags |= REQUEST_BLOCKS_ONLOAD;
699 0 : } else if (aRequest == mPendingRequest) {
700 0 : NS_ASSERTION(!(mPendingRequestFlags & REQUEST_BLOCKS_ONLOAD),
701 : "Double BlockOnload!?");
702 0 : mPendingRequestFlags |= REQUEST_BLOCKS_ONLOAD;
703 : } else {
704 0 : return NS_OK;
705 : }
706 :
707 4 : nsIDocument* doc = GetOurCurrentDoc();
708 4 : if (doc) {
709 4 : doc->BlockOnload();
710 : }
711 :
712 4 : return NS_OK;
713 : }
714 :
715 : NS_IMETHODIMP
716 4 : nsImageLoadingContent::UnblockOnload(imgIRequest* aRequest)
717 : {
718 4 : if (aRequest == mCurrentRequest) {
719 4 : NS_ASSERTION(mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD,
720 : "Double UnblockOnload!?");
721 4 : mCurrentRequestFlags &= ~REQUEST_BLOCKS_ONLOAD;
722 0 : } else if (aRequest == mPendingRequest) {
723 0 : NS_ASSERTION(mPendingRequestFlags & REQUEST_BLOCKS_ONLOAD,
724 : "Double UnblockOnload!?");
725 0 : mPendingRequestFlags &= ~REQUEST_BLOCKS_ONLOAD;
726 : } else {
727 0 : return NS_OK;
728 : }
729 :
730 4 : nsIDocument* doc = GetOurCurrentDoc();
731 4 : if (doc) {
732 4 : doc->UnblockOnload(false);
733 : }
734 :
735 4 : return NS_OK;
736 : }
737 :
738 : /*
739 : * Non-interface methods
740 : */
741 :
742 : nsresult
743 4 : nsImageLoadingContent::LoadImage(const nsAString& aNewURI,
744 : bool aForce,
745 : bool aNotify,
746 : ImageLoadType aImageLoadType)
747 : {
748 : // First, get a document (needed for security checks and the like)
749 4 : nsIDocument* doc = GetOurOwnerDoc();
750 4 : if (!doc) {
751 : // No reason to bother, I think...
752 0 : return NS_OK;
753 : }
754 :
755 : // Pending load/error events need to be canceled in some situations. This
756 : // is not documented in the spec, but can cause site compat problems if not
757 : // done. See bug 1309461 and https://github.com/whatwg/html/issues/1872.
758 4 : CancelPendingEvent();
759 :
760 4 : if (aNewURI.IsEmpty()) {
761 : // Cancel image requests and then fire only error event per spec.
762 0 : CancelImageRequests(aNotify);
763 : // Mark error event as cancelable only for src="" case, since only this
764 : // error causes site compat problem (bug 1308069) for now.
765 0 : FireEvent(NS_LITERAL_STRING("error"), true);
766 0 : return NS_OK;
767 : }
768 :
769 : // Fire loadstart event
770 4 : FireEvent(NS_LITERAL_STRING("loadstart"));
771 :
772 : // Parse the URI string to get image URI
773 8 : nsCOMPtr<nsIURI> imageURI;
774 4 : nsresult rv = StringToURI(aNewURI, doc, getter_AddRefs(imageURI));
775 4 : NS_ENSURE_SUCCESS(rv, rv);
776 : // XXXbiesi fire onerror if that failed?
777 :
778 4 : NS_TryToSetImmutable(imageURI);
779 :
780 4 : return LoadImage(imageURI, aForce, aNotify, aImageLoadType, false, doc);
781 : }
782 :
783 : nsresult
784 4 : nsImageLoadingContent::LoadImage(nsIURI* aNewURI,
785 : bool aForce,
786 : bool aNotify,
787 : ImageLoadType aImageLoadType,
788 : bool aLoadStart,
789 : nsIDocument* aDocument,
790 : nsLoadFlags aLoadFlags)
791 : {
792 4 : MOZ_ASSERT(!mIsStartingImageLoad, "some evil code is reentering LoadImage.");
793 4 : if (mIsStartingImageLoad) {
794 0 : return NS_OK;
795 : }
796 :
797 : // Pending load/error events need to be canceled in some situations. This
798 : // is not documented in the spec, but can cause site compat problems if not
799 : // done. See bug 1309461 and https://github.com/whatwg/html/issues/1872.
800 4 : CancelPendingEvent();
801 :
802 : // Fire loadstart event if required
803 4 : if (aLoadStart) {
804 0 : FireEvent(NS_LITERAL_STRING("loadstart"));
805 : }
806 :
807 4 : if (!mLoadingEnabled) {
808 : // XXX Why fire an error here? seems like the callers to SetLoadingEnabled
809 : // don't want/need it.
810 0 : FireEvent(NS_LITERAL_STRING("error"));
811 0 : FireEvent(NS_LITERAL_STRING("loadend"));
812 0 : return NS_OK;
813 : }
814 :
815 4 : NS_ASSERTION(!aDocument || aDocument == GetOurOwnerDoc(),
816 : "Bogus document passed in");
817 : // First, get a document (needed for security checks and the like)
818 4 : if (!aDocument) {
819 0 : aDocument = GetOurOwnerDoc();
820 0 : if (!aDocument) {
821 : // No reason to bother, I think...
822 0 : return NS_OK;
823 : }
824 : }
825 :
826 8 : AutoRestore<bool> guard(mIsStartingImageLoad);
827 4 : mIsStartingImageLoad = true;
828 :
829 : // Data documents, or documents from DOMParser shouldn't perform image loading.
830 4 : if (aDocument->IsLoadedAsData()) {
831 0 : SetBlockedRequest(nsIContentPolicy::REJECT_REQUEST);
832 :
833 0 : FireEvent(NS_LITERAL_STRING("error"));
834 0 : FireEvent(NS_LITERAL_STRING("loadend"));
835 0 : return NS_OK;
836 : }
837 :
838 : // URI equality check.
839 : //
840 : // We skip the equality check if our current image was blocked, since in that
841 : // case we really do want to try loading again.
842 4 : if (!aForce && NS_CP_ACCEPTED(mImageBlockingStatus)) {
843 8 : nsCOMPtr<nsIURI> currentURI;
844 4 : GetCurrentURI(getter_AddRefs(currentURI));
845 : bool equal;
846 4 : if (currentURI &&
847 4 : NS_SUCCEEDED(currentURI->Equals(aNewURI, &equal)) &&
848 : equal) {
849 : // Nothing to do here.
850 0 : return NS_OK;
851 : }
852 : }
853 :
854 : // From this point on, our image state could change. Watch it.
855 8 : AutoStateChanger changer(this, aNotify);
856 :
857 : // Sanity check.
858 : //
859 : // We use the principal of aDocument to avoid having to QI |this| an extra
860 : // time. It should always be the same as the principal of this node.
861 : #ifdef DEBUG
862 4 : nsIContent* thisContent = AsContent();
863 4 : MOZ_ASSERT(thisContent->NodePrincipal() == aDocument->NodePrincipal(),
864 : "Principal mismatch?");
865 : #endif
866 :
867 4 : nsContentPolicyType policyType = PolicyTypeForLoad(aImageLoadType);
868 :
869 4 : nsLoadFlags loadFlags = aLoadFlags;
870 4 : int32_t corsmode = GetCORSMode();
871 4 : if (corsmode == CORS_ANONYMOUS) {
872 0 : loadFlags |= imgILoader::LOAD_CORS_ANONYMOUS;
873 4 : } else if (corsmode == CORS_USE_CREDENTIALS) {
874 0 : loadFlags |= imgILoader::LOAD_CORS_USE_CREDENTIALS;
875 : }
876 :
877 : // get document wide referrer policy
878 : // if referrer attributes are enabled in preferences, load img referrer attribute
879 : // if the image does not provide a referrer attribute, ignore this
880 4 : net::ReferrerPolicy referrerPolicy = aDocument->GetReferrerPolicy();
881 4 : net::ReferrerPolicy imgReferrerPolicy = GetImageReferrerPolicy();
882 4 : if (imgReferrerPolicy != net::RP_Unset) {
883 0 : referrerPolicy = imgReferrerPolicy;
884 : }
885 :
886 4 : RefPtr<imgRequestProxy>& req = PrepareNextRequest(aImageLoadType);
887 : nsCOMPtr<nsIContent> content =
888 8 : do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
889 : nsCOMPtr<nsINode> thisNode =
890 8 : do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
891 12 : nsresult rv = nsContentUtils::LoadImage(aNewURI,
892 : thisNode,
893 : aDocument,
894 : aDocument->NodePrincipal(),
895 : aDocument->GetDocumentURI(),
896 : referrerPolicy,
897 : this, loadFlags,
898 4 : content->LocalName(),
899 8 : getter_AddRefs(req),
900 : policyType,
901 8 : mUseUrgentStartForChannel);
902 :
903 : // Reset the flag to avoid loading from XPCOM or somewhere again else without
904 : // initiated by user interaction.
905 4 : mUseUrgentStartForChannel = false;
906 :
907 : // Tell the document to forget about the image preload, if any, for
908 : // this URI, now that we might have another imgRequestProxy for it.
909 : // That way if we get canceled later the image load won't continue.
910 4 : aDocument->ForgetImagePreload(aNewURI);
911 :
912 4 : if (NS_SUCCEEDED(rv)) {
913 4 : TrackImage(req);
914 4 : ResetAnimationIfNeeded();
915 :
916 : // Handle cases when we just ended up with a pending request but it's
917 : // already done. In that situation we have to synchronously switch that
918 : // request to being the current request, because websites depend on that
919 : // behavior.
920 4 : if (req == mPendingRequest) {
921 : uint32_t pendingLoadStatus;
922 0 : rv = req->GetImageStatus(&pendingLoadStatus);
923 0 : if (NS_SUCCEEDED(rv) &&
924 0 : (pendingLoadStatus & imgIRequest::STATUS_LOAD_COMPLETE)) {
925 0 : MakePendingRequestCurrent();
926 0 : MOZ_ASSERT(mCurrentRequest,
927 : "How could we not have a current request here?");
928 :
929 0 : nsImageFrame *f = do_QueryFrame(GetOurPrimaryFrame());
930 0 : if (f) {
931 0 : f->NotifyNewCurrentRequest(mCurrentRequest, NS_OK);
932 : }
933 : }
934 : }
935 : } else {
936 0 : MOZ_ASSERT(!req, "Shouldn't have non-null request here");
937 : // If we don't have a current URI, we might as well store this URI so people
938 : // know what we tried (and failed) to load.
939 0 : if (!mCurrentRequest)
940 0 : mCurrentURI = aNewURI;
941 :
942 0 : FireEvent(NS_LITERAL_STRING("error"));
943 0 : FireEvent(NS_LITERAL_STRING("loadend"));
944 : }
945 :
946 4 : return NS_OK;
947 : }
948 :
949 : nsresult
950 0 : nsImageLoadingContent::ForceImageState(bool aForce,
951 : EventStates::InternalType aState)
952 : {
953 0 : mIsImageStateForced = aForce;
954 0 : mForcedImageState = EventStates(aState);
955 0 : return NS_OK;
956 : }
957 :
958 : NS_IMETHODIMP
959 0 : nsImageLoadingContent::GetNaturalWidth(uint32_t* aNaturalWidth)
960 : {
961 0 : NS_ENSURE_ARG_POINTER(aNaturalWidth);
962 :
963 0 : nsCOMPtr<imgIContainer> image;
964 0 : if (mCurrentRequest) {
965 0 : mCurrentRequest->GetImage(getter_AddRefs(image));
966 : }
967 :
968 : int32_t width;
969 0 : if (image && NS_SUCCEEDED(image->GetWidth(&width))) {
970 0 : *aNaturalWidth = width;
971 : } else {
972 0 : *aNaturalWidth = 0;
973 : }
974 :
975 0 : return NS_OK;
976 : }
977 :
978 : NS_IMETHODIMP
979 0 : nsImageLoadingContent::GetNaturalHeight(uint32_t* aNaturalHeight)
980 : {
981 0 : NS_ENSURE_ARG_POINTER(aNaturalHeight);
982 :
983 0 : nsCOMPtr<imgIContainer> image;
984 0 : if (mCurrentRequest) {
985 0 : mCurrentRequest->GetImage(getter_AddRefs(image));
986 : }
987 :
988 : int32_t height;
989 0 : if (image && NS_SUCCEEDED(image->GetHeight(&height))) {
990 0 : *aNaturalHeight = height;
991 : } else {
992 0 : *aNaturalHeight = 0;
993 : }
994 :
995 0 : return NS_OK;
996 : }
997 :
998 : EventStates
999 24 : nsImageLoadingContent::ImageState() const
1000 : {
1001 24 : if (mIsImageStateForced) {
1002 0 : return mForcedImageState;
1003 : }
1004 :
1005 24 : EventStates states;
1006 :
1007 24 : if (mBroken) {
1008 8 : states |= NS_EVENT_STATE_BROKEN;
1009 : }
1010 24 : if (mUserDisabled) {
1011 0 : states |= NS_EVENT_STATE_USERDISABLED;
1012 : }
1013 24 : if (mSuppressed) {
1014 0 : states |= NS_EVENT_STATE_SUPPRESSED;
1015 : }
1016 24 : if (mLoading) {
1017 4 : states |= NS_EVENT_STATE_LOADING;
1018 : }
1019 :
1020 24 : return states;
1021 : }
1022 :
1023 : void
1024 16 : nsImageLoadingContent::UpdateImageState(bool aNotify)
1025 : {
1026 16 : if (mStateChangerDepth > 0) {
1027 : // Ignore this call; we'll update our state when the outermost state changer
1028 : // is destroyed. Need this to work around the fact that some ImageLib
1029 : // stuff is actually sync and hence we can get OnStopDecode called while
1030 : // we're still under LoadImage, and OnStopDecode doesn't know anything about
1031 : // aNotify.
1032 : // XXX - This machinery should be removed after bug 521604.
1033 0 : return;
1034 : }
1035 :
1036 16 : nsIContent* thisContent = AsContent();
1037 :
1038 16 : mLoading = mBroken = mUserDisabled = mSuppressed = false;
1039 :
1040 : // If we were blocked by server-based content policy, we claim to be
1041 : // suppressed. If we were blocked by type-based content policy, we claim to
1042 : // be user-disabled. Otherwise, claim to be broken.
1043 16 : if (mImageBlockingStatus == nsIContentPolicy::REJECT_SERVER) {
1044 0 : mSuppressed = true;
1045 16 : } else if (mImageBlockingStatus == nsIContentPolicy::REJECT_TYPE) {
1046 0 : mUserDisabled = true;
1047 16 : } else if (!mCurrentRequest) {
1048 : // No current request means error, since we weren't disabled or suppressed
1049 0 : mBroken = true;
1050 : } else {
1051 : uint32_t currentLoadStatus;
1052 16 : nsresult rv = mCurrentRequest->GetImageStatus(¤tLoadStatus);
1053 16 : if (NS_FAILED(rv) || (currentLoadStatus & imgIRequest::STATUS_ERROR)) {
1054 0 : mBroken = true;
1055 16 : } else if (!(currentLoadStatus & imgIRequest::STATUS_SIZE_AVAILABLE)) {
1056 4 : mLoading = true;
1057 : }
1058 : }
1059 :
1060 16 : NS_ASSERTION(thisContent->IsElement(), "Not an element?");
1061 16 : thisContent->AsElement()->UpdateState(aNotify);
1062 : }
1063 :
1064 : void
1065 0 : nsImageLoadingContent::CancelImageRequests(bool aNotify)
1066 : {
1067 0 : AutoStateChanger changer(this, aNotify);
1068 0 : ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
1069 0 : ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
1070 0 : }
1071 :
1072 : nsresult
1073 0 : nsImageLoadingContent::UseAsPrimaryRequest(imgRequestProxy* aRequest,
1074 : bool aNotify,
1075 : ImageLoadType aImageLoadType)
1076 : {
1077 : // Our state will change. Watch it.
1078 0 : AutoStateChanger changer(this, aNotify);
1079 :
1080 : // Get rid if our existing images
1081 0 : ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
1082 0 : ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
1083 :
1084 : // Clone the request we were given.
1085 0 : RefPtr<imgRequestProxy>& req = PrepareNextRequest(aImageLoadType);
1086 0 : nsresult rv = aRequest->Clone(this, getter_AddRefs(req));
1087 0 : if (NS_SUCCEEDED(rv)) {
1088 0 : TrackImage(req);
1089 : } else {
1090 0 : MOZ_ASSERT(!req, "Shouldn't have non-null request here");
1091 0 : return rv;
1092 : }
1093 :
1094 0 : return NS_OK;
1095 : }
1096 :
1097 : nsIDocument*
1098 28 : nsImageLoadingContent::GetOurOwnerDoc()
1099 : {
1100 28 : return AsContent()->OwnerDoc();
1101 : }
1102 :
1103 : nsIDocument*
1104 14 : nsImageLoadingContent::GetOurCurrentDoc()
1105 : {
1106 14 : return AsContent()->GetComposedDoc();
1107 : }
1108 :
1109 : nsIFrame*
1110 12 : nsImageLoadingContent::GetOurPrimaryFrame()
1111 : {
1112 12 : return AsContent()->GetPrimaryFrame();
1113 : }
1114 :
1115 0 : nsPresContext* nsImageLoadingContent::GetFramePresContext()
1116 : {
1117 0 : nsIFrame* frame = GetOurPrimaryFrame();
1118 0 : if (!frame) {
1119 0 : return nullptr;
1120 : }
1121 :
1122 0 : return frame->PresContext();
1123 : }
1124 :
1125 : nsresult
1126 8 : nsImageLoadingContent::StringToURI(const nsAString& aSpec,
1127 : nsIDocument* aDocument,
1128 : nsIURI** aURI)
1129 : {
1130 8 : NS_PRECONDITION(aDocument, "Must have a document");
1131 8 : NS_PRECONDITION(aURI, "Null out param");
1132 :
1133 : // (1) Get the base URI
1134 8 : nsIContent* thisContent = AsContent();
1135 16 : nsCOMPtr<nsIURI> baseURL = thisContent->GetBaseURI();
1136 :
1137 : // (2) Get the charset
1138 8 : auto encoding = aDocument->GetDocumentCharacterSet();
1139 :
1140 : // (3) Construct the silly thing
1141 8 : return NS_NewURI(aURI,
1142 : aSpec,
1143 : encoding,
1144 : baseURL,
1145 16 : nsContentUtils::GetIOService());
1146 : }
1147 :
1148 : nsresult
1149 12 : nsImageLoadingContent::FireEvent(const nsAString& aEventType, bool aIsCancelable)
1150 : {
1151 12 : if (nsContentUtils::DocumentInactiveForImageLoads(GetOurOwnerDoc())) {
1152 : // Don't bother to fire any events, especially error events.
1153 0 : return NS_OK;
1154 : }
1155 :
1156 : // We have to fire the event asynchronously so that we won't go into infinite
1157 : // loops in cases when onLoad handlers reset the src and the new src is in
1158 : // cache.
1159 :
1160 24 : nsCOMPtr<nsINode> thisNode = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
1161 :
1162 : RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
1163 36 : new LoadBlockingAsyncEventDispatcher(thisNode, aEventType, false, false);
1164 12 : loadBlockingAsyncDispatcher->PostDOMEvent();
1165 :
1166 12 : if (aIsCancelable) {
1167 0 : mPendingEvent = loadBlockingAsyncDispatcher;
1168 : }
1169 :
1170 12 : return NS_OK;
1171 : }
1172 :
1173 : void
1174 14 : nsImageLoadingContent::AsyncEventRunning(AsyncEventDispatcher* aEvent)
1175 : {
1176 14 : if (mPendingEvent == aEvent) {
1177 0 : mPendingEvent = nullptr;
1178 : }
1179 14 : }
1180 :
1181 : void
1182 8 : nsImageLoadingContent::CancelPendingEvent()
1183 : {
1184 8 : if (mPendingEvent) {
1185 0 : mPendingEvent->Cancel();
1186 0 : mPendingEvent = nullptr;
1187 : }
1188 8 : }
1189 :
1190 : RefPtr<imgRequestProxy>&
1191 4 : nsImageLoadingContent::PrepareNextRequest(ImageLoadType aImageLoadType)
1192 : {
1193 4 : nsImageFrame* imageFrame = do_QueryFrame(GetOurPrimaryFrame());
1194 4 : nsSVGImageFrame* svgImageFrame = do_QueryFrame(GetOurPrimaryFrame());
1195 4 : if (imageFrame || svgImageFrame) {
1196 : // Detect JavaScript-based animations created by changing the |src|
1197 : // attribute on a timer.
1198 0 : TimeStamp now = TimeStamp::Now();
1199 : TimeDuration threshold =
1200 : TimeDuration::FromMilliseconds(
1201 0 : gfxPrefs::ImageInferSrcAnimationThresholdMS());
1202 :
1203 : // If the length of time between request changes is less than the threshold,
1204 : // then force sync decoding to eliminate flicker from the animation.
1205 0 : bool forceSync = (now - mMostRecentRequestChange < threshold);
1206 0 : if (imageFrame) {
1207 0 : imageFrame->SetForceSyncDecoding(forceSync);
1208 : } else {
1209 0 : svgImageFrame->SetForceSyncDecoding(forceSync);
1210 : }
1211 :
1212 0 : mMostRecentRequestChange = now;
1213 : }
1214 :
1215 : // We only want to cancel the existing current request if size is not
1216 : // available. bz says the web depends on this behavior.
1217 : // Otherwise, we get rid of any half-baked request that might be sitting there
1218 : // and make this one current.
1219 : // TODO: Bug 583491
1220 : // Investigate/Cleanup NS_ERROR_IMAGE_SRC_CHANGED use in nsImageFrame.cpp
1221 4 : return HaveSize(mCurrentRequest) ?
1222 : PreparePendingRequest(aImageLoadType) :
1223 4 : PrepareCurrentRequest(aImageLoadType);
1224 : }
1225 :
1226 : nsresult
1227 0 : nsImageLoadingContent::SetBlockedRequest(int16_t aContentDecision)
1228 : {
1229 : // If this is not calling from LoadImage, for example, from ServiceWorker,
1230 : // bail out.
1231 0 : if (!mIsStartingImageLoad) {
1232 0 : return NS_OK;
1233 : }
1234 :
1235 : // Sanity
1236 0 : MOZ_ASSERT(!NS_CP_ACCEPTED(aContentDecision), "Blocked but not?");
1237 :
1238 : // We should never have a pending request after we got blocked.
1239 0 : MOZ_ASSERT(!mPendingRequest, "mPendingRequest should be null.");
1240 :
1241 0 : if (HaveSize(mCurrentRequest)) {
1242 : // PreparePendingRequest set mPendingRequestFlags, now since we've decided
1243 : // to block it, we reset it back to 0.
1244 0 : mPendingRequestFlags = 0;
1245 : } else {
1246 0 : mImageBlockingStatus = aContentDecision;
1247 : }
1248 :
1249 0 : return NS_OK;
1250 : }
1251 :
1252 : RefPtr<imgRequestProxy>&
1253 4 : nsImageLoadingContent::PrepareCurrentRequest(ImageLoadType aImageLoadType)
1254 : {
1255 : // Blocked images go through SetBlockedRequest, which is a separate path. For
1256 : // everything else, we're unblocked.
1257 4 : mImageBlockingStatus = nsIContentPolicy::ACCEPT;
1258 :
1259 : // Get rid of anything that was there previously.
1260 : ClearCurrentRequest(NS_BINDING_ABORTED,
1261 4 : Some(OnNonvisible::DISCARD_IMAGES));
1262 :
1263 4 : if (mNewRequestsWillNeedAnimationReset) {
1264 0 : mCurrentRequestFlags |= REQUEST_NEEDS_ANIMATION_RESET;
1265 : }
1266 :
1267 4 : if (aImageLoadType == eImageLoadType_Imageset) {
1268 0 : mCurrentRequestFlags |= REQUEST_IS_IMAGESET;
1269 : }
1270 :
1271 : // Return a reference.
1272 4 : return mCurrentRequest;
1273 : }
1274 :
1275 : RefPtr<imgRequestProxy>&
1276 0 : nsImageLoadingContent::PreparePendingRequest(ImageLoadType aImageLoadType)
1277 : {
1278 : // Get rid of anything that was there previously.
1279 : ClearPendingRequest(NS_BINDING_ABORTED,
1280 0 : Some(OnNonvisible::DISCARD_IMAGES));
1281 :
1282 0 : if (mNewRequestsWillNeedAnimationReset) {
1283 0 : mPendingRequestFlags |= REQUEST_NEEDS_ANIMATION_RESET;
1284 : }
1285 :
1286 0 : if (aImageLoadType == eImageLoadType_Imageset) {
1287 0 : mPendingRequestFlags |= REQUEST_IS_IMAGESET;
1288 : }
1289 :
1290 : // Return a reference.
1291 0 : return mPendingRequest;
1292 : }
1293 :
1294 : namespace {
1295 :
1296 : class ImageRequestAutoLock
1297 : {
1298 : public:
1299 0 : explicit ImageRequestAutoLock(imgIRequest* aRequest)
1300 0 : : mRequest(aRequest)
1301 : {
1302 0 : if (mRequest) {
1303 0 : mRequest->LockImage();
1304 : }
1305 0 : }
1306 :
1307 0 : ~ImageRequestAutoLock()
1308 0 : {
1309 0 : if (mRequest) {
1310 0 : mRequest->UnlockImage();
1311 : }
1312 0 : }
1313 :
1314 : private:
1315 : nsCOMPtr<imgIRequest> mRequest;
1316 : };
1317 :
1318 : } // namespace
1319 :
1320 : void
1321 0 : nsImageLoadingContent::MakePendingRequestCurrent()
1322 : {
1323 0 : MOZ_ASSERT(mPendingRequest);
1324 :
1325 : // Lock mCurrentRequest for the duration of this method. We do this because
1326 : // PrepareCurrentRequest() might unlock mCurrentRequest. If mCurrentRequest
1327 : // and mPendingRequest are both requests for the same image, unlocking
1328 : // mCurrentRequest before we lock mPendingRequest can cause the lock count
1329 : // to go to 0 and the image to be discarded!
1330 0 : ImageRequestAutoLock autoLock(mCurrentRequest);
1331 :
1332 : ImageLoadType loadType = \
1333 0 : (mPendingRequestFlags & REQUEST_IS_IMAGESET) ? eImageLoadType_Imageset
1334 0 : : eImageLoadType_Normal;
1335 :
1336 0 : PrepareCurrentRequest(loadType) = mPendingRequest;
1337 0 : mPendingRequest = nullptr;
1338 0 : mCurrentRequestFlags = mPendingRequestFlags;
1339 0 : mPendingRequestFlags = 0;
1340 0 : mCurrentRequestRegistered = mPendingRequestRegistered;
1341 0 : mPendingRequestRegistered = false;
1342 0 : ResetAnimationIfNeeded();
1343 0 : }
1344 :
1345 : void
1346 4 : nsImageLoadingContent::ClearCurrentRequest(nsresult aReason,
1347 : const Maybe<OnNonvisible>& aNonvisibleAction)
1348 : {
1349 4 : if (!mCurrentRequest) {
1350 : // Even if we didn't have a current request, we might have been keeping
1351 : // a URI and flags as a placeholder for a failed load. Clear that now.
1352 4 : mCurrentURI = nullptr;
1353 4 : mCurrentRequestFlags = 0;
1354 4 : return;
1355 : }
1356 0 : MOZ_ASSERT(!mCurrentURI,
1357 : "Shouldn't have both mCurrentRequest and mCurrentURI!");
1358 :
1359 : // Deregister this image from the refresh driver so it no longer receives
1360 : // notifications.
1361 0 : nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mCurrentRequest,
1362 0 : &mCurrentRequestRegistered);
1363 :
1364 : // Clean up the request.
1365 0 : UntrackImage(mCurrentRequest, aNonvisibleAction);
1366 0 : mCurrentRequest->CancelAndForgetObserver(aReason);
1367 0 : mCurrentRequest = nullptr;
1368 0 : mCurrentRequestFlags = 0;
1369 : }
1370 :
1371 : void
1372 0 : nsImageLoadingContent::ClearPendingRequest(nsresult aReason,
1373 : const Maybe<OnNonvisible>& aNonvisibleAction)
1374 : {
1375 0 : if (!mPendingRequest)
1376 0 : return;
1377 :
1378 : // Deregister this image from the refresh driver so it no longer receives
1379 : // notifications.
1380 0 : nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mPendingRequest,
1381 0 : &mPendingRequestRegistered);
1382 :
1383 0 : UntrackImage(mPendingRequest, aNonvisibleAction);
1384 0 : mPendingRequest->CancelAndForgetObserver(aReason);
1385 0 : mPendingRequest = nullptr;
1386 0 : mPendingRequestFlags = 0;
1387 : }
1388 :
1389 : bool*
1390 0 : nsImageLoadingContent::GetRegisteredFlagForRequest(imgIRequest* aRequest)
1391 : {
1392 0 : if (aRequest == mCurrentRequest) {
1393 0 : return &mCurrentRequestRegistered;
1394 : }
1395 0 : if (aRequest == mPendingRequest) {
1396 0 : return &mPendingRequestRegistered;
1397 : }
1398 0 : return nullptr;
1399 : }
1400 :
1401 : void
1402 4 : nsImageLoadingContent::ResetAnimationIfNeeded()
1403 : {
1404 8 : if (mCurrentRequest &&
1405 8 : (mCurrentRequestFlags & REQUEST_NEEDS_ANIMATION_RESET)) {
1406 0 : nsCOMPtr<imgIContainer> container;
1407 0 : mCurrentRequest->GetImage(getter_AddRefs(container));
1408 0 : if (container)
1409 0 : container->ResetAnimation();
1410 0 : mCurrentRequestFlags &= ~REQUEST_NEEDS_ANIMATION_RESET;
1411 : }
1412 4 : }
1413 :
1414 : bool
1415 4 : nsImageLoadingContent::HaveSize(imgIRequest *aImage)
1416 : {
1417 : // Handle the null case
1418 4 : if (!aImage)
1419 4 : return false;
1420 :
1421 : // Query the image
1422 : uint32_t status;
1423 0 : nsresult rv = aImage->GetImageStatus(&status);
1424 0 : return (NS_SUCCEEDED(rv) && (status & imgIRequest::STATUS_SIZE_AVAILABLE));
1425 : }
1426 :
1427 : void
1428 25 : nsImageLoadingContent::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
1429 : nsIContent* aBindingParent,
1430 : bool aCompileEventHandlers)
1431 : {
1432 : // We may be entering the document, so if our image should be tracked,
1433 : // track it.
1434 25 : if (!aDocument)
1435 14 : return;
1436 :
1437 11 : TrackImage(mCurrentRequest);
1438 11 : TrackImage(mPendingRequest);
1439 :
1440 11 : if (mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD)
1441 0 : aDocument->BlockOnload();
1442 : }
1443 :
1444 : void
1445 2 : nsImageLoadingContent::UnbindFromTree(bool aDeep, bool aNullParent)
1446 : {
1447 : // We may be leaving the document, so if our image is tracked, untrack it.
1448 2 : nsCOMPtr<nsIDocument> doc = GetOurCurrentDoc();
1449 2 : if (!doc)
1450 2 : return;
1451 :
1452 0 : UntrackImage(mCurrentRequest);
1453 0 : UntrackImage(mPendingRequest);
1454 :
1455 0 : if (mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD)
1456 0 : doc->UnblockOnload(false);
1457 : }
1458 :
1459 : void
1460 0 : nsImageLoadingContent::OnVisibilityChange(Visibility aNewVisibility,
1461 : const Maybe<OnNonvisible>& aNonvisibleAction)
1462 : {
1463 0 : switch (aNewVisibility) {
1464 : case Visibility::APPROXIMATELY_VISIBLE:
1465 0 : TrackImage(mCurrentRequest);
1466 0 : TrackImage(mPendingRequest);
1467 0 : break;
1468 :
1469 : case Visibility::APPROXIMATELY_NONVISIBLE:
1470 0 : UntrackImage(mCurrentRequest, aNonvisibleAction);
1471 0 : UntrackImage(mPendingRequest, aNonvisibleAction);
1472 0 : break;
1473 :
1474 : case Visibility::UNTRACKED:
1475 0 : MOZ_ASSERT_UNREACHABLE("Shouldn't notify for untracked visibility");
1476 : break;
1477 : }
1478 0 : }
1479 :
1480 : void
1481 26 : nsImageLoadingContent::TrackImage(imgIRequest* aImage,
1482 : nsIFrame* aFrame /*= nullptr */)
1483 : {
1484 26 : if (!aImage)
1485 22 : return;
1486 :
1487 4 : MOZ_ASSERT(aImage == mCurrentRequest || aImage == mPendingRequest,
1488 : "Why haven't we heard of this request?");
1489 :
1490 4 : nsIDocument* doc = GetOurCurrentDoc();
1491 4 : if (!doc) {
1492 0 : return;
1493 : }
1494 :
1495 4 : if (!aFrame) {
1496 4 : aFrame = GetOurPrimaryFrame();
1497 : }
1498 :
1499 : /* This line is deceptively simple. It hides a lot of subtlety. Before we
1500 : * create an nsImageFrame we call nsImageFrame::ShouldCreateImageFrameFor
1501 : * to determine if we should create an nsImageFrame or create a frame based
1502 : * on the display of the element (ie inline, block, etc). Inline, block, etc
1503 : * frames don't register for visibility tracking so they will return UNTRACKED
1504 : * from GetVisibility(). So this line is choosing to mark such images as
1505 : * visible. Once the image loads we will get an nsImageFrame and the proper
1506 : * visibility. This is a pitfall of tracking the visibility on the frames
1507 : * instead of the content node.
1508 : */
1509 4 : if (!aFrame || aFrame->GetVisibility() == Visibility::APPROXIMATELY_NONVISIBLE) {
1510 4 : return;
1511 : }
1512 :
1513 0 : if (aImage == mCurrentRequest && !(mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
1514 0 : mCurrentRequestFlags |= REQUEST_IS_TRACKED;
1515 0 : doc->ImageTracker()->Add(mCurrentRequest);
1516 : }
1517 0 : if (aImage == mPendingRequest && !(mPendingRequestFlags & REQUEST_IS_TRACKED)) {
1518 0 : mPendingRequestFlags |= REQUEST_IS_TRACKED;
1519 0 : doc->ImageTracker()->Add(mPendingRequest);
1520 : }
1521 : }
1522 :
1523 : void
1524 0 : nsImageLoadingContent::UntrackImage(imgIRequest* aImage,
1525 : const Maybe<OnNonvisible>& aNonvisibleAction
1526 : /* = Nothing() */)
1527 : {
1528 0 : if (!aImage)
1529 0 : return;
1530 :
1531 0 : MOZ_ASSERT(aImage == mCurrentRequest || aImage == mPendingRequest,
1532 : "Why haven't we heard of this request?");
1533 :
1534 : // We may not be in the document. If we outlived our document that's fine,
1535 : // because the document empties out the tracker and unlocks all locked images
1536 : // on destruction. But if we were never in the document we may need to force
1537 : // discarding the image here, since this is the only chance we have.
1538 0 : nsIDocument* doc = GetOurCurrentDoc();
1539 0 : if (aImage == mCurrentRequest) {
1540 0 : if (doc && (mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
1541 0 : mCurrentRequestFlags &= ~REQUEST_IS_TRACKED;
1542 0 : doc->ImageTracker()->Remove(
1543 : mCurrentRequest,
1544 0 : aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)
1545 : ? ImageTracker::REQUEST_DISCARD
1546 0 : : 0);
1547 0 : } else if (aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)) {
1548 : // If we're not in the document we may still need to be discarded.
1549 0 : aImage->RequestDiscard();
1550 : }
1551 : }
1552 0 : if (aImage == mPendingRequest) {
1553 0 : if (doc && (mPendingRequestFlags & REQUEST_IS_TRACKED)) {
1554 0 : mPendingRequestFlags &= ~REQUEST_IS_TRACKED;
1555 0 : doc->ImageTracker()->Remove(
1556 : mPendingRequest,
1557 0 : aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)
1558 : ? ImageTracker::REQUEST_DISCARD
1559 0 : : 0);
1560 0 : } else if (aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)) {
1561 : // If we're not in the document we may still need to be discarded.
1562 0 : aImage->RequestDiscard();
1563 : }
1564 : }
1565 : }
1566 :
1567 :
1568 : void
1569 0 : nsImageLoadingContent::CreateStaticImageClone(nsImageLoadingContent* aDest) const
1570 : {
1571 0 : aDest->mCurrentRequest = nsContentUtils::GetStaticRequest(mCurrentRequest);
1572 0 : aDest->TrackImage(aDest->mCurrentRequest);
1573 0 : aDest->mForcedImageState = mForcedImageState;
1574 0 : aDest->mImageBlockingStatus = mImageBlockingStatus;
1575 0 : aDest->mLoadingEnabled = mLoadingEnabled;
1576 0 : aDest->mStateChangerDepth = mStateChangerDepth;
1577 0 : aDest->mIsImageStateForced = mIsImageStateForced;
1578 0 : aDest->mLoading = mLoading;
1579 0 : aDest->mBroken = mBroken;
1580 0 : aDest->mUserDisabled = mUserDisabled;
1581 0 : aDest->mSuppressed = mSuppressed;
1582 0 : }
1583 :
1584 : CORSMode
1585 0 : nsImageLoadingContent::GetCORSMode()
1586 : {
1587 0 : return CORS_NONE;
1588 : }
1589 :
1590 11 : nsImageLoadingContent::ImageObserver::ImageObserver(imgINotificationObserver* aObserver)
1591 : : mObserver(aObserver)
1592 11 : , mNext(nullptr)
1593 : {
1594 11 : MOZ_COUNT_CTOR(ImageObserver);
1595 11 : }
1596 :
1597 0 : nsImageLoadingContent::ImageObserver::~ImageObserver()
1598 : {
1599 0 : MOZ_COUNT_DTOR(ImageObserver);
1600 0 : NS_CONTENT_DELETE_LIST_MEMBER(ImageObserver, this, mNext);
1601 0 : }
1602 :
1603 : // Only HTMLInputElement.h overrides this for <img> tags
1604 : // all other subclasses use this one, i.e. ignore referrer attributes
1605 : mozilla::net::ReferrerPolicy
1606 0 : nsImageLoadingContent::GetImageReferrerPolicy()
1607 : {
1608 0 : return mozilla::net::RP_Unset;
1609 : };
|