|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 /* A class that handles style system image loads (other image loads are handled |
|
6 * by the nodes in the content tree). |
|
7 */ |
|
8 |
|
9 #include "mozilla/css/ImageLoader.h" |
|
10 #include "nsContentUtils.h" |
|
11 #include "nsLayoutUtils.h" |
|
12 #include "nsError.h" |
|
13 #include "nsDisplayList.h" |
|
14 #include "FrameLayerBuilder.h" |
|
15 #include "nsSVGEffects.h" |
|
16 #include "imgIContainer.h" |
|
17 |
|
18 namespace mozilla { |
|
19 namespace css { |
|
20 |
|
21 /* static */ PLDHashOperator |
|
22 ImageLoader::SetAnimationModeEnumerator(nsISupports* aKey, FrameSet* aValue, |
|
23 void* aClosure) |
|
24 { |
|
25 imgIRequest* request = static_cast<imgIRequest*>(aKey); |
|
26 |
|
27 uint16_t* mode = static_cast<uint16_t*>(aClosure); |
|
28 |
|
29 #ifdef DEBUG |
|
30 { |
|
31 nsCOMPtr<imgIRequest> debugRequest = do_QueryInterface(aKey); |
|
32 NS_ASSERTION(debugRequest == request, "This is bad"); |
|
33 } |
|
34 #endif |
|
35 |
|
36 nsCOMPtr<imgIContainer> container; |
|
37 request->GetImage(getter_AddRefs(container)); |
|
38 if (!container) { |
|
39 return PL_DHASH_NEXT; |
|
40 } |
|
41 |
|
42 // This can fail if the image is in error, and we don't care. |
|
43 container->SetAnimationMode(*mode); |
|
44 |
|
45 return PL_DHASH_NEXT; |
|
46 } |
|
47 |
|
48 static PLDHashOperator |
|
49 ClearImageHashSet(nsPtrHashKey<ImageLoader::Image>* aKey, void* aClosure) |
|
50 { |
|
51 nsIDocument* doc = static_cast<nsIDocument*>(aClosure); |
|
52 ImageLoader::Image* image = aKey->GetKey(); |
|
53 |
|
54 imgIRequest* request = image->mRequests.GetWeak(doc); |
|
55 if (request) { |
|
56 request->CancelAndForgetObserver(NS_BINDING_ABORTED); |
|
57 } |
|
58 |
|
59 image->mRequests.Remove(doc); |
|
60 |
|
61 return PL_DHASH_REMOVE; |
|
62 } |
|
63 |
|
64 void |
|
65 ImageLoader::DropDocumentReference() |
|
66 { |
|
67 ClearFrames(); |
|
68 mImages.EnumerateEntries(&ClearImageHashSet, mDocument); |
|
69 mDocument = nullptr; |
|
70 } |
|
71 |
|
72 void |
|
73 ImageLoader::AssociateRequestToFrame(imgIRequest* aRequest, |
|
74 nsIFrame* aFrame) |
|
75 { |
|
76 nsCOMPtr<imgINotificationObserver> observer; |
|
77 aRequest->GetNotificationObserver(getter_AddRefs(observer)); |
|
78 if (!observer) { |
|
79 // The request has already been canceled, so ignore it. This is ok because |
|
80 // we're not going to get any more notifications from a canceled request. |
|
81 return; |
|
82 } |
|
83 |
|
84 MOZ_ASSERT(observer == this); |
|
85 |
|
86 FrameSet* frameSet = nullptr; |
|
87 if (mRequestToFrameMap.Get(aRequest, &frameSet)) { |
|
88 NS_ASSERTION(frameSet, "This should never be null!"); |
|
89 } |
|
90 |
|
91 if (!frameSet) { |
|
92 nsAutoPtr<FrameSet> newFrameSet(new FrameSet()); |
|
93 |
|
94 mRequestToFrameMap.Put(aRequest, newFrameSet); |
|
95 frameSet = newFrameSet.forget(); |
|
96 |
|
97 nsPresContext* presContext = GetPresContext(); |
|
98 if (presContext) { |
|
99 nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, |
|
100 aRequest, |
|
101 nullptr); |
|
102 } |
|
103 } |
|
104 |
|
105 RequestSet* requestSet = nullptr; |
|
106 if (mFrameToRequestMap.Get(aFrame, &requestSet)) { |
|
107 NS_ASSERTION(requestSet, "This should never be null"); |
|
108 } |
|
109 |
|
110 if (!requestSet) { |
|
111 nsAutoPtr<RequestSet> newRequestSet(new RequestSet()); |
|
112 |
|
113 mFrameToRequestMap.Put(aFrame, newRequestSet); |
|
114 requestSet = newRequestSet.forget(); |
|
115 } |
|
116 |
|
117 // Add these to the sets, but only if they're not already there. |
|
118 uint32_t i = frameSet->IndexOfFirstElementGt(aFrame); |
|
119 if (i == 0 || aFrame != frameSet->ElementAt(i-1)) { |
|
120 frameSet->InsertElementAt(i, aFrame); |
|
121 } |
|
122 i = requestSet->IndexOfFirstElementGt(aRequest); |
|
123 if (i == 0 || aRequest != requestSet->ElementAt(i-1)) { |
|
124 requestSet->InsertElementAt(i, aRequest); |
|
125 } |
|
126 } |
|
127 |
|
128 void |
|
129 ImageLoader::MaybeRegisterCSSImage(ImageLoader::Image* aImage) |
|
130 { |
|
131 NS_ASSERTION(aImage, "This should never be null!"); |
|
132 |
|
133 bool found = false; |
|
134 aImage->mRequests.GetWeak(mDocument, &found); |
|
135 if (found) { |
|
136 // This document already has a request. |
|
137 return; |
|
138 } |
|
139 |
|
140 imgRequestProxy* canonicalRequest = aImage->mRequests.GetWeak(nullptr); |
|
141 if (!canonicalRequest) { |
|
142 // The image was blocked or something. |
|
143 return; |
|
144 } |
|
145 |
|
146 nsRefPtr<imgRequestProxy> request; |
|
147 |
|
148 // Ignore errors here. If cloning fails for some reason we'll put a null |
|
149 // entry in the hash and we won't keep trying to clone. |
|
150 mInClone = true; |
|
151 canonicalRequest->Clone(this, getter_AddRefs(request)); |
|
152 mInClone = false; |
|
153 |
|
154 aImage->mRequests.Put(mDocument, request); |
|
155 |
|
156 AddImage(aImage); |
|
157 } |
|
158 |
|
159 void |
|
160 ImageLoader::DeregisterCSSImage(ImageLoader::Image* aImage) |
|
161 { |
|
162 RemoveImage(aImage); |
|
163 } |
|
164 |
|
165 void |
|
166 ImageLoader::DisassociateRequestFromFrame(imgIRequest* aRequest, |
|
167 nsIFrame* aFrame) |
|
168 { |
|
169 FrameSet* frameSet = nullptr; |
|
170 RequestSet* requestSet = nullptr; |
|
171 |
|
172 #ifdef DEBUG |
|
173 { |
|
174 nsCOMPtr<imgINotificationObserver> observer; |
|
175 aRequest->GetNotificationObserver(getter_AddRefs(observer)); |
|
176 MOZ_ASSERT(!observer || observer == this); |
|
177 } |
|
178 #endif |
|
179 |
|
180 mRequestToFrameMap.Get(aRequest, &frameSet); |
|
181 mFrameToRequestMap.Get(aFrame, &requestSet); |
|
182 |
|
183 if (frameSet) { |
|
184 frameSet->RemoveElementSorted(aFrame); |
|
185 } |
|
186 if (requestSet) { |
|
187 requestSet->RemoveElementSorted(aRequest); |
|
188 } |
|
189 |
|
190 if (frameSet && !frameSet->Length()) { |
|
191 mRequestToFrameMap.Remove(aRequest); |
|
192 |
|
193 nsPresContext* presContext = GetPresContext(); |
|
194 if (presContext) { |
|
195 nsLayoutUtils::DeregisterImageRequest(presContext, |
|
196 aRequest, |
|
197 nullptr); |
|
198 } |
|
199 } |
|
200 |
|
201 if (requestSet && !requestSet->Length()) { |
|
202 mFrameToRequestMap.Remove(aFrame); |
|
203 } |
|
204 } |
|
205 |
|
206 void |
|
207 ImageLoader::DropRequestsForFrame(nsIFrame* aFrame) |
|
208 { |
|
209 RequestSet* requestSet = nullptr; |
|
210 if (!mFrameToRequestMap.Get(aFrame, &requestSet)) { |
|
211 return; |
|
212 } |
|
213 |
|
214 NS_ASSERTION(requestSet, "This should never be null"); |
|
215 |
|
216 RequestSet frozenRequestSet(*requestSet); |
|
217 for (RequestSet::size_type i = frozenRequestSet.Length(); i != 0; --i) { |
|
218 imgIRequest* request = frozenRequestSet.ElementAt(i - 1); |
|
219 |
|
220 DisassociateRequestFromFrame(request, aFrame); |
|
221 } |
|
222 } |
|
223 |
|
224 void |
|
225 ImageLoader::SetAnimationMode(uint16_t aMode) |
|
226 { |
|
227 NS_ASSERTION(aMode == imgIContainer::kNormalAnimMode || |
|
228 aMode == imgIContainer::kDontAnimMode || |
|
229 aMode == imgIContainer::kLoopOnceAnimMode, |
|
230 "Wrong Animation Mode is being set!"); |
|
231 |
|
232 mRequestToFrameMap.EnumerateRead(SetAnimationModeEnumerator, &aMode); |
|
233 } |
|
234 |
|
235 void |
|
236 ImageLoader::ClearFrames() |
|
237 { |
|
238 mRequestToFrameMap.Clear(); |
|
239 mFrameToRequestMap.Clear(); |
|
240 } |
|
241 |
|
242 void |
|
243 ImageLoader::LoadImage(nsIURI* aURI, nsIPrincipal* aOriginPrincipal, |
|
244 nsIURI* aReferrer, ImageLoader::Image* aImage) |
|
245 { |
|
246 NS_ASSERTION(aImage->mRequests.Count() == 0, "Huh?"); |
|
247 |
|
248 aImage->mRequests.Put(nullptr, nullptr); |
|
249 |
|
250 if (!aURI) { |
|
251 return; |
|
252 } |
|
253 |
|
254 if (!nsContentUtils::CanLoadImage(aURI, mDocument, mDocument, |
|
255 aOriginPrincipal)) { |
|
256 return; |
|
257 } |
|
258 |
|
259 nsRefPtr<imgRequestProxy> request; |
|
260 nsContentUtils::LoadImage(aURI, mDocument, aOriginPrincipal, aReferrer, |
|
261 nullptr, nsIRequest::LOAD_NORMAL, |
|
262 NS_LITERAL_STRING("css"), |
|
263 getter_AddRefs(request)); |
|
264 |
|
265 if (!request) { |
|
266 return; |
|
267 } |
|
268 |
|
269 nsRefPtr<imgRequestProxy> clonedRequest; |
|
270 mInClone = true; |
|
271 nsresult rv = request->Clone(this, getter_AddRefs(clonedRequest)); |
|
272 mInClone = false; |
|
273 |
|
274 if (NS_FAILED(rv)) { |
|
275 return; |
|
276 } |
|
277 |
|
278 aImage->mRequests.Put(nullptr, request); |
|
279 aImage->mRequests.Put(mDocument, clonedRequest); |
|
280 |
|
281 AddImage(aImage); |
|
282 } |
|
283 |
|
284 void |
|
285 ImageLoader::AddImage(ImageLoader::Image* aImage) |
|
286 { |
|
287 NS_ASSERTION(!mImages.Contains(aImage), "Huh?"); |
|
288 if (!mImages.PutEntry(aImage)) { |
|
289 NS_RUNTIMEABORT("OOM"); |
|
290 } |
|
291 } |
|
292 |
|
293 void |
|
294 ImageLoader::RemoveImage(ImageLoader::Image* aImage) |
|
295 { |
|
296 NS_ASSERTION(mImages.Contains(aImage), "Huh?"); |
|
297 mImages.RemoveEntry(aImage); |
|
298 } |
|
299 |
|
300 nsPresContext* |
|
301 ImageLoader::GetPresContext() |
|
302 { |
|
303 if (!mDocument) { |
|
304 return nullptr; |
|
305 } |
|
306 |
|
307 nsIPresShell* shell = mDocument->GetShell(); |
|
308 if (!shell) { |
|
309 return nullptr; |
|
310 } |
|
311 |
|
312 return shell->GetPresContext(); |
|
313 } |
|
314 |
|
315 void InvalidateImagesCallback(nsIFrame* aFrame, |
|
316 FrameLayerBuilder::DisplayItemData* aItem) |
|
317 { |
|
318 nsDisplayItem::Type type = nsDisplayItem::GetDisplayItemTypeFromKey(aItem->GetDisplayItemKey()); |
|
319 uint8_t flags = nsDisplayItem::GetDisplayItemFlagsForType(type); |
|
320 |
|
321 if (flags & nsDisplayItem::TYPE_RENDERS_NO_IMAGES) { |
|
322 return; |
|
323 } |
|
324 |
|
325 aItem->Invalidate(); |
|
326 |
|
327 // Update ancestor rendering observers (-moz-element etc) |
|
328 nsIFrame *f = aFrame; |
|
329 while (f && !f->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) { |
|
330 nsSVGEffects::InvalidateDirectRenderingObservers(f); |
|
331 f = nsLayoutUtils::GetCrossDocParentFrame(f); |
|
332 } |
|
333 } |
|
334 |
|
335 void |
|
336 ImageLoader::DoRedraw(FrameSet* aFrameSet) |
|
337 { |
|
338 NS_ASSERTION(aFrameSet, "Must have a frame set"); |
|
339 NS_ASSERTION(mDocument, "Should have returned earlier!"); |
|
340 |
|
341 FrameSet::size_type length = aFrameSet->Length(); |
|
342 for (FrameSet::size_type i = 0; i < length; i++) { |
|
343 nsIFrame* frame = aFrameSet->ElementAt(i); |
|
344 |
|
345 if (frame->StyleVisibility()->IsVisible()) { |
|
346 if (frame->IsFrameOfType(nsIFrame::eTablePart)) { |
|
347 // Tables don't necessarily build border/background display items |
|
348 // for the individual table part frames, so IterateRetainedDataFor |
|
349 // might not find the right display item. |
|
350 frame->InvalidateFrame(); |
|
351 } else { |
|
352 FrameLayerBuilder::IterateRetainedDataFor(frame, InvalidateImagesCallback); |
|
353 frame->SchedulePaint(); |
|
354 } |
|
355 } |
|
356 } |
|
357 } |
|
358 |
|
359 NS_IMPL_ADDREF(ImageLoader) |
|
360 NS_IMPL_RELEASE(ImageLoader) |
|
361 |
|
362 NS_INTERFACE_MAP_BEGIN(ImageLoader) |
|
363 NS_INTERFACE_MAP_ENTRY(imgINotificationObserver) |
|
364 NS_INTERFACE_MAP_ENTRY(imgIOnloadBlocker) |
|
365 NS_INTERFACE_MAP_END |
|
366 |
|
367 NS_IMETHODIMP |
|
368 ImageLoader::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) |
|
369 { |
|
370 if (aType == imgINotificationObserver::SIZE_AVAILABLE) { |
|
371 nsCOMPtr<imgIContainer> image; |
|
372 aRequest->GetImage(getter_AddRefs(image)); |
|
373 return OnStartContainer(aRequest, image); |
|
374 } |
|
375 |
|
376 if (aType == imgINotificationObserver::IS_ANIMATED) { |
|
377 return OnImageIsAnimated(aRequest); |
|
378 } |
|
379 |
|
380 if (aType == imgINotificationObserver::LOAD_COMPLETE) { |
|
381 return OnStopFrame(aRequest); |
|
382 } |
|
383 |
|
384 if (aType == imgINotificationObserver::FRAME_UPDATE) { |
|
385 return FrameChanged(aRequest); |
|
386 } |
|
387 |
|
388 return NS_OK; |
|
389 } |
|
390 |
|
391 nsresult |
|
392 ImageLoader::OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage) |
|
393 { |
|
394 nsPresContext* presContext = GetPresContext(); |
|
395 if (!presContext) { |
|
396 return NS_OK; |
|
397 } |
|
398 |
|
399 aImage->SetAnimationMode(presContext->ImageAnimationMode()); |
|
400 |
|
401 return NS_OK; |
|
402 } |
|
403 |
|
404 nsresult |
|
405 ImageLoader::OnImageIsAnimated(imgIRequest* aRequest) |
|
406 { |
|
407 if (!mDocument) { |
|
408 return NS_OK; |
|
409 } |
|
410 |
|
411 FrameSet* frameSet = nullptr; |
|
412 if (!mRequestToFrameMap.Get(aRequest, &frameSet)) { |
|
413 return NS_OK; |
|
414 } |
|
415 |
|
416 // Register with the refresh driver now that we are aware that |
|
417 // we are animated. |
|
418 nsPresContext* presContext = GetPresContext(); |
|
419 if (presContext) { |
|
420 nsLayoutUtils::RegisterImageRequest(presContext, |
|
421 aRequest, |
|
422 nullptr); |
|
423 } |
|
424 |
|
425 return NS_OK; |
|
426 } |
|
427 |
|
428 nsresult |
|
429 ImageLoader::OnStopFrame(imgIRequest *aRequest) |
|
430 { |
|
431 if (!mDocument || mInClone) { |
|
432 return NS_OK; |
|
433 } |
|
434 |
|
435 FrameSet* frameSet = nullptr; |
|
436 if (!mRequestToFrameMap.Get(aRequest, &frameSet)) { |
|
437 return NS_OK; |
|
438 } |
|
439 |
|
440 NS_ASSERTION(frameSet, "This should never be null!"); |
|
441 |
|
442 DoRedraw(frameSet); |
|
443 |
|
444 return NS_OK; |
|
445 } |
|
446 |
|
447 nsresult |
|
448 ImageLoader::FrameChanged(imgIRequest *aRequest) |
|
449 { |
|
450 if (!mDocument || mInClone) { |
|
451 return NS_OK; |
|
452 } |
|
453 |
|
454 FrameSet* frameSet = nullptr; |
|
455 if (!mRequestToFrameMap.Get(aRequest, &frameSet)) { |
|
456 return NS_OK; |
|
457 } |
|
458 |
|
459 NS_ASSERTION(frameSet, "This should never be null!"); |
|
460 |
|
461 DoRedraw(frameSet); |
|
462 |
|
463 return NS_OK; |
|
464 } |
|
465 |
|
466 NS_IMETHODIMP |
|
467 ImageLoader::BlockOnload(imgIRequest* aRequest) |
|
468 { |
|
469 if (!mDocument) { |
|
470 return NS_OK; |
|
471 } |
|
472 |
|
473 mDocument->BlockOnload(); |
|
474 |
|
475 return NS_OK; |
|
476 } |
|
477 |
|
478 NS_IMETHODIMP |
|
479 ImageLoader::UnblockOnload(imgIRequest* aRequest) |
|
480 { |
|
481 if (!mDocument) { |
|
482 return NS_OK; |
|
483 } |
|
484 |
|
485 mDocument->UnblockOnload(false); |
|
486 |
|
487 return NS_OK; |
|
488 } |
|
489 |
|
490 } // namespace css |
|
491 } // namespace mozilla |