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 /* rendering object for replaced elements with image data */
8 
9 #include "nsImageFrame.h"
10 
11 #include "gfx2DGlue.h"
12 #include "gfxContext.h"
13 #include "gfxUtils.h"
14 #include "mozilla/DebugOnly.h"
15 #include "mozilla/Encoding.h"
16 #include "mozilla/EventStates.h"
17 #include "mozilla/gfx/2D.h"
18 #include "mozilla/gfx/Helpers.h"
19 #include "mozilla/gfx/PathHelpers.h"
20 #include "mozilla/layers/WebRenderLayerManager.h"
21 #include "mozilla/MouseEvents.h"
22 #include "mozilla/Unused.h"
23 
24 #include "nsCOMPtr.h"
25 #include "nsFontMetrics.h"
26 #include "nsIImageLoadingContent.h"
27 #include "nsString.h"
28 #include "nsPrintfCString.h"
29 #include "nsPresContext.h"
30 #include "nsIPresShell.h"
31 #include "nsGkAtoms.h"
32 #include "nsIDocument.h"
33 #include "nsContentUtils.h"
34 #include "nsCSSAnonBoxes.h"
35 #include "nsStyleContext.h"
36 #include "nsStyleConsts.h"
37 #include "nsStyleCoord.h"
38 #include "nsStyleUtil.h"
39 #include "nsTransform2D.h"
40 #include "nsImageMap.h"
41 #include "nsIIOService.h"
42 #include "nsILoadGroup.h"
43 #include "nsISupportsPriority.h"
44 #include "nsNetUtil.h"
45 #include "nsNetCID.h"
46 #include "nsCSSRendering.h"
47 #include "nsNameSpaceManager.h"
48 #include <algorithm>
49 #ifdef ACCESSIBILITY
50 #include "nsAccessibilityService.h"
51 #endif
52 #include "nsIDOMNode.h"
53 #include "nsLayoutUtils.h"
54 #include "nsDisplayList.h"
55 #include "nsIContent.h"
56 #include "nsIDocument.h"
57 #include "FrameLayerBuilder.h"
58 #include "nsISelectionController.h"
59 #include "nsISelection.h"
60 #include "nsIURIMutator.h"
61 
62 #include "imgIContainer.h"
63 #include "imgLoader.h"
64 #include "imgRequestProxy.h"
65 
66 #include "nsCSSFrameConstructor.h"
67 #include "nsIDOMRange.h"
68 
69 #include "nsError.h"
70 #include "nsBidiUtils.h"
71 #include "nsBidiPresUtils.h"
72 
73 #include "gfxRect.h"
74 #include "ImageLayers.h"
75 #include "ImageContainer.h"
76 #include "mozilla/StyleSetHandle.h"
77 #include "mozilla/StyleSetHandleInlines.h"
78 #include "nsBlockFrame.h"
79 #include "nsStyleStructInlines.h"
80 
81 #include "mozilla/Preferences.h"
82 
83 #include "mozilla/dom/Link.h"
84 #include "SVGImageContext.h"
85 #include "mozilla/dom/HTMLAnchorElement.h"
86 
87 using namespace mozilla;
88 using namespace mozilla::dom;
89 using namespace mozilla::gfx;
90 using namespace mozilla::image;
91 using namespace mozilla::layers;
92 
93 // sizes (pixels) for image icon, padding and border frame
94 #define ICON_SIZE (16)
95 #define ICON_PADDING (3)
96 #define ALT_BORDER_WIDTH (1)
97 
98 // we must add hooks soon
99 #define IMAGE_EDITOR_CHECK 1
100 
101 // Default alignment value (so we can tell an unset value from a set value)
102 #define ALIGN_UNSET uint8_t(-1)
103 
104 // static icon information
105 StaticRefPtr<nsImageFrame::IconLoad> nsImageFrame::gIconLoad;
106 
107 // cached IO service for loading icons
108 nsIIOService* nsImageFrame::sIOService;
109 
110 // test if the width and height are fixed, looking at the style data
111 // This is used by nsImageFrame::ShouldCreateImageFrameFor and should
112 // not be used for layout decisions.
HaveSpecifiedSize(const nsStylePosition * aStylePosition)113 static bool HaveSpecifiedSize(const nsStylePosition* aStylePosition) {
114   // check the width and height values in the reflow state's style struct
115   // - if width and height are specified as either coord or percentage, then
116   //   the size of the image frame is constrained
117   return aStylePosition->mWidth.IsCoordPercentCalcUnit() &&
118          aStylePosition->mHeight.IsCoordPercentCalcUnit();
119 }
120 
121 // Decide whether we can optimize away reflows that result from the
122 // image's intrinsic size changing.
HaveFixedSize(const ReflowInput & aReflowInput)123 inline bool HaveFixedSize(const ReflowInput& aReflowInput) {
124   NS_ASSERTION(aReflowInput.mStylePosition,
125                "crappy reflowInput - null stylePosition");
126   // Don't try to make this optimization when an image has percentages
127   // in its 'width' or 'height'.  The percentages might be treated like
128   // auto (especially for intrinsic width calculations and for heights).
129   return aReflowInput.mStylePosition->mHeight.ConvertsToLength() &&
130          aReflowInput.mStylePosition->mWidth.ConvertsToLength();
131 }
132 
NS_NewImageFrame(nsIPresShell * aPresShell,nsStyleContext * aContext)133 nsIFrame* NS_NewImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) {
134   return new (aPresShell) nsImageFrame(aContext);
135 }
136 
NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame)137 NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame)
138 
139 nsImageFrame::nsImageFrame(nsStyleContext* aContext, ClassID aID)
140     : nsAtomicContainerFrame(aContext, aID),
141       mComputedSize(0, 0),
142       mIntrinsicRatio(0, 0),
143       mDisplayingIcon(false),
144       mFirstFrameComplete(false),
145       mReflowCallbackPosted(false),
146       mForceSyncDecoding(false) {
147   EnableVisibilityTracking();
148 
149   // We assume our size is not constrained and we haven't gotten an
150   // initial reflow yet, so don't touch those flags.
151   mIntrinsicSize.width.SetCoordValue(0);
152   mIntrinsicSize.height.SetCoordValue(0);
153 }
154 
~nsImageFrame()155 nsImageFrame::~nsImageFrame() {}
156 
157 NS_QUERYFRAME_HEAD(nsImageFrame)
NS_QUERYFRAME_ENTRY(nsImageFrame)158 NS_QUERYFRAME_ENTRY(nsImageFrame)
159 NS_QUERYFRAME_TAIL_INHERITING(nsAtomicContainerFrame)
160 
161 #ifdef ACCESSIBILITY
162 a11y::AccType nsImageFrame::AccessibleType() {
163   // Don't use GetImageMap() to avoid reentrancy into accessibility.
164   if (HasImageMap()) {
165     return a11y::eHTMLImageMapType;
166   }
167 
168   return a11y::eImageType;
169 }
170 #endif
171 
DisconnectMap()172 void nsImageFrame::DisconnectMap() {
173   if (!mImageMap) {
174     return;
175   }
176 
177   mImageMap->Destroy();
178   mImageMap = nullptr;
179 
180 #ifdef ACCESSIBILITY
181   if (nsAccessibilityService* accService = GetAccService()) {
182     accService->RecreateAccessible(PresShell(), mContent);
183   }
184 #endif
185 }
186 
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)187 void nsImageFrame::DestroyFrom(nsIFrame* aDestructRoot,
188                                PostDestroyData& aPostDestroyData) {
189   if (mReflowCallbackPosted) {
190     PresShell()->CancelReflowCallback(this);
191     mReflowCallbackPosted = false;
192   }
193 
194   // Tell our image map, if there is one, to clean up
195   // This causes the nsImageMap to unregister itself as
196   // a DOM listener.
197   DisconnectMap();
198 
199   // set the frame to null so we don't send messages to a dead object.
200   if (mListener) {
201     nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
202     if (imageLoader) {
203       // Notify our image loading content that we are going away so it can
204       // deregister with our refresh driver.
205       imageLoader->FrameDestroyed(this);
206 
207       imageLoader->RemoveNativeObserver(mListener);
208     }
209 
210     reinterpret_cast<nsImageListener*>(mListener.get())->SetFrame(nullptr);
211   }
212 
213   mListener = nullptr;
214 
215   // If we were displaying an icon, take ourselves off the list
216   if (mDisplayingIcon) gIconLoad->RemoveIconObserver(this);
217 
218   nsAtomicContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
219 }
220 
DidSetStyleContext(nsStyleContext * aOldStyleContext)221 void nsImageFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) {
222   nsAtomicContainerFrame::DidSetStyleContext(aOldStyleContext);
223 
224   if (!mImage) {
225     // We'll pick this change up whenever we do get an image.
226     return;
227   }
228 
229   nsStyleImageOrientation newOrientation = StyleVisibility()->mImageOrientation;
230 
231   // We need to update our orientation either if we had no style context before
232   // because this is the first time it's been set, or if the image-orientation
233   // property changed from its previous value.
234   bool shouldUpdateOrientation =
235       !aOldStyleContext ||
236       aOldStyleContext->StyleVisibility()->mImageOrientation != newOrientation;
237 
238   if (shouldUpdateOrientation) {
239     nsCOMPtr<imgIContainer> image(mImage->Unwrap());
240     mImage = nsLayoutUtils::OrientImage(image, newOrientation);
241 
242     UpdateIntrinsicSize(mImage);
243     UpdateIntrinsicRatio(mImage);
244   }
245 }
246 
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)247 void nsImageFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
248                         nsIFrame* aPrevInFlow) {
249   nsAtomicContainerFrame::Init(aContent, aParent, aPrevInFlow);
250 
251   mListener = new nsImageListener(this);
252 
253   nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aContent);
254   if (!imageLoader) {
255     MOZ_CRASH("Why do we have an nsImageFrame here at all?");
256   }
257 
258   imageLoader->AddNativeObserver(mListener);
259 
260   nsPresContext* aPresContext = PresContext();
261 
262   if (!gIconLoad) LoadIcons(aPresContext);
263 
264   // We have a PresContext now, so we need to notify the image content node
265   // that it can register images.
266   imageLoader->FrameCreated(this);
267 
268   // Give image loads associated with an image frame a small priority boost!
269   nsCOMPtr<imgIRequest> currentRequest;
270   imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
271                           getter_AddRefs(currentRequest));
272 
273   if (currentRequest) {
274     uint32_t categoryToBoostPriority = imgIRequest::CATEGORY_FRAME_INIT;
275 
276     // Increase load priority further if intrinsic size might be important for
277     // layout.
278     if (!HaveSpecifiedSize(StylePosition())) {
279       categoryToBoostPriority |= imgIRequest::CATEGORY_SIZE_QUERY;
280     }
281 
282     currentRequest->BoostPriority(categoryToBoostPriority);
283   }
284 }
285 
UpdateIntrinsicSize(imgIContainer * aImage)286 bool nsImageFrame::UpdateIntrinsicSize(imgIContainer* aImage) {
287   NS_PRECONDITION(aImage, "null image");
288   if (!aImage) return false;
289 
290   IntrinsicSize oldIntrinsicSize = mIntrinsicSize;
291   mIntrinsicSize = IntrinsicSize();
292 
293   // Set intrinsic size to match aImage's reported intrinsic width & height.
294   nsSize intrinsicSize;
295   if (NS_SUCCEEDED(aImage->GetIntrinsicSize(&intrinsicSize))) {
296     // If the image has no intrinsic width, intrinsicSize.width will be -1, and
297     // we can leave mIntrinsicSize.width at its default value of
298     // eStyleUnit_None. Otherwise we use intrinsicSize.width. Height works the
299     // same way.
300     if (intrinsicSize.width != -1)
301       mIntrinsicSize.width.SetCoordValue(intrinsicSize.width);
302     if (intrinsicSize.height != -1)
303       mIntrinsicSize.height.SetCoordValue(intrinsicSize.height);
304   } else {
305     // Failure means that the image hasn't loaded enough to report a result. We
306     // treat this case as if the image's intrinsic size was 0x0.
307     mIntrinsicSize.width.SetCoordValue(0);
308     mIntrinsicSize.height.SetCoordValue(0);
309   }
310 
311   return mIntrinsicSize != oldIntrinsicSize;
312 }
313 
UpdateIntrinsicRatio(imgIContainer * aImage)314 bool nsImageFrame::UpdateIntrinsicRatio(imgIContainer* aImage) {
315   NS_PRECONDITION(aImage, "null image");
316 
317   if (!aImage) return false;
318 
319   nsSize oldIntrinsicRatio = mIntrinsicRatio;
320 
321   // Set intrinsic ratio to match aImage's reported intrinsic ratio.
322   if (NS_FAILED(aImage->GetIntrinsicRatio(&mIntrinsicRatio)))
323     mIntrinsicRatio.SizeTo(0, 0);
324 
325   return mIntrinsicRatio != oldIntrinsicRatio;
326 }
327 
GetSourceToDestTransform(nsTransform2D & aTransform)328 bool nsImageFrame::GetSourceToDestTransform(nsTransform2D& aTransform) {
329   // First, figure out destRect (the rect we're rendering into).
330   // NOTE: We use mComputedSize instead of just GetInnerArea()'s own size here,
331   // because GetInnerArea() might be smaller if we're fragmented, whereas
332   // mComputedSize has our full content-box size (which we need for
333   // ComputeObjectDestRect to work correctly).
334   nsRect constraintRect(GetInnerArea().TopLeft(), mComputedSize);
335   constraintRect.y -= GetContinuationOffset();
336 
337   nsRect destRect = nsLayoutUtils::ComputeObjectDestRect(
338       constraintRect, mIntrinsicSize, mIntrinsicRatio, StylePosition());
339   // Set the translation components, based on destRect
340   // XXXbz does this introduce rounding errors because of the cast to
341   // float?  Should we just manually add that stuff in every time
342   // instead?
343   aTransform.SetToTranslate(float(destRect.x), float(destRect.y));
344 
345   // Set the scale factors, based on destRect and intrinsic size.
346   if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord &&
347       mIntrinsicSize.width.GetCoordValue() != 0 &&
348       mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord &&
349       mIntrinsicSize.height.GetCoordValue() != 0 &&
350       mIntrinsicSize.width.GetCoordValue() != destRect.width &&
351       mIntrinsicSize.height.GetCoordValue() != destRect.height) {
352     aTransform.SetScale(
353         float(destRect.width) / float(mIntrinsicSize.width.GetCoordValue()),
354         float(destRect.height) / float(mIntrinsicSize.height.GetCoordValue()));
355     return true;
356   }
357 
358   return false;
359 }
360 
361 // This function checks whether the given request is the current request for our
362 // mContent.
IsPendingLoad(imgIRequest * aRequest) const363 bool nsImageFrame::IsPendingLoad(imgIRequest* aRequest) const {
364   // Default to pending load in case of errors
365   nsCOMPtr<nsIImageLoadingContent> imageLoader(do_QueryInterface(mContent));
366   NS_ASSERTION(imageLoader, "No image loading content?");
367 
368   int32_t requestType = nsIImageLoadingContent::UNKNOWN_REQUEST;
369   imageLoader->GetRequestType(aRequest, &requestType);
370 
371   return requestType != nsIImageLoadingContent::CURRENT_REQUEST;
372 }
373 
SourceRectToDest(const nsIntRect & aRect)374 nsRect nsImageFrame::SourceRectToDest(const nsIntRect& aRect) {
375   // When scaling the image, row N of the source image may (depending on
376   // the scaling function) be used to draw any row in the destination image
377   // between floor(F * (N-1)) and ceil(F * (N+1)), where F is the
378   // floating-point scaling factor.  The same holds true for columns.
379   // So, we start by computing that bound without the floor and ceiling.
380 
381   nsRect r(nsPresContext::CSSPixelsToAppUnits(aRect.x - 1),
382            nsPresContext::CSSPixelsToAppUnits(aRect.y - 1),
383            nsPresContext::CSSPixelsToAppUnits(aRect.width + 2),
384            nsPresContext::CSSPixelsToAppUnits(aRect.height + 2));
385 
386   nsTransform2D sourceToDest;
387   if (!GetSourceToDestTransform(sourceToDest)) {
388     // Failed to generate transform matrix. Return our whole inner area,
389     // to be on the safe side (since this method is used for generating
390     // invalidation rects).
391     return GetInnerArea();
392   }
393 
394   sourceToDest.TransformCoord(&r.x, &r.y, &r.width, &r.height);
395 
396   // Now, round the edges out to the pixel boundary.
397   nscoord scale = nsPresContext::CSSPixelsToAppUnits(1);
398   nscoord right = r.x + r.width;
399   nscoord bottom = r.y + r.height;
400 
401   r.x -= (scale + (r.x % scale)) % scale;
402   r.y -= (scale + (r.y % scale)) % scale;
403   r.width = right + ((scale - (right % scale)) % scale) - r.x;
404   r.height = bottom + ((scale - (bottom % scale)) % scale) - r.y;
405 
406   return r;
407 }
408 
409 // Note that we treat NS_EVENT_STATE_SUPPRESSED images as "OK".  This means
410 // that we'll construct image frames for them as needed if their display is
411 // toggled from "none" (though we won't paint them, unless their visibility
412 // is changed too).
413 #define BAD_STATES \
414   (NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_USERDISABLED | NS_EVENT_STATE_LOADING)
415 
416 // This is a macro so that we don't evaluate the boolean last arg
417 // unless we have to; it can be expensive
418 #define IMAGE_OK(_state, _loadingOK)                                \
419   (!(_state).HasAtLeastOneOfStates(BAD_STATES) ||                   \
420    (!(_state).HasAtLeastOneOfStates(NS_EVENT_STATE_BROKEN |         \
421                                     NS_EVENT_STATE_USERDISABLED) && \
422     (_state).HasState(NS_EVENT_STATE_LOADING) && (_loadingOK)))
423 
424 /* static */
ShouldCreateImageFrameFor(Element * aElement,nsStyleContext * aStyleContext)425 bool nsImageFrame::ShouldCreateImageFrameFor(Element* aElement,
426                                              nsStyleContext* aStyleContext) {
427   EventStates state = aElement->State();
428   if (IMAGE_OK(state, HaveSpecifiedSize(aStyleContext->StylePosition()))) {
429     // Image is fine; do the image frame thing
430     return true;
431   }
432 
433   // Check if we want to use a placeholder box with an icon or just
434   // let the presShell make us into inline text.  Decide as follows:
435   //
436   //  - if our special "force icons" style is set, show an icon
437   //  - else if our "do not show placeholders" pref is set, skip the icon
438   //  - else:
439   //  - if there is a src attribute, there is no alt attribute,
440   //    and this is not an <object> (which could not possibly have
441   //    such an attribute), show an icon.
442   //  - if QuirksMode, and the IMG has a size show an icon.
443   //  - otherwise, skip the icon
444   bool useSizedBox;
445 
446   if (aStyleContext->StyleUIReset()->mForceBrokenImageIcon) {
447     useSizedBox = true;
448   } else if (gIconLoad && gIconLoad->mPrefForceInlineAltText) {
449     useSizedBox = false;
450   } else if (aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::src) &&
451              !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::alt) &&
452              !aElement->IsHTMLElement(nsGkAtoms::object) &&
453              !aElement->IsHTMLElement(nsGkAtoms::input)) {
454     // Use a sized box if we have no alt text.  This means no alt attribute
455     // and the node is not an object or an input (since those always have alt
456     // text).
457     useSizedBox = true;
458   } else if (aStyleContext->PresContext()->CompatibilityMode() !=
459              eCompatibility_NavQuirks) {
460     useSizedBox = false;
461   } else {
462     // check whether we have specified size
463     useSizedBox = HaveSpecifiedSize(aStyleContext->StylePosition());
464   }
465 
466   return useSizedBox;
467 }
468 
Notify(imgIRequest * aRequest,int32_t aType,const nsIntRect * aRect)469 nsresult nsImageFrame::Notify(imgIRequest* aRequest, int32_t aType,
470                               const nsIntRect* aRect) {
471   if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
472     nsCOMPtr<imgIContainer> image;
473     aRequest->GetImage(getter_AddRefs(image));
474     return OnSizeAvailable(aRequest, image);
475   }
476 
477   if (aType == imgINotificationObserver::FRAME_UPDATE) {
478     return OnFrameUpdate(aRequest, aRect);
479   }
480 
481   if (aType == imgINotificationObserver::FRAME_COMPLETE) {
482     mFirstFrameComplete = true;
483   }
484 
485   if (aType == imgINotificationObserver::LOAD_COMPLETE) {
486     uint32_t imgStatus;
487     aRequest->GetImageStatus(&imgStatus);
488     nsresult status =
489         imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
490     return OnLoadComplete(aRequest, status);
491   }
492 
493   return NS_OK;
494 }
495 
SizeIsAvailable(imgIRequest * aRequest)496 static bool SizeIsAvailable(imgIRequest* aRequest) {
497   if (!aRequest) return false;
498 
499   uint32_t imageStatus = 0;
500   nsresult rv = aRequest->GetImageStatus(&imageStatus);
501 
502   return NS_SUCCEEDED(rv) && (imageStatus & imgIRequest::STATUS_SIZE_AVAILABLE);
503 }
504 
OnSizeAvailable(imgIRequest * aRequest,imgIContainer * aImage)505 nsresult nsImageFrame::OnSizeAvailable(imgIRequest* aRequest,
506                                        imgIContainer* aImage) {
507   if (!aImage) return NS_ERROR_INVALID_ARG;
508 
509   /* Get requested animation policy from the pres context:
510    *   normal = 0
511    *   one frame = 1
512    *   one loop = 2
513    */
514   nsPresContext* presContext = PresContext();
515   aImage->SetAnimationMode(presContext->ImageAnimationMode());
516 
517   if (IsPendingLoad(aRequest)) {
518     // We don't care
519     return NS_OK;
520   }
521 
522   bool intrinsicSizeChanged = false;
523   if (SizeIsAvailable(aRequest)) {
524     // This is valid and for the current request, so update our stored image
525     // container, orienting according to our style.
526     mImage = nsLayoutUtils::OrientImage(aImage,
527                                         StyleVisibility()->mImageOrientation);
528 
529     intrinsicSizeChanged = UpdateIntrinsicSize(mImage);
530     intrinsicSizeChanged = UpdateIntrinsicRatio(mImage) || intrinsicSizeChanged;
531   } else {
532     // We no longer have a valid image, so release our stored image container.
533     mImage = mPrevImage = nullptr;
534 
535     // Have to size to 0,0 so that GetDesiredSize recalculates the size.
536     mIntrinsicSize.width.SetCoordValue(0);
537     mIntrinsicSize.height.SetCoordValue(0);
538     mIntrinsicRatio.SizeTo(0, 0);
539     intrinsicSizeChanged = true;
540   }
541 
542   MarkNeedsDisplayItemRebuild();
543 
544   if (intrinsicSizeChanged && (mState & IMAGE_GOTINITIALREFLOW)) {
545     // Now we need to reflow if we have an unconstrained size and have
546     // already gotten the initial reflow
547     if (!(mState & IMAGE_SIZECONSTRAINED)) {
548       nsIPresShell* presShell = presContext->GetPresShell();
549       NS_ASSERTION(presShell, "No PresShell.");
550       if (presShell) {
551         presShell->FrameNeedsReflow(this, nsIPresShell::eStyleChange,
552                                     NS_FRAME_IS_DIRTY);
553       }
554     } else {
555       // We've already gotten the initial reflow, and our size hasn't changed,
556       // so we're ready to request a decode.
557       MaybeDecodeForPredictedSize();
558     }
559 
560     mPrevImage = nullptr;
561   }
562 
563   return NS_OK;
564 }
565 
OnFrameUpdate(imgIRequest * aRequest,const nsIntRect * aRect)566 nsresult nsImageFrame::OnFrameUpdate(imgIRequest* aRequest,
567                                      const nsIntRect* aRect) {
568   NS_ENSURE_ARG_POINTER(aRect);
569 
570   if (!(mState & IMAGE_GOTINITIALREFLOW)) {
571     // Don't bother to do anything; we have a reflow coming up!
572     return NS_OK;
573   }
574 
575   if (mFirstFrameComplete && !StyleVisibility()->IsVisible()) {
576     return NS_OK;
577   }
578 
579   if (IsPendingLoad(aRequest)) {
580     // We don't care
581     return NS_OK;
582   }
583 
584   nsIntRect layerInvalidRect =
585       mImage ? mImage->GetImageSpaceInvalidationRect(*aRect) : *aRect;
586 
587   if (layerInvalidRect.IsEqualInterior(GetMaxSizedIntRect())) {
588     // Invalidate our entire area.
589     InvalidateSelf(nullptr, nullptr);
590     return NS_OK;
591   }
592 
593   nsRect frameInvalidRect = SourceRectToDest(layerInvalidRect);
594   InvalidateSelf(&layerInvalidRect, &frameInvalidRect);
595   return NS_OK;
596 }
597 
InvalidateSelf(const nsIntRect * aLayerInvalidRect,const nsRect * aFrameInvalidRect)598 void nsImageFrame::InvalidateSelf(const nsIntRect* aLayerInvalidRect,
599                                   const nsRect* aFrameInvalidRect) {
600   if (HasProperty(nsIFrame::WebRenderUserDataProperty())) {
601     nsIFrame::WebRenderUserDataTable* userDataTable =
602         GetProperty(nsIFrame::WebRenderUserDataProperty());
603     RefPtr<WebRenderUserData> data;
604     userDataTable->Get(static_cast<uint32_t>(DisplayItemType::TYPE_IMAGE),
605                        getter_AddRefs(data));
606     if (data && data->AsFallbackData()) {
607       data->AsFallbackData()->SetInvalid(true);
608     }
609     SchedulePaint();
610     return;
611   }
612 
613   InvalidateLayer(DisplayItemType::TYPE_IMAGE, aLayerInvalidRect,
614                   aFrameInvalidRect);
615 
616   if (!mFirstFrameComplete) {
617     InvalidateLayer(DisplayItemType::TYPE_ALT_FEEDBACK, aLayerInvalidRect,
618                     aFrameInvalidRect);
619   }
620 }
621 
OnLoadComplete(imgIRequest * aRequest,nsresult aStatus)622 nsresult nsImageFrame::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus) {
623   // Check what request type we're dealing with
624   nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
625   NS_ASSERTION(imageLoader, "Who's notifying us??");
626   int32_t loadType = nsIImageLoadingContent::UNKNOWN_REQUEST;
627   imageLoader->GetRequestType(aRequest, &loadType);
628   if (loadType != nsIImageLoadingContent::CURRENT_REQUEST &&
629       loadType != nsIImageLoadingContent::PENDING_REQUEST) {
630     return NS_ERROR_FAILURE;
631   }
632 
633   NotifyNewCurrentRequest(aRequest, aStatus);
634   return NS_OK;
635 }
636 
NotifyNewCurrentRequest(imgIRequest * aRequest,nsresult aStatus)637 void nsImageFrame::NotifyNewCurrentRequest(imgIRequest* aRequest,
638                                            nsresult aStatus) {
639   nsCOMPtr<imgIContainer> image;
640   aRequest->GetImage(getter_AddRefs(image));
641   NS_ASSERTION(image || NS_FAILED(aStatus),
642                "Successful load with no container?");
643 
644   // May have to switch sizes here!
645   bool intrinsicSizeChanged = true;
646   if (NS_SUCCEEDED(aStatus) && image && SizeIsAvailable(aRequest)) {
647     // Update our stored image container, orienting according to our style.
648     mImage =
649         nsLayoutUtils::OrientImage(image, StyleVisibility()->mImageOrientation);
650 
651     intrinsicSizeChanged = UpdateIntrinsicSize(mImage);
652     intrinsicSizeChanged = UpdateIntrinsicRatio(mImage) || intrinsicSizeChanged;
653   } else {
654     // We no longer have a valid image, so release our stored image container.
655     mImage = mPrevImage = nullptr;
656 
657     // Have to size to 0,0 so that GetDesiredSize recalculates the size
658     mIntrinsicSize.width.SetCoordValue(0);
659     mIntrinsicSize.height.SetCoordValue(0);
660     mIntrinsicRatio.SizeTo(0, 0);
661   }
662 
663   if (mState & IMAGE_GOTINITIALREFLOW) {  // do nothing if we haven't gotten the
664                                           // initial reflow yet
665     if (intrinsicSizeChanged) {
666       if (!(mState & IMAGE_SIZECONSTRAINED)) {
667         nsIPresShell* presShell = PresContext()->GetPresShell();
668         if (presShell) {
669           presShell->FrameNeedsReflow(this, nsIPresShell::eStyleChange,
670                                       NS_FRAME_IS_DIRTY);
671         }
672       } else {
673         // We've already gotten the initial reflow, and our size hasn't changed,
674         // so we're ready to request a decode.
675         MaybeDecodeForPredictedSize();
676       }
677 
678       mPrevImage = nullptr;
679     }
680     // Update border+content to account for image change
681     InvalidateFrame();
682   }
683 }
684 
MaybeDecodeForPredictedSize()685 void nsImageFrame::MaybeDecodeForPredictedSize() {
686   // Check that we're ready to decode.
687   if (!mImage) {
688     return;  // Nothing to do yet.
689   }
690 
691   if (mComputedSize.IsEmpty()) {
692     return;  // We won't draw anything, so no point in decoding.
693   }
694 
695   if (GetVisibility() != Visibility::APPROXIMATELY_VISIBLE) {
696     return;  // We're not visible, so don't decode.
697   }
698 
699   // OK, we're ready to decode. Compute the scale to the screen...
700   nsIPresShell* presShell = PresContext()->GetPresShell();
701   LayoutDeviceToScreenScale2D resolutionToScreen(
702       presShell->GetCumulativeResolution() *
703       nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(this));
704 
705   // ...and this frame's content box...
706   const nsPoint offset =
707       GetOffsetToCrossDoc(nsLayoutUtils::GetReferenceFrame(this));
708   const nsRect frameContentBox = GetInnerArea() + offset;
709 
710   // ...and our predicted dest rect...
711   const int32_t factor = PresContext()->AppUnitsPerDevPixel();
712   const LayoutDeviceRect destRect = LayoutDeviceRect::FromAppUnits(
713       PredictedDestRect(frameContentBox), factor);
714 
715   // ...and use them to compute our predicted size in screen pixels.
716   const ScreenSize predictedScreenSize = destRect.Size() * resolutionToScreen;
717   const ScreenIntSize predictedScreenIntSize =
718       RoundedToInt(predictedScreenSize);
719   if (predictedScreenIntSize.IsEmpty()) {
720     return;
721   }
722 
723   // Determine the optimal image size to use.
724   uint32_t flags = imgIContainer::FLAG_HIGH_QUALITY_SCALING |
725                    imgIContainer::FLAG_ASYNC_NOTIFY;
726   SamplingFilter samplingFilter =
727       nsLayoutUtils::GetSamplingFilterForFrame(this);
728   gfxSize gfxPredictedScreenSize =
729       gfxSize(predictedScreenIntSize.width, predictedScreenIntSize.height);
730   nsIntSize predictedImageSize = mImage->OptimalImageSizeForDest(
731       gfxPredictedScreenSize, imgIContainer::FRAME_CURRENT, samplingFilter,
732       flags);
733 
734   // Request a decode.
735   mImage->RequestDecodeForSize(predictedImageSize, flags);
736 }
737 
PredictedDestRect(const nsRect & aFrameContentBox)738 nsRect nsImageFrame::PredictedDestRect(const nsRect& aFrameContentBox) {
739   // Note: To get the "dest rect", we have to provide the "constraint rect"
740   // (which is the content-box, with the effects of fragmentation undone).
741   nsRect constraintRect(aFrameContentBox.TopLeft(), mComputedSize);
742   constraintRect.y -= GetContinuationOffset();
743 
744   return nsLayoutUtils::ComputeObjectDestRect(constraintRect, mIntrinsicSize,
745                                               mIntrinsicRatio, StylePosition());
746 }
747 
EnsureIntrinsicSizeAndRatio()748 void nsImageFrame::EnsureIntrinsicSizeAndRatio() {
749   // If mIntrinsicSize.width and height are 0, then we need to update from the
750   // image container.
751   if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord &&
752       mIntrinsicSize.width.GetCoordValue() == 0 &&
753       mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord &&
754       mIntrinsicSize.height.GetCoordValue() == 0) {
755     if (mImage) {
756       UpdateIntrinsicSize(mImage);
757       UpdateIntrinsicRatio(mImage);
758     } else {
759       // image request is null or image size not known, probably an
760       // invalid image specified
761       if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
762         bool imageInvalid = false;
763         // check for broken images. valid null images (eg. img src="") are
764         // not considered broken because they have no image requests
765         nsCOMPtr<nsIImageLoadingContent> imageLoader =
766             do_QueryInterface(mContent);
767         if (imageLoader) {
768           nsCOMPtr<imgIRequest> currentRequest;
769           imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
770                                   getter_AddRefs(currentRequest));
771           if (currentRequest) {
772             uint32_t imageStatus;
773             imageInvalid =
774                 NS_SUCCEEDED(currentRequest->GetImageStatus(&imageStatus)) &&
775                 (imageStatus & imgIRequest::STATUS_ERROR);
776           } else {
777             // check if images are user-disabled (or blocked for other
778             // reasons)
779             int16_t imageBlockingStatus;
780             imageLoader->GetImageBlockingStatus(&imageBlockingStatus);
781             imageInvalid = imageBlockingStatus != nsIContentPolicy::ACCEPT;
782           }
783         }
784         // invalid image specified. make the image big enough for the "broken"
785         // icon
786         if (imageInvalid) {
787           nscoord edgeLengthToUse = nsPresContext::CSSPixelsToAppUnits(
788               ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH)));
789           mIntrinsicSize.width.SetCoordValue(edgeLengthToUse);
790           mIntrinsicSize.height.SetCoordValue(edgeLengthToUse);
791           mIntrinsicRatio.SizeTo(1, 1);
792         }
793       }
794     }
795   }
796 }
797 
798 /* virtual */
ComputeSize(gfxContext * aRenderingContext,WritingMode aWM,const LogicalSize & aCBSize,nscoord aAvailableISize,const LogicalSize & aMargin,const LogicalSize & aBorder,const LogicalSize & aPadding,ComputeSizeFlags aFlags)799 LogicalSize nsImageFrame::ComputeSize(
800     gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
801     nscoord aAvailableISize, const LogicalSize& aMargin,
802     const LogicalSize& aBorder, const LogicalSize& aPadding,
803     ComputeSizeFlags aFlags) {
804   EnsureIntrinsicSizeAndRatio();
805 
806   nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
807   NS_ASSERTION(imageLoader, "No content node??");
808   mozilla::IntrinsicSize intrinsicSize(mIntrinsicSize);
809 
810   // XXX(seth): We may sometimes find ourselves in the situation where we have
811   // mImage, but imageLoader's current request does not have a size yet.
812   // This can happen when we load an image speculatively from cache, it fails
813   // to validate, and the new image load hasn't fired SIZE_AVAILABLE yet. In
814   // this situation we should always use mIntrinsicSize, because
815   // GetNaturalWidth/Height will return 0, so we check CurrentRequestHasSize()
816   // below. See bug 1019840. We will fix this in bug 1141395.
817 
818   // Content may override our default dimensions. This is termed as overriding
819   // the intrinsic size by the spec, but all other consumers of mIntrinsic*
820   // values are being used to refer to the real/true size of the image data.
821   if (imageLoader && imageLoader->CurrentRequestHasSize() && mImage &&
822       intrinsicSize.width.GetUnit() == eStyleUnit_Coord &&
823       intrinsicSize.height.GetUnit() == eStyleUnit_Coord) {
824     uint32_t width;
825     uint32_t height;
826     if (NS_SUCCEEDED(imageLoader->GetNaturalWidth(&width)) &&
827         NS_SUCCEEDED(imageLoader->GetNaturalHeight(&height))) {
828       nscoord appWidth = nsPresContext::CSSPixelsToAppUnits((int32_t)width);
829       nscoord appHeight = nsPresContext::CSSPixelsToAppUnits((int32_t)height);
830       // If this image is rotated, we'll need to transpose the natural
831       // width/height.
832       bool coordFlip;
833       if (StyleVisibility()->mImageOrientation.IsFromImage()) {
834         coordFlip = mImage->GetOrientation().SwapsWidthAndHeight();
835       } else {
836         coordFlip = StyleVisibility()->mImageOrientation.SwapsWidthAndHeight();
837       }
838       intrinsicSize.width.SetCoordValue(coordFlip ? appHeight : appWidth);
839       intrinsicSize.height.SetCoordValue(coordFlip ? appWidth : appHeight);
840     }
841   }
842 
843   return ComputeSizeWithIntrinsicDimensions(
844       aRenderingContext, aWM, intrinsicSize, mIntrinsicRatio, aCBSize, aMargin,
845       aBorder, aPadding, aFlags);
846 }
847 
848 // XXXdholbert This function's clients should probably just be calling
849 // GetContentRectRelativeToSelf() directly.
GetInnerArea() const850 nsRect nsImageFrame::GetInnerArea() const {
851   return GetContentRectRelativeToSelf();
852 }
853 
GetMapElement() const854 Element* nsImageFrame::GetMapElement() const {
855   nsAutoString usemap;
856   if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::usemap,
857                                      usemap)) {
858     return mContent->OwnerDoc()->FindImageMap(usemap);
859   }
860   return nullptr;
861 }
862 
863 // get the offset into the content area of the image where aImg starts if it is
864 // a continuation.
GetContinuationOffset() const865 nscoord nsImageFrame::GetContinuationOffset() const {
866   nscoord offset = 0;
867   for (nsIFrame* f = GetPrevInFlow(); f; f = f->GetPrevInFlow()) {
868     offset += f->GetContentRect().height;
869   }
870   NS_ASSERTION(offset >= 0, "bogus GetContentRect");
871   return offset;
872 }
873 
GetMinISize(gfxContext * aRenderingContext)874 /* virtual */ nscoord nsImageFrame::GetMinISize(gfxContext* aRenderingContext) {
875   // XXX The caller doesn't account for constraints of the height,
876   // min-height, and max-height properties.
877   DebugOnly<nscoord> result;
878   DISPLAY_MIN_WIDTH(this, result);
879   EnsureIntrinsicSizeAndRatio();
880   return mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord
881              ? mIntrinsicSize.width.GetCoordValue()
882              : 0;
883 }
884 
GetPrefISize(gfxContext * aRenderingContext)885 /* virtual */ nscoord nsImageFrame::GetPrefISize(
886     gfxContext* aRenderingContext) {
887   // XXX The caller doesn't account for constraints of the height,
888   // min-height, and max-height properties.
889   DebugOnly<nscoord> result;
890   DISPLAY_PREF_WIDTH(this, result);
891   EnsureIntrinsicSizeAndRatio();
892   // convert from normal twips to scaled twips (printing...)
893   return mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord
894              ? mIntrinsicSize.width.GetCoordValue()
895              : 0;
896 }
897 
GetIntrinsicSize()898 /* virtual */ IntrinsicSize nsImageFrame::GetIntrinsicSize() {
899   return mIntrinsicSize;
900 }
901 
GetIntrinsicRatio()902 /* virtual */ nsSize nsImageFrame::GetIntrinsicRatio() {
903   return mIntrinsicRatio;
904 }
905 
Reflow(nsPresContext * aPresContext,ReflowOutput & aMetrics,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)906 void nsImageFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
907                           const ReflowInput& aReflowInput,
908                           nsReflowStatus& aStatus) {
909   MarkInReflow();
910   DO_GLOBAL_REFLOW_COUNT("nsImageFrame");
911   DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
912   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
913   NS_FRAME_TRACE(
914       NS_FRAME_TRACE_CALLS,
915       ("enter nsImageFrame::Reflow: availSize=%d,%d",
916        aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
917 
918   NS_PRECONDITION(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow");
919 
920   // see if we have a frozen size (i.e. a fixed width and height)
921   if (HaveFixedSize(aReflowInput)) {
922     AddStateBits(IMAGE_SIZECONSTRAINED);
923   } else {
924     RemoveStateBits(IMAGE_SIZECONSTRAINED);
925   }
926 
927   // XXXldb These two bits are almost exact opposites (except in the
928   // middle of the initial reflow); remove IMAGE_GOTINITIALREFLOW.
929   if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
930     AddStateBits(IMAGE_GOTINITIALREFLOW);
931   }
932 
933   mComputedSize =
934       nsSize(aReflowInput.ComputedWidth(), aReflowInput.ComputedHeight());
935 
936   aMetrics.Width() = mComputedSize.width;
937   aMetrics.Height() = mComputedSize.height;
938 
939   // add borders and padding
940   aMetrics.Width() += aReflowInput.ComputedPhysicalBorderPadding().LeftRight();
941   aMetrics.Height() += aReflowInput.ComputedPhysicalBorderPadding().TopBottom();
942 
943   if (GetPrevInFlow()) {
944     aMetrics.Width() = GetPrevInFlow()->GetSize().width;
945     nscoord y = GetContinuationOffset();
946     aMetrics.Height() -= y + aReflowInput.ComputedPhysicalBorderPadding().top;
947     aMetrics.Height() = std::max(0, aMetrics.Height());
948   }
949 
950   // we have to split images if we are:
951   //  in Paginated mode, we need to have a constrained height, and have a height
952   //  larger than our available height
953   uint32_t loadStatus = imgIRequest::STATUS_NONE;
954   nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
955   NS_ASSERTION(imageLoader, "No content node??");
956   if (imageLoader) {
957     nsCOMPtr<imgIRequest> currentRequest;
958     imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
959                             getter_AddRefs(currentRequest));
960     if (currentRequest) {
961       currentRequest->GetImageStatus(&loadStatus);
962     }
963   }
964   if (aPresContext->IsPaginated() &&
965       ((loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) ||
966        (mState & IMAGE_SIZECONSTRAINED)) &&
967       NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableHeight() &&
968       aMetrics.Height() > aReflowInput.AvailableHeight()) {
969     // our desired height was greater than 0, so to avoid infinite
970     // splitting, use 1 pixel as the min
971     aMetrics.Height() = std::max(nsPresContext::CSSPixelsToAppUnits(1),
972                                  aReflowInput.AvailableHeight());
973     aStatus.SetIncomplete();
974   }
975 
976   aMetrics.SetOverflowAreasToDesiredBounds();
977   EventStates contentState = mContent->AsElement()->State();
978   bool imageOK = IMAGE_OK(contentState, true);
979 
980   // Determine if the size is available
981   bool haveSize = false;
982   if (loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) {
983     haveSize = true;
984   }
985 
986   if (!imageOK || !haveSize) {
987     nsRect altFeedbackSize(
988         0, 0,
989         nsPresContext::CSSPixelsToAppUnits(
990             ICON_SIZE + 2 * (ICON_PADDING + ALT_BORDER_WIDTH)),
991         nsPresContext::CSSPixelsToAppUnits(
992             ICON_SIZE + 2 * (ICON_PADDING + ALT_BORDER_WIDTH)));
993     // We include the altFeedbackSize in our visual overflow, but not in our
994     // scrollable overflow, since it doesn't really need to be scrolled to
995     // outside the image.
996     static_assert(eOverflowType_LENGTH == 2, "Unknown overflow types?");
997     nsRect& visualOverflow = aMetrics.VisualOverflow();
998     visualOverflow.UnionRect(visualOverflow, altFeedbackSize);
999   } else {
1000     // We've just reflowed and we should have an accurate size, so we're ready
1001     // to request a decode.
1002     MaybeDecodeForPredictedSize();
1003   }
1004   FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
1005 
1006   if ((GetStateBits() & NS_FRAME_FIRST_REFLOW) && !mReflowCallbackPosted) {
1007     nsIPresShell* shell = PresShell();
1008     mReflowCallbackPosted = true;
1009     shell->PostReflowCallback(this);
1010   }
1011 
1012   NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("exit nsImageFrame::Reflow: size=%d,%d",
1013                                         aMetrics.Width(), aMetrics.Height()));
1014   NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics);
1015 }
1016 
ReflowFinished()1017 bool nsImageFrame::ReflowFinished() {
1018   mReflowCallbackPosted = false;
1019 
1020   // XXX(seth): We don't need this. The purpose of updating visibility
1021   // synchronously is to ensure that animated images start animating
1022   // immediately. In the short term, however,
1023   // nsImageLoadingContent::OnUnlockedDraw() is enough to ensure that
1024   // animations start as soon as the image is painted for the first time, and in
1025   // the long term we want to update visibility information from the display
1026   // list whenever we paint, so we don't actually need to do this. However, to
1027   // avoid behavior changes during the transition from the old image visibility
1028   // code, we'll leave it in for now.
1029   UpdateVisibilitySynchronously();
1030 
1031   return false;
1032 }
1033 
ReflowCallbackCanceled()1034 void nsImageFrame::ReflowCallbackCanceled() { mReflowCallbackPosted = false; }
1035 
1036 // Computes the width of the specified string. aMaxWidth specifies the maximum
1037 // width available. Once this limit is reached no more characters are measured.
1038 // The number of characters that fit within the maximum width are returned in
1039 // aMaxFit. NOTE: it is assumed that the fontmetrics have already been selected
1040 // into the rendering context before this is called (for performance). MMP
MeasureString(const char16_t * aString,int32_t aLength,nscoord aMaxWidth,uint32_t & aMaxFit,gfxContext & aContext,nsFontMetrics & aFontMetrics)1041 nscoord nsImageFrame::MeasureString(const char16_t* aString, int32_t aLength,
1042                                     nscoord aMaxWidth, uint32_t& aMaxFit,
1043                                     gfxContext& aContext,
1044                                     nsFontMetrics& aFontMetrics) {
1045   nscoord totalWidth = 0;
1046   aFontMetrics.SetTextRunRTL(false);
1047   nscoord spaceWidth = aFontMetrics.SpaceWidth();
1048 
1049   aMaxFit = 0;
1050   while (aLength > 0) {
1051     // Find the next place we can line break
1052     uint32_t len = aLength;
1053     bool trailingSpace = false;
1054     for (int32_t i = 0; i < aLength; i++) {
1055       if (dom::IsSpaceCharacter(aString[i]) && (i > 0)) {
1056         len = i;  // don't include the space when measuring
1057         trailingSpace = true;
1058         break;
1059       }
1060     }
1061 
1062     // Measure this chunk of text, and see if it fits
1063     nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(
1064         aString, len, this, aFontMetrics, aContext);
1065     bool fits = (totalWidth + width) <= aMaxWidth;
1066 
1067     // If it fits on the line, or it's the first word we've processed then
1068     // include it
1069     if (fits || (0 == totalWidth)) {
1070       // New piece fits
1071       totalWidth += width;
1072 
1073       // If there's a trailing space then see if it fits as well
1074       if (trailingSpace) {
1075         if ((totalWidth + spaceWidth) <= aMaxWidth) {
1076           totalWidth += spaceWidth;
1077         } else {
1078           // Space won't fit. Leave it at the end but don't include it in
1079           // the width
1080           fits = false;
1081         }
1082 
1083         len++;
1084       }
1085 
1086       aMaxFit += len;
1087       aString += len;
1088       aLength -= len;
1089     }
1090 
1091     if (!fits) {
1092       break;
1093     }
1094   }
1095   return totalWidth;
1096 }
1097 
1098 // Formats the alt-text to fit within the specified rectangle. Breaks lines
1099 // between words if a word would extend past the edge of the rectangle
DisplayAltText(nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsString & aAltText,const nsRect & aRect)1100 void nsImageFrame::DisplayAltText(nsPresContext* aPresContext,
1101                                   gfxContext& aRenderingContext,
1102                                   const nsString& aAltText,
1103                                   const nsRect& aRect) {
1104   // Set font and color
1105   aRenderingContext.SetColor(Color::FromABGR(StyleColor()->mColor));
1106   RefPtr<nsFontMetrics> fm =
1107       nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
1108 
1109   // Format the text to display within the formatting rect
1110 
1111   nscoord maxAscent = fm->MaxAscent();
1112   nscoord maxDescent = fm->MaxDescent();
1113   nscoord lineHeight = fm->MaxHeight();  // line-relative, so an x-coordinate
1114                                          // length if writing mode is vertical
1115 
1116   WritingMode wm = GetWritingMode();
1117   bool isVertical = wm.IsVertical();
1118 
1119   fm->SetVertical(isVertical);
1120   fm->SetTextOrientation(StyleVisibility()->mTextOrientation);
1121 
1122   // XXX It would be nice if there was a way to have the font metrics tell
1123   // use where to break the text given a maximum width. At a minimum we need
1124   // to be able to get the break character...
1125   const char16_t* str = aAltText.get();
1126   int32_t strLen = aAltText.Length();
1127   nsPoint pt = wm.IsVerticalRL() ? aRect.TopRight() - nsPoint(lineHeight, 0)
1128                                  : aRect.TopLeft();
1129   nscoord iSize = isVertical ? aRect.height : aRect.width;
1130 
1131   if (!aPresContext->BidiEnabled() && HasRTLChars(aAltText)) {
1132     aPresContext->SetBidiEnabled();
1133   }
1134 
1135   // Always show the first line, even if we have to clip it below
1136   bool firstLine = true;
1137   while (strLen > 0) {
1138     if (!firstLine) {
1139       // If we've run out of space, break out of the loop
1140       if ((!isVertical && (pt.y + maxDescent) >= aRect.YMost()) ||
1141           (wm.IsVerticalRL() && (pt.x + maxDescent < aRect.x)) ||
1142           (wm.IsVerticalLR() && (pt.x + maxDescent >= aRect.XMost()))) {
1143         break;
1144       }
1145     }
1146 
1147     // Determine how much of the text to display on this line
1148     uint32_t maxFit;  // number of characters that fit
1149     nscoord strWidth =
1150         MeasureString(str, strLen, iSize, maxFit, aRenderingContext, *fm);
1151 
1152     // Display the text
1153     nsresult rv = NS_ERROR_FAILURE;
1154 
1155     if (aPresContext->BidiEnabled()) {
1156       nsBidiDirection dir;
1157       nscoord x, y;
1158 
1159       if (isVertical) {
1160         x = pt.x + maxDescent;
1161         if (wm.IsBidiLTR()) {
1162           y = aRect.y;
1163           dir = NSBIDI_LTR;
1164         } else {
1165           y = aRect.YMost() - strWidth;
1166           dir = NSBIDI_RTL;
1167         }
1168       } else {
1169         y = pt.y + maxAscent;
1170         if (wm.IsBidiLTR()) {
1171           x = aRect.x;
1172           dir = NSBIDI_LTR;
1173         } else {
1174           x = aRect.XMost() - strWidth;
1175           dir = NSBIDI_RTL;
1176         }
1177       }
1178 
1179       rv = nsBidiPresUtils::RenderText(
1180           str, maxFit, dir, aPresContext, aRenderingContext,
1181           aRenderingContext.GetDrawTarget(), *fm, x, y);
1182     }
1183     if (NS_FAILED(rv)) {
1184       nsLayoutUtils::DrawUniDirString(str, maxFit,
1185                                       isVertical
1186                                           ? nsPoint(pt.x + maxDescent, pt.y)
1187                                           : nsPoint(pt.x, pt.y + maxAscent),
1188                                       *fm, aRenderingContext);
1189     }
1190 
1191     // Move to the next line
1192     str += maxFit;
1193     strLen -= maxFit;
1194     if (wm.IsVerticalRL()) {
1195       pt.x -= lineHeight;
1196     } else if (wm.IsVerticalLR()) {
1197       pt.x += lineHeight;
1198     } else {
1199       pt.y += lineHeight;
1200     }
1201 
1202     firstLine = false;
1203   }
1204 }
1205 
1206 struct nsRecessedBorder : public nsStyleBorder {
nsRecessedBordernsRecessedBorder1207   nsRecessedBorder(nscoord aBorderWidth, nsPresContext* aPresContext)
1208       : nsStyleBorder(aPresContext) {
1209     NS_FOR_CSS_SIDES(side) {
1210       mBorderColor[side] = StyleComplexColor::FromColor(NS_RGB(0, 0, 0));
1211       mBorder.Side(side) = aBorderWidth;
1212       // Note: use SetBorderStyle here because we want to affect
1213       // mComputedBorder
1214       SetBorderStyle(side, NS_STYLE_BORDER_STYLE_INSET);
1215     }
1216   }
1217 };
1218 
1219 class nsDisplayAltFeedback : public nsDisplayItem {
1220  public:
nsDisplayAltFeedback(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame)1221   nsDisplayAltFeedback(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
1222       : nsDisplayItem(aBuilder, aFrame) {}
1223 
AllocateGeometry(nsDisplayListBuilder * aBuilder)1224   virtual nsDisplayItemGeometry* AllocateGeometry(
1225       nsDisplayListBuilder* aBuilder) override {
1226     return new nsDisplayItemGenericImageGeometry(this, aBuilder);
1227   }
1228 
ComputeInvalidationRegion(nsDisplayListBuilder * aBuilder,const nsDisplayItemGeometry * aGeometry,nsRegion * aInvalidRegion) const1229   virtual void ComputeInvalidationRegion(
1230       nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
1231       nsRegion* aInvalidRegion) const override {
1232     auto geometry =
1233         static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
1234 
1235     if (aBuilder->ShouldSyncDecodeImages() &&
1236         geometry->ShouldInvalidateToSyncDecodeImages()) {
1237       bool snap;
1238       aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
1239     }
1240 
1241     nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry,
1242                                              aInvalidRegion);
1243   }
1244 
GetBounds(nsDisplayListBuilder * aBuilder,bool * aSnap) const1245   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
1246                            bool* aSnap) const override {
1247     *aSnap = false;
1248     return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
1249   }
1250 
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)1251   virtual void Paint(nsDisplayListBuilder* aBuilder,
1252                      gfxContext* aCtx) override {
1253     // Always sync decode, because these icons are UI, and since they're not
1254     // discardable we'll pay the price of sync decoding at most once.
1255     uint32_t flags = imgIContainer::FLAG_SYNC_DECODE;
1256 
1257     nsImageFrame* f = static_cast<nsImageFrame*>(mFrame);
1258     ImgDrawResult result =
1259         f->DisplayAltFeedback(*aCtx, mVisibleRect, ToReferenceFrame(), flags);
1260 
1261     nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
1262   }
1263 
1264   NS_DISPLAY_DECL_NAME("AltFeedback", TYPE_ALT_FEEDBACK)
1265 };
1266 
DisplayAltFeedback(gfxContext & aRenderingContext,const nsRect & aDirtyRect,nsPoint aPt,uint32_t aFlags)1267 ImgDrawResult nsImageFrame::DisplayAltFeedback(gfxContext& aRenderingContext,
1268                                                const nsRect& aDirtyRect,
1269                                                nsPoint aPt, uint32_t aFlags) {
1270   // We should definitely have a gIconLoad here.
1271   MOZ_ASSERT(gIconLoad, "How did we succeed in Init then?");
1272 
1273   // Whether we draw the broken or loading icon.
1274   bool isLoading = IMAGE_OK(GetContent()->AsElement()->State(), true);
1275 
1276   // Calculate the inner area
1277   nsRect inner = GetInnerArea() + aPt;
1278 
1279   // Display a recessed one pixel border
1280   nscoord borderEdgeWidth =
1281       nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH);
1282 
1283   // if inner area is empty, then make it big enough for at least the icon
1284   if (inner.IsEmpty()) {
1285     inner.SizeTo(2 * (nsPresContext::CSSPixelsToAppUnits(
1286                          ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)),
1287                  2 * (nsPresContext::CSSPixelsToAppUnits(
1288                          ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)));
1289   }
1290 
1291   // Make sure we have enough room to actually render the border within
1292   // our frame bounds
1293   if ((inner.width < 2 * borderEdgeWidth) ||
1294       (inner.height < 2 * borderEdgeWidth)) {
1295     return ImgDrawResult::SUCCESS;
1296   }
1297 
1298   // Paint the border
1299   if (!isLoading || gIconLoad->mPrefShowLoadingPlaceholder) {
1300     nsRecessedBorder recessedBorder(borderEdgeWidth, PresContext());
1301 
1302     // Assert that we're not drawing a border-image here; if we were, we
1303     // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder
1304     // returns.
1305     MOZ_ASSERT(recessedBorder.mBorderImageSource.GetType() ==
1306                eStyleImageType_Null);
1307 
1308     Unused << nsCSSRendering::PaintBorderWithStyleBorder(
1309         PresContext(), aRenderingContext, this, inner, inner, recessedBorder,
1310         mStyleContext, PaintBorderFlags::SYNC_DECODE_IMAGES);
1311   }
1312 
1313   // Adjust the inner rect to account for the one pixel recessed border,
1314   // and a six pixel padding on each edge
1315   inner.Deflate(
1316       nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH),
1317       nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH));
1318   if (inner.IsEmpty()) {
1319     return ImgDrawResult::SUCCESS;
1320   }
1321 
1322   DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
1323 
1324   // Clip so we don't render outside the inner rect
1325   aRenderingContext.Save();
1326   aRenderingContext.Clip(NSRectToSnappedRect(
1327       inner, PresContext()->AppUnitsPerDevPixel(), *drawTarget));
1328 
1329   ImgDrawResult result = ImgDrawResult::NOT_READY;
1330 
1331   // Check if we should display image placeholders
1332   if (!gIconLoad->mPrefShowPlaceholders ||
1333       (isLoading && !gIconLoad->mPrefShowLoadingPlaceholder)) {
1334     result = ImgDrawResult::SUCCESS;
1335   } else {
1336     nscoord size = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE);
1337 
1338     imgIRequest* request = isLoading ? nsImageFrame::gIconLoad->mLoadingImage
1339                                      : nsImageFrame::gIconLoad->mBrokenImage;
1340 
1341     // If we weren't previously displaying an icon, register ourselves
1342     // as an observer for load and animation updates and flag that we're
1343     // doing so now.
1344     if (request && !mDisplayingIcon) {
1345       gIconLoad->AddIconObserver(this);
1346       mDisplayingIcon = true;
1347     }
1348 
1349     WritingMode wm = GetWritingMode();
1350     bool flushRight =
1351         (!wm.IsVertical() && !wm.IsBidiLTR()) || wm.IsVerticalRL();
1352 
1353     // If the icon in question is loaded, draw it.
1354     uint32_t imageStatus = 0;
1355     if (request) request->GetImageStatus(&imageStatus);
1356     if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE &&
1357         !(imageStatus & imgIRequest::STATUS_ERROR)) {
1358       nsCOMPtr<imgIContainer> imgCon;
1359       request->GetImage(getter_AddRefs(imgCon));
1360       MOZ_ASSERT(imgCon, "Load complete, but no image container?");
1361       nsRect dest(flushRight ? inner.XMost() - size : inner.x, inner.y, size,
1362                   size);
1363       result = nsLayoutUtils::DrawSingleImage(
1364           aRenderingContext, PresContext(), imgCon,
1365           nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect,
1366           /* no SVGImageContext */ Nothing(), aFlags);
1367     }
1368 
1369     // If we could not draw the icon, just draw some graffiti in the mean time.
1370     if (result == ImgDrawResult::NOT_READY) {
1371       ColorPattern color(ToDeviceColor(Color(1.f, 0.f, 0.f, 1.f)));
1372 
1373       nscoord iconXPos = flushRight ? inner.XMost() - size : inner.x;
1374 
1375       // stroked rect:
1376       nsRect rect(iconXPos, inner.y, size, size);
1377       Rect devPxRect = ToRect(nsLayoutUtils::RectToGfxRect(
1378           rect, PresContext()->AppUnitsPerDevPixel()));
1379       drawTarget->StrokeRect(devPxRect, color);
1380 
1381       // filled circle in bottom right quadrant of stroked rect:
1382       nscoord twoPX = nsPresContext::CSSPixelsToAppUnits(2);
1383       rect = nsRect(iconXPos + size / 2, inner.y + size / 2, size / 2 - twoPX,
1384                     size / 2 - twoPX);
1385       devPxRect = ToRect(nsLayoutUtils::RectToGfxRect(
1386           rect, PresContext()->AppUnitsPerDevPixel()));
1387       RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder();
1388       AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size());
1389       RefPtr<Path> ellipse = builder->Finish();
1390       drawTarget->Fill(ellipse, color);
1391     }
1392 
1393     // Reduce the inner rect by the width of the icon, and leave an
1394     // additional ICON_PADDING pixels for padding
1395     int32_t paddedIconSize =
1396         nsPresContext::CSSPixelsToAppUnits(ICON_SIZE + ICON_PADDING);
1397     if (wm.IsVertical()) {
1398       inner.y += paddedIconSize;
1399       inner.height -= paddedIconSize;
1400     } else {
1401       if (!flushRight) {
1402         inner.x += paddedIconSize;
1403       }
1404       inner.width -= paddedIconSize;
1405     }
1406   }
1407 
1408   // If there's still room, display the alt-text
1409   if (!inner.IsEmpty()) {
1410     nsIContent* content = GetContent();
1411     if (content) {
1412       nsAutoString altText;
1413       nsCSSFrameConstructor::GetAlternateTextFor(
1414           content->AsElement(), content->NodeInfo()->NameAtom(), altText);
1415       DisplayAltText(PresContext(), aRenderingContext, altText, inner);
1416     }
1417   }
1418 
1419   aRenderingContext.Restore();
1420 
1421   return result;
1422 }
1423 
1424 #ifdef DEBUG
PaintDebugImageMap(nsIFrame * aFrame,DrawTarget * aDrawTarget,const nsRect & aDirtyRect,nsPoint aPt)1425 static void PaintDebugImageMap(nsIFrame* aFrame, DrawTarget* aDrawTarget,
1426                                const nsRect& aDirtyRect, nsPoint aPt) {
1427   nsImageFrame* f = static_cast<nsImageFrame*>(aFrame);
1428   nsRect inner = f->GetInnerArea() + aPt;
1429   gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
1430       inner.TopLeft(), aFrame->PresContext()->AppUnitsPerDevPixel());
1431   AutoRestoreTransform autoRestoreTransform(aDrawTarget);
1432   aDrawTarget->SetTransform(
1433       aDrawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
1434   f->GetImageMap()->Draw(
1435       aFrame, *aDrawTarget,
1436       ColorPattern(ToDeviceColor(Color(0.f, 0.f, 0.f, 1.f))));
1437 }
1438 #endif
1439 
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)1440 void nsDisplayImage::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
1441   uint32_t flags = imgIContainer::FLAG_NONE;
1442   if (aBuilder->ShouldSyncDecodeImages()) {
1443     flags |= imgIContainer::FLAG_SYNC_DECODE;
1444   }
1445   if (aBuilder->IsPaintingToWindow()) {
1446     flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
1447   }
1448 
1449   ImgDrawResult result = static_cast<nsImageFrame*>(mFrame)->PaintImage(
1450       *aCtx, ToReferenceFrame(), mVisibleRect, mImage, flags);
1451 
1452   if (result == ImgDrawResult::NOT_READY ||
1453       result == ImgDrawResult::INCOMPLETE ||
1454       result == ImgDrawResult::TEMPORARY_ERROR) {
1455     // If the current image failed to paint because it's still loading or
1456     // decoding, try painting the previous image.
1457     if (mPrevImage) {
1458       result = static_cast<nsImageFrame*>(mFrame)->PaintImage(
1459           *aCtx, ToReferenceFrame(), mVisibleRect, mPrevImage, flags);
1460     }
1461   }
1462 
1463   nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
1464 }
1465 
AllocateGeometry(nsDisplayListBuilder * aBuilder)1466 nsDisplayItemGeometry* nsDisplayImage::AllocateGeometry(
1467     nsDisplayListBuilder* aBuilder) {
1468   return new nsDisplayItemGenericImageGeometry(this, aBuilder);
1469 }
1470 
ComputeInvalidationRegion(nsDisplayListBuilder * aBuilder,const nsDisplayItemGeometry * aGeometry,nsRegion * aInvalidRegion) const1471 void nsDisplayImage::ComputeInvalidationRegion(
1472     nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
1473     nsRegion* aInvalidRegion) const {
1474   auto geometry =
1475       static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
1476 
1477   if (aBuilder->ShouldSyncDecodeImages() &&
1478       geometry->ShouldInvalidateToSyncDecodeImages()) {
1479     bool snap;
1480     aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
1481   }
1482 
1483   nsDisplayImageContainer::ComputeInvalidationRegion(aBuilder, aGeometry,
1484                                                      aInvalidRegion);
1485 }
1486 
GetImage()1487 already_AddRefed<imgIContainer> nsDisplayImage::GetImage() {
1488   nsCOMPtr<imgIContainer> image = mImage;
1489   return image.forget();
1490 }
1491 
GetDestRect() const1492 nsRect nsDisplayImage::GetDestRect() const {
1493   bool snap = true;
1494   const nsRect frameContentBox = GetBounds(&snap);
1495 
1496   nsImageFrame* imageFrame = static_cast<nsImageFrame*>(mFrame);
1497   return imageFrame->PredictedDestRect(frameContentBox);
1498 }
1499 
GetLayerState(nsDisplayListBuilder * aBuilder,LayerManager * aManager,const ContainerLayerParameters & aParameters)1500 LayerState nsDisplayImage::GetLayerState(
1501     nsDisplayListBuilder* aBuilder, LayerManager* aManager,
1502     const ContainerLayerParameters& aParameters) {
1503   if (!nsDisplayItem::ForceActiveLayers() &&
1504       !ShouldUseAdvancedLayer(aManager, gfxPrefs::LayersAllowImageLayers)) {
1505     bool animated = false;
1506     if (!nsLayoutUtils::AnimatedImageLayersEnabled() ||
1507         mImage->GetType() != imgIContainer::TYPE_RASTER ||
1508         NS_FAILED(mImage->GetAnimated(&animated)) || !animated) {
1509       if (!aManager->IsCompositingCheap() ||
1510           !nsLayoutUtils::GPUImageScalingEnabled()) {
1511         return LAYER_NONE;
1512       }
1513     }
1514 
1515     if (!animated) {
1516       int32_t imageWidth;
1517       int32_t imageHeight;
1518       mImage->GetWidth(&imageWidth);
1519       mImage->GetHeight(&imageHeight);
1520 
1521       NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!");
1522 
1523       const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
1524       const LayoutDeviceRect destRect =
1525           LayoutDeviceRect::FromAppUnits(GetDestRect(), factor);
1526       const LayerRect destLayerRect = destRect * aParameters.Scale();
1527 
1528       // Calculate the scaling factor for the frame.
1529       const gfxSize scale = gfxSize(destLayerRect.width / imageWidth,
1530                                     destLayerRect.height / imageHeight);
1531 
1532       // If we are not scaling at all, no point in separating this into a layer.
1533       if (scale.width == 1.0f && scale.height == 1.0f) {
1534         return LAYER_NONE;
1535       }
1536 
1537       // If the target size is pretty small, no point in using a layer.
1538       if (destLayerRect.width * destLayerRect.height < 64 * 64) {
1539         return LAYER_NONE;
1540       }
1541     }
1542   }
1543 
1544   if (!CanOptimizeToImageLayer(aManager, aBuilder)) {
1545     return LAYER_NONE;
1546   }
1547 
1548   // Image layer doesn't support draw focus ring for image map.
1549   nsImageFrame* f = static_cast<nsImageFrame*>(mFrame);
1550   if (f->HasImageMap()) {
1551     return LAYER_NONE;
1552   }
1553 
1554   return LAYER_ACTIVE;
1555 }
1556 
GetOpaqueRegion(nsDisplayListBuilder * aBuilder,bool * aSnap) const1557 /* virtual */ nsRegion nsDisplayImage::GetOpaqueRegion(
1558     nsDisplayListBuilder* aBuilder, bool* aSnap) const {
1559   *aSnap = false;
1560   if (mImage && mImage->WillDrawOpaqueNow()) {
1561     const nsRect frameContentBox = GetBounds(aSnap);
1562     return GetDestRect().Intersect(frameContentBox);
1563   }
1564   return nsRegion();
1565 }
1566 
BuildLayer(nsDisplayListBuilder * aBuilder,LayerManager * aManager,const ContainerLayerParameters & aParameters)1567 already_AddRefed<Layer> nsDisplayImage::BuildLayer(
1568     nsDisplayListBuilder* aBuilder, LayerManager* aManager,
1569     const ContainerLayerParameters& aParameters) {
1570   uint32_t flags = imgIContainer::FLAG_ASYNC_NOTIFY;
1571   if (aBuilder->ShouldSyncDecodeImages()) {
1572     flags |= imgIContainer::FLAG_SYNC_DECODE;
1573   }
1574 
1575   RefPtr<ImageContainer> container = mImage->GetImageContainer(aManager, flags);
1576   if (!container || !container->HasCurrentImage()) {
1577     return nullptr;
1578   }
1579 
1580   RefPtr<ImageLayer> layer = static_cast<ImageLayer*>(
1581       aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this));
1582   if (!layer) {
1583     layer = aManager->CreateImageLayer();
1584     if (!layer) return nullptr;
1585   }
1586   layer->SetContainer(container);
1587   ConfigureLayer(layer, aParameters);
1588   return layer.forget();
1589 }
1590 
CreateWebRenderCommands(mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const StackingContextHelper & aSc,WebRenderLayerManager * aManager,nsDisplayListBuilder * aDisplayListBuilder)1591 bool nsDisplayImage::CreateWebRenderCommands(
1592     mozilla::wr::DisplayListBuilder& aBuilder,
1593     mozilla::wr::IpcResourceUpdateQueue& aResources,
1594     const StackingContextHelper& aSc, WebRenderLayerManager* aManager,
1595     nsDisplayListBuilder* aDisplayListBuilder) {
1596   if (!mImage) {
1597     return false;
1598   }
1599 
1600   if (mFrame->IsImageFrame()) {
1601     // Image layer doesn't support draw focus ring for image map.
1602     nsImageFrame* f = static_cast<nsImageFrame*>(mFrame);
1603     if (f->HasImageMap()) {
1604       return false;
1605     }
1606   }
1607 
1608   uint32_t flags = imgIContainer::FLAG_ASYNC_NOTIFY;
1609   if (aDisplayListBuilder->IsPaintingToWindow()) {
1610     flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
1611   }
1612   if (aDisplayListBuilder->ShouldSyncDecodeImages()) {
1613     flags |= imgIContainer::FLAG_SYNC_DECODE;
1614   }
1615 
1616   const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
1617   const LayoutDeviceRect destRect(
1618       LayoutDeviceRect::FromAppUnits(GetDestRect(), factor));
1619   Maybe<SVGImageContext> svgContext;
1620   IntSize decodeSize = nsLayoutUtils::ComputeImageContainerDrawingParameters(
1621       mImage, mFrame, destRect, aSc, flags, svgContext);
1622   RefPtr<ImageContainer> container =
1623       mImage->GetImageContainerAtSize(aManager, decodeSize, svgContext, flags);
1624   if (!container) {
1625     return false;
1626   }
1627 
1628   return aManager->CommandBuilder().PushImage(this, container, aBuilder,
1629                                               aResources, aSc, destRect);
1630 }
1631 
PaintImage(gfxContext & aRenderingContext,nsPoint aPt,const nsRect & aDirtyRect,imgIContainer * aImage,uint32_t aFlags)1632 ImgDrawResult nsImageFrame::PaintImage(gfxContext& aRenderingContext,
1633                                        nsPoint aPt, const nsRect& aDirtyRect,
1634                                        imgIContainer* aImage, uint32_t aFlags) {
1635   DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
1636 
1637   // Render the image into our content area (the area inside
1638   // the borders and padding)
1639   NS_ASSERTION(GetInnerArea().width == mComputedSize.width, "bad width");
1640 
1641   // NOTE: We use mComputedSize instead of just GetInnerArea()'s own size here,
1642   // because GetInnerArea() might be smaller if we're fragmented, whereas
1643   // mComputedSize has our full content-box size (which we need for
1644   // ComputeObjectDestRect to work correctly).
1645   nsRect constraintRect(aPt + GetInnerArea().TopLeft(), mComputedSize);
1646   constraintRect.y -= GetContinuationOffset();
1647 
1648   nsPoint anchorPoint;
1649   nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
1650       constraintRect, mIntrinsicSize, mIntrinsicRatio, StylePosition(),
1651       &anchorPoint);
1652 
1653   uint32_t flags = aFlags;
1654   if (mForceSyncDecoding) {
1655     flags |= imgIContainer::FLAG_SYNC_DECODE;
1656   }
1657 
1658   Maybe<SVGImageContext> svgContext;
1659   SVGImageContext::MaybeStoreContextPaint(svgContext, this, aImage);
1660 
1661   ImgDrawResult result = nsLayoutUtils::DrawSingleImage(
1662       aRenderingContext, PresContext(), aImage,
1663       nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect,
1664       svgContext, flags, &anchorPoint);
1665 
1666   if (nsImageMap* map = GetImageMap()) {
1667     gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
1668         dest.TopLeft(), PresContext()->AppUnitsPerDevPixel());
1669     AutoRestoreTransform autoRestoreTransform(drawTarget);
1670     drawTarget->SetTransform(
1671         drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
1672 
1673     // solid white stroke:
1674     ColorPattern white(ToDeviceColor(Color(1.f, 1.f, 1.f, 1.f)));
1675     map->Draw(this, *drawTarget, white);
1676 
1677     // then dashed black stroke over the top:
1678     ColorPattern black(ToDeviceColor(Color(0.f, 0.f, 0.f, 1.f)));
1679     StrokeOptions strokeOptions;
1680     nsLayoutUtils::InitDashPattern(strokeOptions, NS_STYLE_BORDER_STYLE_DOTTED);
1681     map->Draw(this, *drawTarget, black, strokeOptions);
1682   }
1683 
1684   if (result == ImgDrawResult::SUCCESS) {
1685     mPrevImage = aImage;
1686   } else if (result == ImgDrawResult::BAD_IMAGE) {
1687     mPrevImage = nullptr;
1688   }
1689 
1690   return result;
1691 }
1692 
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)1693 void nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1694                                     const nsDisplayListSet& aLists) {
1695   if (!IsVisibleForPainting(aBuilder)) return;
1696 
1697   DisplayBorderBackgroundOutline(aBuilder, aLists);
1698 
1699   uint32_t clipFlags =
1700       nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition())
1701           ? 0
1702           : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
1703 
1704   DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox clip(
1705       aBuilder, this, clipFlags);
1706 
1707   if (mComputedSize.width != 0 && mComputedSize.height != 0) {
1708     nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
1709     NS_ASSERTION(imageLoader, "Not an image loading content?");
1710 
1711     nsCOMPtr<imgIRequest> currentRequest;
1712     if (imageLoader) {
1713       imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
1714                               getter_AddRefs(currentRequest));
1715     }
1716 
1717     EventStates contentState = mContent->AsElement()->State();
1718     bool imageOK = IMAGE_OK(contentState, true);
1719 
1720     // XXX(seth): The SizeIsAvailable check here should not be necessary - the
1721     // intention is that a non-null mImage means we have a size, but there is
1722     // currently some code that violates this invariant.
1723     if (!imageOK || !mImage || !SizeIsAvailable(currentRequest)) {
1724       // No image yet, or image load failed. Draw the alt-text and an icon
1725       // indicating the status
1726       aLists.Content()->AppendToTop(
1727           MakeDisplayItem<nsDisplayAltFeedback>(aBuilder, this));
1728 
1729       // This image is visible (we are being asked to paint it) but it's not
1730       // decoded yet. And we are not going to ask the image to draw, so this
1731       // may be the only chance to tell it that it should decode.
1732       if (currentRequest) {
1733         uint32_t status = 0;
1734         currentRequest->GetImageStatus(&status);
1735         if (!(status & imgIRequest::STATUS_DECODE_COMPLETE)) {
1736           MaybeDecodeForPredictedSize();
1737         }
1738         // Increase loading priority if the image is ready to be displayed.
1739         if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
1740           currentRequest->BoostPriority(imgIRequest::CATEGORY_DISPLAY);
1741         }
1742       }
1743     } else {
1744       aLists.Content()->AppendToTop(
1745           MakeDisplayItem<nsDisplayImage>(aBuilder, this, mImage, mPrevImage));
1746 
1747       // If we were previously displaying an icon, we're not anymore
1748       if (mDisplayingIcon) {
1749         gIconLoad->RemoveIconObserver(this);
1750         mDisplayingIcon = false;
1751       }
1752 
1753 #ifdef DEBUG
1754       if (GetShowFrameBorders() && GetImageMap()) {
1755         aLists.Outlines()->AppendToTop(MakeDisplayItem<nsDisplayGeneric>(
1756             aBuilder, this, PaintDebugImageMap, "DebugImageMap",
1757             DisplayItemType::TYPE_DEBUG_IMAGE_MAP));
1758       }
1759 #endif
1760     }
1761   }
1762 
1763   if (ShouldDisplaySelection()) {
1764     DisplaySelectionOverlay(aBuilder, aLists.Content(),
1765                             nsISelectionDisplay::DISPLAY_IMAGES);
1766   }
1767 }
1768 
ShouldDisplaySelection()1769 bool nsImageFrame::ShouldDisplaySelection() {
1770   // XXX what on EARTH is this code for?
1771   nsresult result;
1772   nsPresContext* presContext = PresContext();
1773   int16_t displaySelection = presContext->PresShell()->GetSelectionFlags();
1774   if (!(displaySelection & nsISelectionDisplay::DISPLAY_IMAGES))
1775     return false;  // no need to check the blue border, we cannot be drawn
1776                    // selected
1777 // insert hook here for image selection drawing
1778 #if IMAGE_EDITOR_CHECK
1779   // check to see if this frame is in an editor context
1780   // isEditor check. this needs to be changed to have better way to check
1781   if (displaySelection == nsISelectionDisplay::DISPLAY_ALL) {
1782     nsCOMPtr<nsISelectionController> selCon;
1783     result = GetSelectionController(presContext, getter_AddRefs(selCon));
1784     if (NS_SUCCEEDED(result) && selCon) {
1785       nsCOMPtr<nsISelection> selection;
1786       result = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
1787                                     getter_AddRefs(selection));
1788       if (NS_SUCCEEDED(result) && selection) {
1789         int32_t rangeCount;
1790         selection->GetRangeCount(&rangeCount);
1791         if (rangeCount == 1)  // if not one then let code drop to nsFrame::Paint
1792         {
1793           nsCOMPtr<nsIContent> parentContent = mContent->GetParent();
1794           if (parentContent) {
1795             int32_t thisOffset = parentContent->ComputeIndexOf(mContent);
1796             nsCOMPtr<nsIDOMNode> parentNode = do_QueryInterface(parentContent);
1797             nsCOMPtr<nsIDOMNode> rangeNode;
1798             uint32_t rangeOffset;
1799             nsCOMPtr<nsIDOMRange> range;
1800             selection->GetRangeAt(0, getter_AddRefs(range));
1801             if (range) {
1802               range->GetStartContainer(getter_AddRefs(rangeNode));
1803               range->GetStartOffset(&rangeOffset);
1804 
1805               if (parentNode && rangeNode && rangeNode == parentNode &&
1806                   static_cast<int32_t>(rangeOffset) == thisOffset) {
1807                 range->GetEndContainer(getter_AddRefs(rangeNode));
1808                 range->GetEndOffset(&rangeOffset);
1809                 // +1 since that would mean this whole content is selected only
1810                 if (rangeNode == parentNode &&
1811                     static_cast<int32_t>(rangeOffset) == thisOffset + 1) {
1812                   // Do not allow nsFrame do draw any further selection
1813                   return false;
1814                 }
1815               }
1816             }
1817           }
1818         }
1819       }
1820     }
1821   }
1822 #endif
1823   return true;
1824 }
1825 
GetImageMap()1826 nsImageMap* nsImageFrame::GetImageMap() {
1827   if (!mImageMap) {
1828     if (nsIContent* map = GetMapElement()) {
1829       mImageMap = new nsImageMap();
1830       mImageMap->Init(this, map);
1831     }
1832   }
1833 
1834   return mImageMap;
1835 }
1836 
IsServerImageMap()1837 bool nsImageFrame::IsServerImageMap() {
1838   return mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::ismap);
1839 }
1840 
1841 // Translate an point that is relative to our frame
1842 // into a localized pixel coordinate that is relative to the
1843 // content area of this frame (inside the border+padding).
TranslateEventCoords(const nsPoint & aPoint,nsIntPoint & aResult)1844 void nsImageFrame::TranslateEventCoords(const nsPoint& aPoint,
1845                                         nsIntPoint& aResult) {
1846   nscoord x = aPoint.x;
1847   nscoord y = aPoint.y;
1848 
1849   // Subtract out border and padding here so that the coordinates are
1850   // now relative to the content area of this frame.
1851   nsRect inner = GetInnerArea();
1852   x -= inner.x;
1853   y -= inner.y;
1854 
1855   aResult.x = nsPresContext::AppUnitsToIntCSSPixels(x);
1856   aResult.y = nsPresContext::AppUnitsToIntCSSPixels(y);
1857 }
1858 
GetAnchorHREFTargetAndNode(nsIURI ** aHref,nsString & aTarget,nsIContent ** aNode)1859 bool nsImageFrame::GetAnchorHREFTargetAndNode(nsIURI** aHref, nsString& aTarget,
1860                                               nsIContent** aNode) {
1861   bool status = false;
1862   aTarget.Truncate();
1863   *aHref = nullptr;
1864   *aNode = nullptr;
1865 
1866   // Walk up the content tree, looking for an nsIDOMAnchorElement
1867   for (nsIContent* content = mContent->GetParent(); content;
1868        content = content->GetParent()) {
1869     nsCOMPtr<dom::Link> link(do_QueryInterface(content));
1870     if (link) {
1871       nsCOMPtr<nsIURI> href = content->GetHrefURI();
1872       if (href) {
1873         href->Clone(aHref);
1874       }
1875       status = (*aHref != nullptr);
1876 
1877       RefPtr<HTMLAnchorElement> anchor =
1878           HTMLAnchorElement::FromContent(content);
1879       if (anchor) {
1880         anchor->GetTarget(aTarget);
1881       }
1882       NS_ADDREF(*aNode = content);
1883       break;
1884     }
1885   }
1886   return status;
1887 }
1888 
GetContentForEvent(WidgetEvent * aEvent,nsIContent ** aContent)1889 nsresult nsImageFrame::GetContentForEvent(WidgetEvent* aEvent,
1890                                           nsIContent** aContent) {
1891   NS_ENSURE_ARG_POINTER(aContent);
1892 
1893   nsIFrame* f = nsLayoutUtils::GetNonGeneratedAncestor(this);
1894   if (f != this) {
1895     return f->GetContentForEvent(aEvent, aContent);
1896   }
1897 
1898   // XXX We need to make this special check for area element's capturing the
1899   // mouse due to bug 135040. Remove it once that's fixed.
1900   nsIContent* capturingContent = aEvent->HasMouseEventMessage()
1901                                      ? nsIPresShell::GetCapturingContent()
1902                                      : nullptr;
1903   if (capturingContent && capturingContent->GetPrimaryFrame() == this) {
1904     *aContent = capturingContent;
1905     NS_IF_ADDREF(*aContent);
1906     return NS_OK;
1907   }
1908 
1909   if (nsImageMap* map = GetImageMap()) {
1910     nsIntPoint p;
1911     TranslateEventCoords(
1912         nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this), p);
1913     nsCOMPtr<nsIContent> area = map->GetArea(p.x, p.y);
1914     if (area) {
1915       area.forget(aContent);
1916       return NS_OK;
1917     }
1918   }
1919 
1920   *aContent = GetContent();
1921   NS_IF_ADDREF(*aContent);
1922   return NS_OK;
1923 }
1924 
1925 // XXX what should clicks on transparent pixels do?
HandleEvent(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)1926 nsresult nsImageFrame::HandleEvent(nsPresContext* aPresContext,
1927                                    WidgetGUIEvent* aEvent,
1928                                    nsEventStatus* aEventStatus) {
1929   NS_ENSURE_ARG_POINTER(aEventStatus);
1930 
1931   if ((aEvent->mMessage == eMouseClick &&
1932        aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) ||
1933       aEvent->mMessage == eMouseMove) {
1934     nsImageMap* map = GetImageMap();
1935     bool isServerMap = IsServerImageMap();
1936     if (map || isServerMap) {
1937       nsIntPoint p;
1938       TranslateEventCoords(
1939           nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this), p);
1940       bool inside = false;
1941       // Even though client-side image map triggering happens
1942       // through content, we need to make sure we're not inside
1943       // (in case we deal with a case of both client-side and
1944       // sever-side on the same image - it happens!)
1945       if (nullptr != map) {
1946         inside = !!map->GetArea(p.x, p.y);
1947       }
1948 
1949       if (!inside && isServerMap) {
1950         // Server side image maps use the href in a containing anchor
1951         // element to provide the basis for the destination url.
1952         nsCOMPtr<nsIURI> uri;
1953         nsAutoString target;
1954         nsCOMPtr<nsIContent> anchorNode;
1955         if (GetAnchorHREFTargetAndNode(getter_AddRefs(uri), target,
1956                                        getter_AddRefs(anchorNode))) {
1957           // XXX if the mouse is over/clicked in the border/padding area
1958           // we should probably just pretend nothing happened. Nav4
1959           // keeps the x,y coordinates positive as we do; IE doesn't
1960           // bother. Both of them send the click through even when the
1961           // mouse is over the border.
1962           if (p.x < 0) p.x = 0;
1963           if (p.y < 0) p.y = 0;
1964 
1965           nsAutoCString spec;
1966           nsresult rv = uri->GetSpec(spec);
1967           NS_ENSURE_SUCCESS(rv, rv);
1968 
1969           spec += nsPrintfCString("?%d,%d", p.x, p.y);
1970           rv = NS_MutateURI(uri).SetSpec(spec).Finalize(uri);
1971           NS_ENSURE_SUCCESS(rv, rv);
1972 
1973           bool clicked = false;
1974           if (aEvent->mMessage == eMouseClick && !aEvent->DefaultPrevented()) {
1975             *aEventStatus = nsEventStatus_eConsumeDoDefault;
1976             clicked = true;
1977           }
1978           nsContentUtils::TriggerLink(anchorNode, aPresContext, uri, target,
1979                                       clicked, true, true);
1980         }
1981       }
1982     }
1983   }
1984 
1985   return nsAtomicContainerFrame::HandleEvent(aPresContext, aEvent,
1986                                              aEventStatus);
1987 }
1988 
GetCursor(const nsPoint & aPoint,nsIFrame::Cursor & aCursor)1989 nsresult nsImageFrame::GetCursor(const nsPoint& aPoint,
1990                                  nsIFrame::Cursor& aCursor) {
1991   if (nsImageMap* map = GetImageMap()) {
1992     nsIntPoint p;
1993     TranslateEventCoords(aPoint, p);
1994     nsCOMPtr<nsIContent> area = map->GetArea(p.x, p.y);
1995     if (area) {
1996       // Use the cursor from the style of the *area* element.
1997       // XXX Using the image as the parent style context isn't
1998       // technically correct, but it's probably the right thing to do
1999       // here, since it means that areas on which the cursor isn't
2000       // specified will inherit the style from the image.
2001       RefPtr<nsStyleContext> areaStyle =
2002           PresShell()->StyleSet()->ResolveStyleFor(
2003               area->AsElement(), StyleContext(), LazyComputeBehavior::Allow);
2004       FillCursorInformationFromStyle(areaStyle->StyleUserInterface(), aCursor);
2005       if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) {
2006         aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT;
2007       }
2008       return NS_OK;
2009     }
2010   }
2011   return nsFrame::GetCursor(aPoint, aCursor);
2012 }
2013 
AttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType)2014 nsresult nsImageFrame::AttributeChanged(int32_t aNameSpaceID,
2015                                         nsAtom* aAttribute, int32_t aModType) {
2016   nsresult rv = nsAtomicContainerFrame::AttributeChanged(aNameSpaceID,
2017                                                          aAttribute, aModType);
2018   if (NS_FAILED(rv)) {
2019     return rv;
2020   }
2021   if (nsGkAtoms::alt == aAttribute) {
2022     PresShell()->FrameNeedsReflow(this, nsIPresShell::eStyleChange,
2023                                   NS_FRAME_IS_DIRTY);
2024   }
2025 
2026   return NS_OK;
2027 }
2028 
OnVisibilityChange(Visibility aNewVisibility,const Maybe<OnNonvisible> & aNonvisibleAction)2029 void nsImageFrame::OnVisibilityChange(
2030     Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
2031   nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
2032   if (!imageLoader) {
2033     MOZ_ASSERT_UNREACHABLE("Should have an nsIImageLoadingContent");
2034     nsAtomicContainerFrame::OnVisibilityChange(aNewVisibility,
2035                                                aNonvisibleAction);
2036     return;
2037   }
2038 
2039   imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
2040 
2041   if (aNewVisibility == Visibility::APPROXIMATELY_VISIBLE) {
2042     MaybeDecodeForPredictedSize();
2043   }
2044 
2045   nsAtomicContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
2046 }
2047 
2048 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const2049 nsresult nsImageFrame::GetFrameName(nsAString& aResult) const {
2050   return MakeFrameName(NS_LITERAL_STRING("ImageFrame"), aResult);
2051 }
2052 
List(FILE * out,const char * aPrefix,uint32_t aFlags) const2053 void nsImageFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const {
2054   nsCString str;
2055   ListGeneric(str, aPrefix, aFlags);
2056 
2057   // output the img src url
2058   nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
2059   if (imageLoader) {
2060     nsCOMPtr<imgIRequest> currentRequest;
2061     imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
2062                             getter_AddRefs(currentRequest));
2063     if (currentRequest) {
2064       nsCOMPtr<nsIURI> uri;
2065       currentRequest->GetURI(getter_AddRefs(uri));
2066       nsAutoCString uristr;
2067       uri->GetAsciiSpec(uristr);
2068       str += nsPrintfCString(" [src=%s]", uristr.get());
2069     }
2070   }
2071   fprintf_stderr(out, "%s\n", str.get());
2072 }
2073 #endif
2074 
GetLogicalSkipSides(const ReflowInput * aReflowInput) const2075 nsIFrame::LogicalSides nsImageFrame::GetLogicalSkipSides(
2076     const ReflowInput* aReflowInput) const {
2077   if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
2078                    StyleBoxDecorationBreak::Clone)) {
2079     return LogicalSides();
2080   }
2081   LogicalSides skip;
2082   if (nullptr != GetPrevInFlow()) {
2083     skip |= eLogicalSideBitsBStart;
2084   }
2085   if (nullptr != GetNextInFlow()) {
2086     skip |= eLogicalSideBitsBEnd;
2087   }
2088   return skip;
2089 }
2090 
GetIntrinsicImageSize(nsSize & aSize)2091 nsresult nsImageFrame::GetIntrinsicImageSize(nsSize& aSize) {
2092   if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord &&
2093       mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord) {
2094     aSize.SizeTo(mIntrinsicSize.width.GetCoordValue(),
2095                  mIntrinsicSize.height.GetCoordValue());
2096     return NS_OK;
2097   }
2098 
2099   return NS_ERROR_FAILURE;
2100 }
2101 
LoadIcon(const nsAString & aSpec,nsPresContext * aPresContext,imgRequestProxy ** aRequest)2102 nsresult nsImageFrame::LoadIcon(const nsAString& aSpec,
2103                                 nsPresContext* aPresContext,
2104                                 imgRequestProxy** aRequest) {
2105   nsresult rv = NS_OK;
2106   NS_PRECONDITION(!aSpec.IsEmpty(), "What happened??");
2107 
2108   if (!sIOService) {
2109     rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService);
2110     NS_ENSURE_SUCCESS(rv, rv);
2111   }
2112 
2113   nsCOMPtr<nsIURI> realURI;
2114   SpecToURI(aSpec, sIOService, getter_AddRefs(realURI));
2115 
2116   RefPtr<imgLoader> il =
2117       nsContentUtils::GetImgLoaderForDocument(aPresContext->Document());
2118 
2119   nsCOMPtr<nsILoadGroup> loadGroup;
2120   GetLoadGroup(aPresContext, getter_AddRefs(loadGroup));
2121 
2122   // For icon loads, we don't need to merge with the loadgroup flags
2123   nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
2124   nsContentPolicyType contentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE;
2125 
2126   return il->LoadImage(
2127       realURI,                          /* icon URI */
2128       nullptr,                          /* initial document URI; this is only
2129                                           relevant for cookies, so does not
2130                                           apply to icons. */
2131       nullptr,                          /* referrer (not relevant for icons) */
2132       mozilla::net::RP_Unset, nullptr,  /* principal (not relevant for icons) */
2133       0, loadGroup, gIconLoad, nullptr, /* No context */
2134       nullptr, /* Not associated with any particular document */
2135       loadFlags, nullptr, contentPolicyType, EmptyString(),
2136       false, /* aUseUrgentStartForChannel */
2137       aRequest);
2138 }
2139 
GetDocumentCharacterSet(nsACString & aCharset) const2140 void nsImageFrame::GetDocumentCharacterSet(nsACString& aCharset) const {
2141   if (mContent) {
2142     NS_ASSERTION(mContent->GetComposedDoc(),
2143                  "Frame still alive after content removed from document!");
2144     mContent->GetComposedDoc()->GetDocumentCharacterSet()->Name(aCharset);
2145   }
2146 }
2147 
SpecToURI(const nsAString & aSpec,nsIIOService * aIOService,nsIURI ** aURI)2148 void nsImageFrame::SpecToURI(const nsAString& aSpec, nsIIOService* aIOService,
2149                              nsIURI** aURI) {
2150   nsCOMPtr<nsIURI> baseURI;
2151   if (mContent) {
2152     baseURI = mContent->GetBaseURI();
2153   }
2154   nsAutoCString charset;
2155   GetDocumentCharacterSet(charset);
2156   NS_NewURI(aURI, aSpec, charset.IsEmpty() ? nullptr : charset.get(), baseURI,
2157             aIOService);
2158 }
2159 
GetLoadGroup(nsPresContext * aPresContext,nsILoadGroup ** aLoadGroup)2160 void nsImageFrame::GetLoadGroup(nsPresContext* aPresContext,
2161                                 nsILoadGroup** aLoadGroup) {
2162   if (!aPresContext) return;
2163 
2164   NS_PRECONDITION(nullptr != aLoadGroup, "null OUT parameter pointer");
2165 
2166   nsIPresShell* shell = aPresContext->GetPresShell();
2167 
2168   if (!shell) return;
2169 
2170   nsIDocument* doc = shell->GetDocument();
2171   if (!doc) return;
2172 
2173   *aLoadGroup = doc->GetDocumentLoadGroup().take();
2174 }
2175 
LoadIcons(nsPresContext * aPresContext)2176 nsresult nsImageFrame::LoadIcons(nsPresContext* aPresContext) {
2177   NS_ASSERTION(!gIconLoad, "called LoadIcons twice");
2178 
2179   NS_NAMED_LITERAL_STRING(loadingSrc,
2180                           "resource://gre-resources/loading-image.png");
2181   NS_NAMED_LITERAL_STRING(brokenSrc,
2182                           "resource://gre-resources/broken-image.png");
2183 
2184   gIconLoad = new IconLoad();
2185 
2186   nsresult rv;
2187   // create a loader and load the images
2188   rv = LoadIcon(loadingSrc, aPresContext,
2189                 getter_AddRefs(gIconLoad->mLoadingImage));
2190   if (NS_FAILED(rv)) {
2191     return rv;
2192   }
2193 
2194   rv = LoadIcon(brokenSrc, aPresContext,
2195                 getter_AddRefs(gIconLoad->mBrokenImage));
2196   if (NS_FAILED(rv)) {
2197     return rv;
2198   }
2199 
2200   return rv;
2201 }
2202 
2203 NS_IMPL_ISUPPORTS(nsImageFrame::IconLoad, nsIObserver, imgINotificationObserver)
2204 
2205 static const char* kIconLoadPrefs[] = {
2206     "browser.display.force_inline_alttext",
2207     "browser.display.show_image_placeholders",
2208     "browser.display.show_loading_image_placeholder", nullptr};
2209 
IconLoad()2210 nsImageFrame::IconLoad::IconLoad() {
2211   // register observers
2212   Preferences::AddStrongObservers(this, kIconLoadPrefs);
2213   GetPrefs();
2214 }
2215 
Shutdown()2216 void nsImageFrame::IconLoad::Shutdown() {
2217   Preferences::RemoveObservers(this, kIconLoadPrefs);
2218   // in case the pref service releases us later
2219   if (mLoadingImage) {
2220     mLoadingImage->CancelAndForgetObserver(NS_ERROR_FAILURE);
2221     mLoadingImage = nullptr;
2222   }
2223   if (mBrokenImage) {
2224     mBrokenImage->CancelAndForgetObserver(NS_ERROR_FAILURE);
2225     mBrokenImage = nullptr;
2226   }
2227 }
2228 
2229 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)2230 nsImageFrame::IconLoad::Observe(nsISupports* aSubject, const char* aTopic,
2231                                 const char16_t* aData) {
2232   NS_ASSERTION(!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID),
2233                "wrong topic");
2234 #ifdef DEBUG
2235   // assert |aData| is one of our prefs.
2236   uint32_t i = 0;
2237   for (; i < ArrayLength(kIconLoadPrefs); ++i) {
2238     if (NS_ConvertASCIItoUTF16(kIconLoadPrefs[i]) == nsDependentString(aData))
2239       break;
2240   }
2241   MOZ_ASSERT(i < ArrayLength(kIconLoadPrefs));
2242 #endif
2243 
2244   GetPrefs();
2245   return NS_OK;
2246 }
2247 
GetPrefs()2248 void nsImageFrame::IconLoad::GetPrefs() {
2249   mPrefForceInlineAltText =
2250       Preferences::GetBool("browser.display.force_inline_alttext");
2251 
2252   mPrefShowPlaceholders =
2253       Preferences::GetBool("browser.display.show_image_placeholders", true);
2254 
2255   mPrefShowLoadingPlaceholder = Preferences::GetBool(
2256       "browser.display.show_loading_image_placeholder", true);
2257 }
2258 
2259 NS_IMETHODIMP
Notify(imgIRequest * aRequest,int32_t aType,const nsIntRect * aData)2260 nsImageFrame::IconLoad::Notify(imgIRequest* aRequest, int32_t aType,
2261                                const nsIntRect* aData) {
2262   MOZ_ASSERT(aRequest);
2263 
2264   if (aType != imgINotificationObserver::LOAD_COMPLETE &&
2265       aType != imgINotificationObserver::FRAME_UPDATE) {
2266     return NS_OK;
2267   }
2268 
2269   if (aType == imgINotificationObserver::LOAD_COMPLETE) {
2270     nsCOMPtr<imgIContainer> image;
2271     aRequest->GetImage(getter_AddRefs(image));
2272     if (!image) {
2273       return NS_ERROR_FAILURE;
2274     }
2275 
2276     // Retrieve the image's intrinsic size.
2277     int32_t width = 0;
2278     int32_t height = 0;
2279     image->GetWidth(&width);
2280     image->GetHeight(&height);
2281 
2282     // Request a decode at that size.
2283     image->RequestDecodeForSize(IntSize(width, height),
2284                                 imgIContainer::DECODE_FLAGS_DEFAULT);
2285   }
2286 
2287   nsTObserverArray<nsImageFrame*>::ForwardIterator iter(mIconObservers);
2288   nsImageFrame* frame;
2289   while (iter.HasMore()) {
2290     frame = iter.GetNext();
2291     frame->InvalidateFrame();
2292   }
2293 
2294   return NS_OK;
2295 }
2296 
NS_IMPL_ISUPPORTS(nsImageListener,imgINotificationObserver)2297 NS_IMPL_ISUPPORTS(nsImageListener, imgINotificationObserver)
2298 
2299 nsImageListener::nsImageListener(nsImageFrame* aFrame) : mFrame(aFrame) {}
2300 
~nsImageListener()2301 nsImageListener::~nsImageListener() {}
2302 
2303 NS_IMETHODIMP
Notify(imgIRequest * aRequest,int32_t aType,const nsIntRect * aData)2304 nsImageListener::Notify(imgIRequest* aRequest, int32_t aType,
2305                         const nsIntRect* aData) {
2306   if (!mFrame) return NS_ERROR_FAILURE;
2307 
2308   return mFrame->Notify(aRequest, aType, aData);
2309 }
2310 
IsInAutoWidthTableCellForQuirk(nsIFrame * aFrame)2311 static bool IsInAutoWidthTableCellForQuirk(nsIFrame* aFrame) {
2312   if (eCompatibility_NavQuirks != aFrame->PresContext()->CompatibilityMode())
2313     return false;
2314   // Check if the parent of the closest nsBlockFrame has auto width.
2315   nsBlockFrame* ancestor = nsLayoutUtils::FindNearestBlockAncestor(aFrame);
2316   if (ancestor->StyleContext()->GetPseudo() == nsCSSAnonBoxes::cellContent) {
2317     // Assume direct parent is a table cell frame.
2318     nsFrame* grandAncestor = static_cast<nsFrame*>(ancestor->GetParent());
2319     return grandAncestor &&
2320            grandAncestor->StylePosition()->mWidth.GetUnit() == eStyleUnit_Auto;
2321   }
2322   return false;
2323 }
2324 
AddInlineMinISize(gfxContext * aRenderingContext,nsIFrame::InlineMinISizeData * aData)2325 /* virtual */ void nsImageFrame::AddInlineMinISize(
2326     gfxContext* aRenderingContext, nsIFrame::InlineMinISizeData* aData) {
2327   nscoord isize = nsLayoutUtils::IntrinsicForContainer(
2328       aRenderingContext, this, nsLayoutUtils::MIN_ISIZE);
2329   bool canBreak = !IsInAutoWidthTableCellForQuirk(this);
2330   aData->DefaultAddInlineMinISize(this, isize, canBreak);
2331 }
2332