Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
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/. */
5 /* A class that handles style system image loads (other image loads are handled
6 * by the nodes in the content tree).
7 */
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"
18 namespace mozilla {
19 namespace css {
21 /* static */ PLDHashOperator
22 ImageLoader::SetAnimationModeEnumerator(nsISupports* aKey, FrameSet* aValue,
23 void* aClosure)
24 {
25 imgIRequest* request = static_cast<imgIRequest*>(aKey);
27 uint16_t* mode = static_cast<uint16_t*>(aClosure);
29 #ifdef DEBUG
30 {
31 nsCOMPtr<imgIRequest> debugRequest = do_QueryInterface(aKey);
32 NS_ASSERTION(debugRequest == request, "This is bad");
33 }
34 #endif
36 nsCOMPtr<imgIContainer> container;
37 request->GetImage(getter_AddRefs(container));
38 if (!container) {
39 return PL_DHASH_NEXT;
40 }
42 // This can fail if the image is in error, and we don't care.
43 container->SetAnimationMode(*mode);
45 return PL_DHASH_NEXT;
46 }
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();
54 imgIRequest* request = image->mRequests.GetWeak(doc);
55 if (request) {
56 request->CancelAndForgetObserver(NS_BINDING_ABORTED);
57 }
59 image->mRequests.Remove(doc);
61 return PL_DHASH_REMOVE;
62 }
64 void
65 ImageLoader::DropDocumentReference()
66 {
67 ClearFrames();
68 mImages.EnumerateEntries(&ClearImageHashSet, mDocument);
69 mDocument = nullptr;
70 }
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 }
84 MOZ_ASSERT(observer == this);
86 FrameSet* frameSet = nullptr;
87 if (mRequestToFrameMap.Get(aRequest, &frameSet)) {
88 NS_ASSERTION(frameSet, "This should never be null!");
89 }
91 if (!frameSet) {
92 nsAutoPtr<FrameSet> newFrameSet(new FrameSet());
94 mRequestToFrameMap.Put(aRequest, newFrameSet);
95 frameSet = newFrameSet.forget();
97 nsPresContext* presContext = GetPresContext();
98 if (presContext) {
99 nsLayoutUtils::RegisterImageRequestIfAnimated(presContext,
100 aRequest,
101 nullptr);
102 }
103 }
105 RequestSet* requestSet = nullptr;
106 if (mFrameToRequestMap.Get(aFrame, &requestSet)) {
107 NS_ASSERTION(requestSet, "This should never be null");
108 }
110 if (!requestSet) {
111 nsAutoPtr<RequestSet> newRequestSet(new RequestSet());
113 mFrameToRequestMap.Put(aFrame, newRequestSet);
114 requestSet = newRequestSet.forget();
115 }
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 }
128 void
129 ImageLoader::MaybeRegisterCSSImage(ImageLoader::Image* aImage)
130 {
131 NS_ASSERTION(aImage, "This should never be null!");
133 bool found = false;
134 aImage->mRequests.GetWeak(mDocument, &found);
135 if (found) {
136 // This document already has a request.
137 return;
138 }
140 imgRequestProxy* canonicalRequest = aImage->mRequests.GetWeak(nullptr);
141 if (!canonicalRequest) {
142 // The image was blocked or something.
143 return;
144 }
146 nsRefPtr<imgRequestProxy> request;
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;
154 aImage->mRequests.Put(mDocument, request);
156 AddImage(aImage);
157 }
159 void
160 ImageLoader::DeregisterCSSImage(ImageLoader::Image* aImage)
161 {
162 RemoveImage(aImage);
163 }
165 void
166 ImageLoader::DisassociateRequestFromFrame(imgIRequest* aRequest,
167 nsIFrame* aFrame)
168 {
169 FrameSet* frameSet = nullptr;
170 RequestSet* requestSet = nullptr;
172 #ifdef DEBUG
173 {
174 nsCOMPtr<imgINotificationObserver> observer;
175 aRequest->GetNotificationObserver(getter_AddRefs(observer));
176 MOZ_ASSERT(!observer || observer == this);
177 }
178 #endif
180 mRequestToFrameMap.Get(aRequest, &frameSet);
181 mFrameToRequestMap.Get(aFrame, &requestSet);
183 if (frameSet) {
184 frameSet->RemoveElementSorted(aFrame);
185 }
186 if (requestSet) {
187 requestSet->RemoveElementSorted(aRequest);
188 }
190 if (frameSet && !frameSet->Length()) {
191 mRequestToFrameMap.Remove(aRequest);
193 nsPresContext* presContext = GetPresContext();
194 if (presContext) {
195 nsLayoutUtils::DeregisterImageRequest(presContext,
196 aRequest,
197 nullptr);
198 }
199 }
201 if (requestSet && !requestSet->Length()) {
202 mFrameToRequestMap.Remove(aFrame);
203 }
204 }
206 void
207 ImageLoader::DropRequestsForFrame(nsIFrame* aFrame)
208 {
209 RequestSet* requestSet = nullptr;
210 if (!mFrameToRequestMap.Get(aFrame, &requestSet)) {
211 return;
212 }
214 NS_ASSERTION(requestSet, "This should never be null");
216 RequestSet frozenRequestSet(*requestSet);
217 for (RequestSet::size_type i = frozenRequestSet.Length(); i != 0; --i) {
218 imgIRequest* request = frozenRequestSet.ElementAt(i - 1);
220 DisassociateRequestFromFrame(request, aFrame);
221 }
222 }
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!");
232 mRequestToFrameMap.EnumerateRead(SetAnimationModeEnumerator, &aMode);
233 }
235 void
236 ImageLoader::ClearFrames()
237 {
238 mRequestToFrameMap.Clear();
239 mFrameToRequestMap.Clear();
240 }
242 void
243 ImageLoader::LoadImage(nsIURI* aURI, nsIPrincipal* aOriginPrincipal,
244 nsIURI* aReferrer, ImageLoader::Image* aImage)
245 {
246 NS_ASSERTION(aImage->mRequests.Count() == 0, "Huh?");
248 aImage->mRequests.Put(nullptr, nullptr);
250 if (!aURI) {
251 return;
252 }
254 if (!nsContentUtils::CanLoadImage(aURI, mDocument, mDocument,
255 aOriginPrincipal)) {
256 return;
257 }
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));
265 if (!request) {
266 return;
267 }
269 nsRefPtr<imgRequestProxy> clonedRequest;
270 mInClone = true;
271 nsresult rv = request->Clone(this, getter_AddRefs(clonedRequest));
272 mInClone = false;
274 if (NS_FAILED(rv)) {
275 return;
276 }
278 aImage->mRequests.Put(nullptr, request);
279 aImage->mRequests.Put(mDocument, clonedRequest);
281 AddImage(aImage);
282 }
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 }
293 void
294 ImageLoader::RemoveImage(ImageLoader::Image* aImage)
295 {
296 NS_ASSERTION(mImages.Contains(aImage), "Huh?");
297 mImages.RemoveEntry(aImage);
298 }
300 nsPresContext*
301 ImageLoader::GetPresContext()
302 {
303 if (!mDocument) {
304 return nullptr;
305 }
307 nsIPresShell* shell = mDocument->GetShell();
308 if (!shell) {
309 return nullptr;
310 }
312 return shell->GetPresContext();
313 }
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);
321 if (flags & nsDisplayItem::TYPE_RENDERS_NO_IMAGES) {
322 return;
323 }
325 aItem->Invalidate();
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 }
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!");
341 FrameSet::size_type length = aFrameSet->Length();
342 for (FrameSet::size_type i = 0; i < length; i++) {
343 nsIFrame* frame = aFrameSet->ElementAt(i);
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 }
359 NS_IMPL_ADDREF(ImageLoader)
360 NS_IMPL_RELEASE(ImageLoader)
362 NS_INTERFACE_MAP_BEGIN(ImageLoader)
363 NS_INTERFACE_MAP_ENTRY(imgINotificationObserver)
364 NS_INTERFACE_MAP_ENTRY(imgIOnloadBlocker)
365 NS_INTERFACE_MAP_END
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 }
376 if (aType == imgINotificationObserver::IS_ANIMATED) {
377 return OnImageIsAnimated(aRequest);
378 }
380 if (aType == imgINotificationObserver::LOAD_COMPLETE) {
381 return OnStopFrame(aRequest);
382 }
384 if (aType == imgINotificationObserver::FRAME_UPDATE) {
385 return FrameChanged(aRequest);
386 }
388 return NS_OK;
389 }
391 nsresult
392 ImageLoader::OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage)
393 {
394 nsPresContext* presContext = GetPresContext();
395 if (!presContext) {
396 return NS_OK;
397 }
399 aImage->SetAnimationMode(presContext->ImageAnimationMode());
401 return NS_OK;
402 }
404 nsresult
405 ImageLoader::OnImageIsAnimated(imgIRequest* aRequest)
406 {
407 if (!mDocument) {
408 return NS_OK;
409 }
411 FrameSet* frameSet = nullptr;
412 if (!mRequestToFrameMap.Get(aRequest, &frameSet)) {
413 return NS_OK;
414 }
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 }
425 return NS_OK;
426 }
428 nsresult
429 ImageLoader::OnStopFrame(imgIRequest *aRequest)
430 {
431 if (!mDocument || mInClone) {
432 return NS_OK;
433 }
435 FrameSet* frameSet = nullptr;
436 if (!mRequestToFrameMap.Get(aRequest, &frameSet)) {
437 return NS_OK;
438 }
440 NS_ASSERTION(frameSet, "This should never be null!");
442 DoRedraw(frameSet);
444 return NS_OK;
445 }
447 nsresult
448 ImageLoader::FrameChanged(imgIRequest *aRequest)
449 {
450 if (!mDocument || mInClone) {
451 return NS_OK;
452 }
454 FrameSet* frameSet = nullptr;
455 if (!mRequestToFrameMap.Get(aRequest, &frameSet)) {
456 return NS_OK;
457 }
459 NS_ASSERTION(frameSet, "This should never be null!");
461 DoRedraw(frameSet);
463 return NS_OK;
464 }
466 NS_IMETHODIMP
467 ImageLoader::BlockOnload(imgIRequest* aRequest)
468 {
469 if (!mDocument) {
470 return NS_OK;
471 }
473 mDocument->BlockOnload();
475 return NS_OK;
476 }
478 NS_IMETHODIMP
479 ImageLoader::UnblockOnload(imgIRequest* aRequest)
480 {
481 if (!mDocument) {
482 return NS_OK;
483 }
485 mDocument->UnblockOnload(false);
487 return NS_OK;
488 }
490 } // namespace css
491 } // namespace mozilla