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