Bumping manifests a=b2g-bump
[gecko.git] / layout / generic / nsImageFrame.cpp
blob75b587c88a69898b248a7cdf3b07db167bf81cd2
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /* rendering object for replaced elements with image data */
8 #include "nsImageFrame.h"
10 #include "gfx2DGlue.h"
11 #include "gfxUtils.h"
12 #include "mozilla/DebugOnly.h"
13 #include "mozilla/EventStates.h"
14 #include "mozilla/gfx/2D.h"
15 #include "mozilla/gfx/Helpers.h"
16 #include "mozilla/gfx/PathHelpers.h"
17 #include "mozilla/MouseEvents.h"
19 #include "nsCOMPtr.h"
20 #include "nsFontMetrics.h"
21 #include "nsIImageLoadingContent.h"
22 #include "nsString.h"
23 #include "nsPrintfCString.h"
24 #include "nsPresContext.h"
25 #include "nsRenderingContext.h"
26 #include "nsIPresShell.h"
27 #include "nsGkAtoms.h"
28 #include "nsIDocument.h"
29 #include "nsContentUtils.h"
30 #include "nsCSSAnonBoxes.h"
31 #include "nsStyleContext.h"
32 #include "nsStyleConsts.h"
33 #include "nsStyleCoord.h"
34 #include "nsStyleUtil.h"
35 #include "nsTransform2D.h"
36 #include "nsImageMap.h"
37 #include "nsIIOService.h"
38 #include "nsILoadGroup.h"
39 #include "nsISupportsPriority.h"
40 #include "nsNetUtil.h"
41 #include "nsCSSRendering.h"
42 #include "nsIDOMHTMLAnchorElement.h"
43 #include "nsNameSpaceManager.h"
44 #include <algorithm>
45 #ifdef ACCESSIBILITY
46 #include "nsAccessibilityService.h"
47 #endif
48 #include "nsIDOMNode.h"
49 #include "nsLayoutUtils.h"
50 #include "nsDisplayList.h"
51 #include "nsIContent.h"
52 #include "nsIDocument.h"
53 #include "FrameLayerBuilder.h"
54 #include "nsISelectionController.h"
55 #include "nsISelection.h"
57 #include "imgIContainer.h"
58 #include "imgLoader.h"
59 #include "imgRequestProxy.h"
61 #include "nsCSSFrameConstructor.h"
62 #include "nsIDOMRange.h"
64 #include "nsError.h"
65 #include "nsBidiUtils.h"
66 #include "nsBidiPresUtils.h"
68 #include "gfxRect.h"
69 #include "ImageLayers.h"
70 #include "ImageContainer.h"
71 #include "nsStyleSet.h"
72 #include "nsBlockFrame.h"
73 #include "nsStyleStructInlines.h"
75 #include "mozilla/Preferences.h"
77 #include "mozilla/dom/Link.h"
79 using namespace mozilla;
80 using namespace mozilla::dom;
81 using namespace mozilla::gfx;
82 using namespace mozilla::image;
83 using namespace mozilla::layers;
85 // sizes (pixels) for image icon, padding and border frame
86 #define ICON_SIZE (16)
87 #define ICON_PADDING (3)
88 #define ALT_BORDER_WIDTH (1)
90 //we must add hooks soon
91 #define IMAGE_EDITOR_CHECK 1
93 // Default alignment value (so we can tell an unset value from a set value)
94 #define ALIGN_UNSET uint8_t(-1)
96 // static icon information
97 nsImageFrame::IconLoad* nsImageFrame::gIconLoad = nullptr;
99 // cached IO service for loading icons
100 nsIIOService* nsImageFrame::sIOService;
102 // test if the width and height are fixed, looking at the style data
103 static bool HaveFixedSize(const nsStylePosition* aStylePosition)
105 // check the width and height values in the reflow state's style struct
106 // - if width and height are specified as either coord or percentage, then
107 // the size of the image frame is constrained
108 return aStylePosition->mWidth.IsCoordPercentCalcUnit() &&
109 aStylePosition->mHeight.IsCoordPercentCalcUnit();
112 // use the data in the reflow state to decide if the image has a constrained size
113 // (i.e. width and height that are based on the containing block size and not the image size)
114 // so we can avoid animated GIF related reflows
115 inline bool HaveFixedSize(const nsHTMLReflowState& aReflowState)
117 NS_ASSERTION(aReflowState.mStylePosition, "crappy reflowState - null stylePosition");
118 // when an image has percent css style height or width, but ComputedHeight()
119 // or ComputedWidth() of reflow state is NS_UNCONSTRAINEDSIZE
120 // it needs to return false to cause an incremental reflow later
121 // if an image is inside table like bug 156731 simple testcase III,
122 // during pass 1 reflow, ComputedWidth() is NS_UNCONSTRAINEDSIZE
123 // in pass 2 reflow, ComputedWidth() is 0, it also needs to return false
124 // see bug 156731
125 const nsStyleCoord &height = aReflowState.mStylePosition->mHeight;
126 const nsStyleCoord &width = aReflowState.mStylePosition->mWidth;
127 return ((height.HasPercent() &&
128 NS_UNCONSTRAINEDSIZE == aReflowState.ComputedHeight()) ||
129 (width.HasPercent() &&
130 (NS_UNCONSTRAINEDSIZE == aReflowState.ComputedWidth() ||
131 0 == aReflowState.ComputedWidth())))
132 ? false
133 : HaveFixedSize(aReflowState.mStylePosition);
136 nsIFrame*
137 NS_NewImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
139 return new (aPresShell) nsImageFrame(aContext);
142 NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame)
145 nsImageFrame::nsImageFrame(nsStyleContext* aContext) :
146 ImageFrameSuper(aContext),
147 mComputedSize(0, 0),
148 mIntrinsicRatio(0, 0),
149 mDisplayingIcon(false),
150 mFirstFrameComplete(false),
151 mReflowCallbackPosted(false)
153 // We assume our size is not constrained and we haven't gotten an
154 // initial reflow yet, so don't touch those flags.
155 mIntrinsicSize.width.SetCoordValue(0);
156 mIntrinsicSize.height.SetCoordValue(0);
159 nsImageFrame::~nsImageFrame()
163 NS_QUERYFRAME_HEAD(nsImageFrame)
164 NS_QUERYFRAME_ENTRY(nsImageFrame)
165 NS_QUERYFRAME_TAIL_INHERITING(ImageFrameSuper)
167 #ifdef ACCESSIBILITY
168 a11y::AccType
169 nsImageFrame::AccessibleType()
171 // Don't use GetImageMap() to avoid reentrancy into accessibility.
172 if (HasImageMap()) {
173 return a11y::eHTMLImageMapType;
176 return a11y::eImageType;
178 #endif
180 void
181 nsImageFrame::DisconnectMap()
183 if (mImageMap) {
184 mImageMap->Destroy();
185 NS_RELEASE(mImageMap);
187 #ifdef ACCESSIBILITY
188 nsAccessibilityService* accService = GetAccService();
189 if (accService) {
190 accService->RecreateAccessible(PresContext()->PresShell(), mContent);
192 #endif
196 void
197 nsImageFrame::DestroyFrom(nsIFrame* aDestructRoot)
199 if (mReflowCallbackPosted) {
200 PresContext()->PresShell()->CancelReflowCallback(this);
201 mReflowCallbackPosted = false;
204 // Tell our image map, if there is one, to clean up
205 // This causes the nsImageMap to unregister itself as
206 // a DOM listener.
207 DisconnectMap();
209 // set the frame to null so we don't send messages to a dead object.
210 if (mListener) {
211 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
212 if (imageLoader) {
213 // Notify our image loading content that we are going away so it can
214 // deregister with our refresh driver.
215 imageLoader->FrameDestroyed(this);
217 imageLoader->RemoveObserver(mListener);
220 reinterpret_cast<nsImageListener*>(mListener.get())->SetFrame(nullptr);
223 mListener = nullptr;
225 // If we were displaying an icon, take ourselves off the list
226 if (mDisplayingIcon)
227 gIconLoad->RemoveIconObserver(this);
229 nsSplittableFrame::DestroyFrom(aDestructRoot);
232 void
233 nsImageFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
235 ImageFrameSuper::DidSetStyleContext(aOldStyleContext);
237 if (!mImage) {
238 // We'll pick this change up whenever we do get an image.
239 return;
242 nsStyleImageOrientation newOrientation = StyleVisibility()->mImageOrientation;
244 // We need to update our orientation either if we had no style context before
245 // because this is the first time it's been set, or if the image-orientation
246 // property changed from its previous value.
247 bool shouldUpdateOrientation =
248 !aOldStyleContext ||
249 aOldStyleContext->StyleVisibility()->mImageOrientation != newOrientation;
251 if (shouldUpdateOrientation) {
252 nsCOMPtr<imgIContainer> image(mImage->Unwrap());
253 mImage = nsLayoutUtils::OrientImage(image, newOrientation);
255 UpdateIntrinsicSize(mImage);
256 UpdateIntrinsicRatio(mImage);
260 void
261 nsImageFrame::Init(nsIContent* aContent,
262 nsContainerFrame* aParent,
263 nsIFrame* aPrevInFlow)
265 nsSplittableFrame::Init(aContent, aParent, aPrevInFlow);
267 mListener = new nsImageListener(this);
269 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aContent);
270 if (!imageLoader) {
271 NS_RUNTIMEABORT("Why do we have an nsImageFrame here at all?");
274 imageLoader->AddObserver(mListener);
276 nsPresContext *aPresContext = PresContext();
278 if (!gIconLoad)
279 LoadIcons(aPresContext);
281 // We have a PresContext now, so we need to notify the image content node
282 // that it can register images.
283 imageLoader->FrameCreated(this);
285 // Give image loads associated with an image frame a small priority boost!
286 nsCOMPtr<imgIRequest> currentRequest;
287 imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
288 getter_AddRefs(currentRequest));
289 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(currentRequest);
290 if (p)
291 p->AdjustPriority(-1);
294 bool
295 nsImageFrame::UpdateIntrinsicSize(imgIContainer* aImage)
297 NS_PRECONDITION(aImage, "null image");
298 if (!aImage)
299 return false;
301 IntrinsicSize oldIntrinsicSize = mIntrinsicSize;
302 mIntrinsicSize = IntrinsicSize();
304 // Set intrinsic size to match aImage's reported intrinsic width & height.
305 nsSize intrinsicSize;
306 if (NS_SUCCEEDED(aImage->GetIntrinsicSize(&intrinsicSize))) {
307 // If the image has no intrinsic width, intrinsicSize.width will be -1, and
308 // we can leave mIntrinsicSize.width at its default value of eStyleUnit_None.
309 // Otherwise we use intrinsicSize.width. Height works the same way.
310 if (intrinsicSize.width != -1)
311 mIntrinsicSize.width.SetCoordValue(intrinsicSize.width);
312 if (intrinsicSize.height != -1)
313 mIntrinsicSize.height.SetCoordValue(intrinsicSize.height);
314 } else {
315 // Failure means that the image hasn't loaded enough to report a result. We
316 // treat this case as if the image's intrinsic size was 0x0.
317 mIntrinsicSize.width.SetCoordValue(0);
318 mIntrinsicSize.height.SetCoordValue(0);
321 return mIntrinsicSize != oldIntrinsicSize;
324 bool
325 nsImageFrame::UpdateIntrinsicRatio(imgIContainer* aImage)
327 NS_PRECONDITION(aImage, "null image");
329 if (!aImage)
330 return false;
332 nsSize oldIntrinsicRatio = mIntrinsicRatio;
334 // Set intrinsic ratio to match aImage's reported intrinsic ratio.
335 if (NS_FAILED(aImage->GetIntrinsicRatio(&mIntrinsicRatio)))
336 mIntrinsicRatio.SizeTo(0, 0);
338 return mIntrinsicRatio != oldIntrinsicRatio;
341 bool
342 nsImageFrame::GetSourceToDestTransform(nsTransform2D& aTransform)
344 // First, figure out destRect (the rect we're rendering into).
345 // NOTE: We use mComputedSize instead of just GetInnerArea()'s own size here,
346 // because GetInnerArea() might be smaller if we're fragmented, whereas
347 // mComputedSize has our full content-box size (which we need for
348 // ComputeObjectDestRect to work correctly).
349 nsRect constraintRect(GetInnerArea().TopLeft(), mComputedSize);
350 constraintRect.y -= GetContinuationOffset();
352 nsRect destRect = nsLayoutUtils::ComputeObjectDestRect(constraintRect,
353 mIntrinsicSize,
354 mIntrinsicRatio,
355 StylePosition());
356 // Set the translation components, based on destRect
357 // XXXbz does this introduce rounding errors because of the cast to
358 // float? Should we just manually add that stuff in every time
359 // instead?
360 aTransform.SetToTranslate(float(destRect.x),
361 float(destRect.y));
363 // Set the scale factors, based on destRect and intrinsic size.
364 if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord &&
365 mIntrinsicSize.width.GetCoordValue() != 0 &&
366 mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord &&
367 mIntrinsicSize.height.GetCoordValue() != 0 &&
368 mIntrinsicSize.width.GetCoordValue() != destRect.width &&
369 mIntrinsicSize.height.GetCoordValue() != destRect.height) {
371 aTransform.SetScale(float(destRect.width) /
372 float(mIntrinsicSize.width.GetCoordValue()),
373 float(destRect.height) /
374 float(mIntrinsicSize.height.GetCoordValue()));
375 return true;
378 return false;
381 // This function checks whether the given request is the current request for our
382 // mContent.
383 bool
384 nsImageFrame::IsPendingLoad(imgIRequest* aRequest) const
386 // Default to pending load in case of errors
387 nsCOMPtr<nsIImageLoadingContent> imageLoader(do_QueryInterface(mContent));
388 NS_ASSERTION(imageLoader, "No image loading content?");
390 int32_t requestType = nsIImageLoadingContent::UNKNOWN_REQUEST;
391 imageLoader->GetRequestType(aRequest, &requestType);
393 return requestType != nsIImageLoadingContent::CURRENT_REQUEST;
396 nsRect
397 nsImageFrame::SourceRectToDest(const nsIntRect& aRect)
399 // When scaling the image, row N of the source image may (depending on
400 // the scaling function) be used to draw any row in the destination image
401 // between floor(F * (N-1)) and ceil(F * (N+1)), where F is the
402 // floating-point scaling factor. The same holds true for columns.
403 // So, we start by computing that bound without the floor and ceiling.
405 nsRect r(nsPresContext::CSSPixelsToAppUnits(aRect.x - 1),
406 nsPresContext::CSSPixelsToAppUnits(aRect.y - 1),
407 nsPresContext::CSSPixelsToAppUnits(aRect.width + 2),
408 nsPresContext::CSSPixelsToAppUnits(aRect.height + 2));
410 nsTransform2D sourceToDest;
411 if (!GetSourceToDestTransform(sourceToDest)) {
412 // Failed to generate transform matrix. Return our whole inner area,
413 // to be on the safe side (since this method is used for generating
414 // invalidation rects).
415 return GetInnerArea();
418 sourceToDest.TransformCoord(&r.x, &r.y, &r.width, &r.height);
420 // Now, round the edges out to the pixel boundary.
421 nscoord scale = nsPresContext::CSSPixelsToAppUnits(1);
422 nscoord right = r.x + r.width;
423 nscoord bottom = r.y + r.height;
425 r.x -= (scale + (r.x % scale)) % scale;
426 r.y -= (scale + (r.y % scale)) % scale;
427 r.width = right + ((scale - (right % scale)) % scale) - r.x;
428 r.height = bottom + ((scale - (bottom % scale)) % scale) - r.y;
430 return r;
433 // Note that we treat NS_EVENT_STATE_SUPPRESSED images as "OK". This means
434 // that we'll construct image frames for them as needed if their display is
435 // toggled from "none" (though we won't paint them, unless their visibility
436 // is changed too).
437 #define BAD_STATES (NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_USERDISABLED | \
438 NS_EVENT_STATE_LOADING)
440 // This is a macro so that we don't evaluate the boolean last arg
441 // unless we have to; it can be expensive
442 #define IMAGE_OK(_state, _loadingOK) \
443 (!(_state).HasAtLeastOneOfStates(BAD_STATES) || \
444 (!(_state).HasAtLeastOneOfStates(NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_USERDISABLED) && \
445 (_state).HasState(NS_EVENT_STATE_LOADING) && (_loadingOK)))
447 /* static */
448 bool
449 nsImageFrame::ShouldCreateImageFrameFor(Element* aElement,
450 nsStyleContext* aStyleContext)
452 EventStates state = aElement->State();
453 if (IMAGE_OK(state,
454 HaveFixedSize(aStyleContext->StylePosition()))) {
455 // Image is fine; do the image frame thing
456 return true;
459 // Check if we want to use a placeholder box with an icon or just
460 // let the presShell make us into inline text. Decide as follows:
462 // - if our special "force icons" style is set, show an icon
463 // - else if our "do not show placeholders" pref is set, skip the icon
464 // - else:
465 // - if there is a src attribute, there is no alt attribute,
466 // and this is not an <object> (which could not possibly have
467 // such an attribute), show an icon.
468 // - if QuirksMode, and the IMG has a size show an icon.
469 // - otherwise, skip the icon
470 bool useSizedBox;
472 if (aStyleContext->StyleUIReset()->mForceBrokenImageIcon) {
473 useSizedBox = true;
475 else if (gIconLoad && gIconLoad->mPrefForceInlineAltText) {
476 useSizedBox = false;
478 else if (aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::src) &&
479 !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::alt) &&
480 !aElement->IsHTML(nsGkAtoms::object) &&
481 !aElement->IsHTML(nsGkAtoms::input)) {
482 // Use a sized box if we have no alt text. This means no alt attribute
483 // and the node is not an object or an input (since those always have alt
484 // text).
485 useSizedBox = true;
487 else if (aStyleContext->PresContext()->CompatibilityMode() !=
488 eCompatibility_NavQuirks) {
489 useSizedBox = false;
491 else {
492 // check whether we have fixed size
493 useSizedBox = HaveFixedSize(aStyleContext->StylePosition());
496 return useSizedBox;
499 nsresult
500 nsImageFrame::Notify(imgIRequest* aRequest,
501 int32_t aType,
502 const nsIntRect* aRect)
504 if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
505 nsCOMPtr<imgIContainer> image;
506 aRequest->GetImage(getter_AddRefs(image));
507 return OnSizeAvailable(aRequest, image);
510 if (aType == imgINotificationObserver::FRAME_UPDATE) {
511 return OnFrameUpdate(aRequest, aRect);
514 if (aType == imgINotificationObserver::FRAME_COMPLETE) {
515 mFirstFrameComplete = true;
518 if (aType == imgINotificationObserver::LOAD_COMPLETE) {
519 uint32_t imgStatus;
520 aRequest->GetImageStatus(&imgStatus);
521 nsresult status =
522 imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
523 return OnLoadComplete(aRequest, status);
526 return NS_OK;
529 static bool
530 SizeIsAvailable(imgIRequest* aRequest)
532 if (!aRequest)
533 return false;
535 uint32_t imageStatus = 0;
536 nsresult rv = aRequest->GetImageStatus(&imageStatus);
538 return NS_SUCCEEDED(rv) && (imageStatus & imgIRequest::STATUS_SIZE_AVAILABLE);
541 nsresult
542 nsImageFrame::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage)
544 if (!aImage) return NS_ERROR_INVALID_ARG;
546 /* Get requested animation policy from the pres context:
547 * normal = 0
548 * one frame = 1
549 * one loop = 2
551 nsPresContext *presContext = PresContext();
552 aImage->SetAnimationMode(presContext->ImageAnimationMode());
554 if (IsPendingLoad(aRequest)) {
555 // We don't care
556 return NS_OK;
559 bool intrinsicSizeChanged = false;
560 if (SizeIsAvailable(aRequest)) {
561 // This is valid and for the current request, so update our stored image
562 // container, orienting according to our style.
563 mImage = nsLayoutUtils::OrientImage(aImage, StyleVisibility()->mImageOrientation);
565 intrinsicSizeChanged = UpdateIntrinsicSize(mImage);
566 intrinsicSizeChanged = UpdateIntrinsicRatio(mImage) || intrinsicSizeChanged;
567 } else {
568 // We no longer have a valid image, so release our stored image container.
569 mImage = nullptr;
571 // Have to size to 0,0 so that GetDesiredSize recalculates the size.
572 mIntrinsicSize.width.SetCoordValue(0);
573 mIntrinsicSize.height.SetCoordValue(0);
574 mIntrinsicRatio.SizeTo(0, 0);
575 intrinsicSizeChanged = true;
578 if (intrinsicSizeChanged && (mState & IMAGE_GOTINITIALREFLOW)) {
579 // Now we need to reflow if we have an unconstrained size and have
580 // already gotten the initial reflow
581 if (!(mState & IMAGE_SIZECONSTRAINED)) {
582 nsIPresShell *presShell = presContext->GetPresShell();
583 NS_ASSERTION(presShell, "No PresShell.");
584 if (presShell) {
585 presShell->FrameNeedsReflow(this, nsIPresShell::eStyleChange,
586 NS_FRAME_IS_DIRTY);
591 return NS_OK;
594 nsresult
595 nsImageFrame::OnFrameUpdate(imgIRequest* aRequest, const nsIntRect* aRect)
597 NS_ENSURE_ARG_POINTER(aRect);
599 if (!(mState & IMAGE_GOTINITIALREFLOW)) {
600 // Don't bother to do anything; we have a reflow coming up!
601 return NS_OK;
604 if (mFirstFrameComplete && !StyleVisibility()->IsVisible()) {
605 return NS_OK;
608 if (IsPendingLoad(aRequest)) {
609 // We don't care
610 return NS_OK;
613 nsIntRect layerInvalidRect = mImage
614 ? mImage->GetImageSpaceInvalidationRect(*aRect)
615 : *aRect;
617 if (layerInvalidRect.IsEqualInterior(nsIntRect::GetMaxSizedIntRect())) {
618 // Invalidate our entire area.
619 InvalidateSelf(nullptr, nullptr);
620 return NS_OK;
623 nsRect frameInvalidRect = SourceRectToDest(layerInvalidRect);
624 InvalidateSelf(&layerInvalidRect, &frameInvalidRect);
625 return NS_OK;
628 void
629 nsImageFrame::InvalidateSelf(const nsIntRect* aLayerInvalidRect,
630 const nsRect* aFrameInvalidRect)
632 InvalidateLayer(nsDisplayItem::TYPE_IMAGE,
633 aLayerInvalidRect,
634 aFrameInvalidRect);
636 if (!mFirstFrameComplete) {
637 InvalidateLayer(nsDisplayItem::TYPE_ALT_FEEDBACK,
638 aLayerInvalidRect,
639 aFrameInvalidRect);
643 nsresult
644 nsImageFrame::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus)
646 // Check what request type we're dealing with
647 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
648 NS_ASSERTION(imageLoader, "Who's notifying us??");
649 int32_t loadType = nsIImageLoadingContent::UNKNOWN_REQUEST;
650 imageLoader->GetRequestType(aRequest, &loadType);
651 if (loadType != nsIImageLoadingContent::CURRENT_REQUEST &&
652 loadType != nsIImageLoadingContent::PENDING_REQUEST) {
653 return NS_ERROR_FAILURE;
656 NotifyNewCurrentRequest(aRequest, aStatus);
657 return NS_OK;
660 void
661 nsImageFrame::NotifyNewCurrentRequest(imgIRequest *aRequest,
662 nsresult aStatus)
664 nsCOMPtr<imgIContainer> image;
665 aRequest->GetImage(getter_AddRefs(image));
666 NS_ASSERTION(image || NS_FAILED(aStatus), "Successful load with no container?");
668 // May have to switch sizes here!
669 bool intrinsicSizeChanged = true;
670 if (NS_SUCCEEDED(aStatus) && image && SizeIsAvailable(aRequest)) {
671 // Update our stored image container, orienting according to our style.
672 mImage = nsLayoutUtils::OrientImage(image, StyleVisibility()->mImageOrientation);
674 intrinsicSizeChanged = UpdateIntrinsicSize(mImage);
675 intrinsicSizeChanged = UpdateIntrinsicRatio(mImage) || intrinsicSizeChanged;
676 } else {
677 // We no longer have a valid image, so release our stored image container.
678 mImage = nullptr;
680 // Have to size to 0,0 so that GetDesiredSize recalculates the size
681 mIntrinsicSize.width.SetCoordValue(0);
682 mIntrinsicSize.height.SetCoordValue(0);
683 mIntrinsicRatio.SizeTo(0, 0);
686 if (mState & IMAGE_GOTINITIALREFLOW) { // do nothing if we haven't gotten the initial reflow yet
687 if (!(mState & IMAGE_SIZECONSTRAINED) && intrinsicSizeChanged) {
688 nsIPresShell *presShell = PresContext()->GetPresShell();
689 if (presShell) {
690 presShell->FrameNeedsReflow(this, nsIPresShell::eStyleChange,
691 NS_FRAME_IS_DIRTY);
694 // Update border+content to account for image change
695 InvalidateFrame();
699 void
700 nsImageFrame::EnsureIntrinsicSizeAndRatio()
702 // If mIntrinsicSize.width and height are 0, then we need to update from the
703 // image container.
704 if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord &&
705 mIntrinsicSize.width.GetCoordValue() == 0 &&
706 mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord &&
707 mIntrinsicSize.height.GetCoordValue() == 0) {
709 if (mImage) {
710 UpdateIntrinsicSize(mImage);
711 UpdateIntrinsicRatio(mImage);
712 } else {
713 // image request is null or image size not known, probably an
714 // invalid image specified
715 // - make the image big enough for the icon (it may not be
716 // used if inline alt expansion is used instead)
717 if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
718 nscoord edgeLengthToUse =
719 nsPresContext::CSSPixelsToAppUnits(
720 ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH)));
721 mIntrinsicSize.width.SetCoordValue(edgeLengthToUse);
722 mIntrinsicSize.height.SetCoordValue(edgeLengthToUse);
723 mIntrinsicRatio.SizeTo(1, 1);
729 /* virtual */
730 LogicalSize
731 nsImageFrame::ComputeSize(nsRenderingContext *aRenderingContext,
732 WritingMode aWM,
733 const LogicalSize& aCBSize,
734 nscoord aAvailableISize,
735 const LogicalSize& aMargin,
736 const LogicalSize& aBorder,
737 const LogicalSize& aPadding,
738 ComputeSizeFlags aFlags)
740 EnsureIntrinsicSizeAndRatio();
742 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
743 NS_ASSERTION(imageLoader, "No content node??");
744 mozilla::IntrinsicSize intrinsicSize(mIntrinsicSize);
746 // XXX(seth): We may sometimes find ourselves in the situation where we have
747 // mImage, but imageLoader's current request does not have a size yet.
748 // This can happen when we load an image speculatively from cache, it fails
749 // to validate, and the new image load hasn't fired SIZE_AVAILABLE yet. In
750 // this situation we should always use mIntrinsicSize, because
751 // GetNaturalWidth/Height will return 0, so we check CurrentRequestHasSize()
752 // below. See bug 1019840. We will fix this in bug 1141395.
754 // Content may override our default dimensions. This is termed as overriding
755 // the intrinsic size by the spec, but all other consumers of mIntrinsic*
756 // values are being used to refer to the real/true size of the image data.
757 if (imageLoader && imageLoader->CurrentRequestHasSize() && mImage &&
758 intrinsicSize.width.GetUnit() == eStyleUnit_Coord &&
759 intrinsicSize.height.GetUnit() == eStyleUnit_Coord) {
760 uint32_t width;
761 uint32_t height;
762 if (NS_SUCCEEDED(imageLoader->GetNaturalWidth(&width)) &&
763 NS_SUCCEEDED(imageLoader->GetNaturalHeight(&height))) {
764 nscoord appWidth = nsPresContext::CSSPixelsToAppUnits((int32_t)width);
765 nscoord appHeight = nsPresContext::CSSPixelsToAppUnits((int32_t)height);
766 // If this image is rotated, we'll need to transpose the natural
767 // width/height.
768 bool coordFlip;
769 if (StyleVisibility()->mImageOrientation.IsFromImage()) {
770 coordFlip = mImage->GetOrientation().SwapsWidthAndHeight();
771 } else {
772 coordFlip = StyleVisibility()->mImageOrientation.SwapsWidthAndHeight();
774 intrinsicSize.width.SetCoordValue(coordFlip ? appHeight : appWidth);
775 intrinsicSize.height.SetCoordValue(coordFlip ? appWidth : appHeight);
779 return nsLayoutUtils::ComputeSizeWithIntrinsicDimensions(aWM,
780 aRenderingContext, this,
781 intrinsicSize, mIntrinsicRatio,
782 aCBSize,
783 aMargin,
784 aBorder,
785 aPadding);
788 // XXXdholbert This function's clients should probably just be calling
789 // GetContentRectRelativeToSelf() directly.
790 nsRect
791 nsImageFrame::GetInnerArea() const
793 return GetContentRectRelativeToSelf();
796 Element*
797 nsImageFrame::GetMapElement() const
799 nsAutoString usemap;
800 if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usemap, usemap)) {
801 return mContent->OwnerDoc()->FindImageMap(usemap);
803 return nullptr;
806 // get the offset into the content area of the image where aImg starts if it is a continuation.
807 nscoord
808 nsImageFrame::GetContinuationOffset() const
810 nscoord offset = 0;
811 for (nsIFrame *f = GetPrevInFlow(); f; f = f->GetPrevInFlow()) {
812 offset += f->GetContentRect().height;
814 NS_ASSERTION(offset >= 0, "bogus GetContentRect");
815 return offset;
818 /* virtual */ nscoord
819 nsImageFrame::GetMinISize(nsRenderingContext *aRenderingContext)
821 // XXX The caller doesn't account for constraints of the height,
822 // min-height, and max-height properties.
823 DebugOnly<nscoord> result;
824 DISPLAY_MIN_WIDTH(this, result);
825 EnsureIntrinsicSizeAndRatio();
826 return mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord ?
827 mIntrinsicSize.width.GetCoordValue() : 0;
830 /* virtual */ nscoord
831 nsImageFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
833 // XXX The caller doesn't account for constraints of the height,
834 // min-height, and max-height properties.
835 DebugOnly<nscoord> result;
836 DISPLAY_PREF_WIDTH(this, result);
837 EnsureIntrinsicSizeAndRatio();
838 // convert from normal twips to scaled twips (printing...)
839 return mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord ?
840 mIntrinsicSize.width.GetCoordValue() : 0;
843 /* virtual */ IntrinsicSize
844 nsImageFrame::GetIntrinsicSize()
846 return mIntrinsicSize;
849 /* virtual */ nsSize
850 nsImageFrame::GetIntrinsicRatio()
852 return mIntrinsicRatio;
855 void
856 nsImageFrame::Reflow(nsPresContext* aPresContext,
857 nsHTMLReflowMetrics& aMetrics,
858 const nsHTMLReflowState& aReflowState,
859 nsReflowStatus& aStatus)
861 DO_GLOBAL_REFLOW_COUNT("nsImageFrame");
862 DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus);
863 NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
864 ("enter nsImageFrame::Reflow: availSize=%d,%d",
865 aReflowState.AvailableWidth(), aReflowState.AvailableHeight()));
867 NS_PRECONDITION(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow");
869 aStatus = NS_FRAME_COMPLETE;
871 // see if we have a frozen size (i.e. a fixed width and height)
872 if (HaveFixedSize(aReflowState)) {
873 mState |= IMAGE_SIZECONSTRAINED;
874 } else {
875 mState &= ~IMAGE_SIZECONSTRAINED;
878 // XXXldb These two bits are almost exact opposites (except in the
879 // middle of the initial reflow); remove IMAGE_GOTINITIALREFLOW.
880 if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
881 mState |= IMAGE_GOTINITIALREFLOW;
884 mComputedSize =
885 nsSize(aReflowState.ComputedWidth(), aReflowState.ComputedHeight());
887 aMetrics.Width() = mComputedSize.width;
888 aMetrics.Height() = mComputedSize.height;
890 // add borders and padding
891 aMetrics.Width() += aReflowState.ComputedPhysicalBorderPadding().LeftRight();
892 aMetrics.Height() += aReflowState.ComputedPhysicalBorderPadding().TopBottom();
894 if (GetPrevInFlow()) {
895 aMetrics.Width() = GetPrevInFlow()->GetSize().width;
896 nscoord y = GetContinuationOffset();
897 aMetrics.Height() -= y + aReflowState.ComputedPhysicalBorderPadding().top;
898 aMetrics.Height() = std::max(0, aMetrics.Height());
902 // we have to split images if we are:
903 // in Paginated mode, we need to have a constrained height, and have a height larger than our available height
904 uint32_t loadStatus = imgIRequest::STATUS_NONE;
905 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
906 NS_ASSERTION(imageLoader, "No content node??");
907 if (imageLoader) {
908 nsCOMPtr<imgIRequest> currentRequest;
909 imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
910 getter_AddRefs(currentRequest));
911 if (currentRequest) {
912 currentRequest->GetImageStatus(&loadStatus);
915 if (aPresContext->IsPaginated() &&
916 ((loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) || (mState & IMAGE_SIZECONSTRAINED)) &&
917 NS_UNCONSTRAINEDSIZE != aReflowState.AvailableHeight() &&
918 aMetrics.Height() > aReflowState.AvailableHeight()) {
919 // our desired height was greater than 0, so to avoid infinite
920 // splitting, use 1 pixel as the min
921 aMetrics.Height() = std::max(nsPresContext::CSSPixelsToAppUnits(1), aReflowState.AvailableHeight());
922 aStatus = NS_FRAME_NOT_COMPLETE;
925 aMetrics.SetOverflowAreasToDesiredBounds();
926 EventStates contentState = mContent->AsElement()->State();
927 bool imageOK = IMAGE_OK(contentState, true);
929 // Determine if the size is available
930 bool haveSize = false;
931 if (loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) {
932 haveSize = true;
935 if (!imageOK || !haveSize) {
936 nsRect altFeedbackSize(0, 0,
937 nsPresContext::CSSPixelsToAppUnits(ICON_SIZE+2*(ICON_PADDING+ALT_BORDER_WIDTH)),
938 nsPresContext::CSSPixelsToAppUnits(ICON_SIZE+2*(ICON_PADDING+ALT_BORDER_WIDTH)));
939 // We include the altFeedbackSize in our visual overflow, but not in our
940 // scrollable overflow, since it doesn't really need to be scrolled to
941 // outside the image.
942 static_assert(eOverflowType_LENGTH == 2, "Unknown overflow types?");
943 nsRect& visualOverflow = aMetrics.VisualOverflow();
944 visualOverflow.UnionRect(visualOverflow, altFeedbackSize);
946 FinishAndStoreOverflow(&aMetrics);
948 if ((GetStateBits() & NS_FRAME_FIRST_REFLOW) && !mReflowCallbackPosted) {
949 nsIPresShell* shell = PresContext()->PresShell();
950 mReflowCallbackPosted = true;
951 shell->PostReflowCallback(this);
954 NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
955 ("exit nsImageFrame::Reflow: size=%d,%d",
956 aMetrics.Width(), aMetrics.Height()));
957 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics);
960 bool
961 nsImageFrame::ReflowFinished()
963 mReflowCallbackPosted = false;
965 nsLayoutUtils::UpdateImageVisibilityForFrame(this);
967 return false;
970 void
971 nsImageFrame::ReflowCallbackCanceled()
973 mReflowCallbackPosted = false;
976 // Computes the width of the specified string. aMaxWidth specifies the maximum
977 // width available. Once this limit is reached no more characters are measured.
978 // The number of characters that fit within the maximum width are returned in
979 // aMaxFit. NOTE: it is assumed that the fontmetrics have already been selected
980 // into the rendering context before this is called (for performance). MMP
981 nscoord
982 nsImageFrame::MeasureString(const char16_t* aString,
983 int32_t aLength,
984 nscoord aMaxWidth,
985 uint32_t& aMaxFit,
986 nsRenderingContext& aContext,
987 nsFontMetrics& aFontMetrics)
989 nscoord totalWidth = 0;
990 aFontMetrics.SetTextRunRTL(false);
991 nscoord spaceWidth = aFontMetrics.SpaceWidth();
993 aMaxFit = 0;
994 while (aLength > 0) {
995 // Find the next place we can line break
996 uint32_t len = aLength;
997 bool trailingSpace = false;
998 for (int32_t i = 0; i < aLength; i++) {
999 if (dom::IsSpaceCharacter(aString[i]) && (i > 0)) {
1000 len = i; // don't include the space when measuring
1001 trailingSpace = true;
1002 break;
1006 // Measure this chunk of text, and see if it fits
1007 nscoord width =
1008 nsLayoutUtils::AppUnitWidthOfStringBidi(aString, len, this, aFontMetrics,
1009 aContext);
1010 bool fits = (totalWidth + width) <= aMaxWidth;
1012 // If it fits on the line, or it's the first word we've processed then
1013 // include it
1014 if (fits || (0 == totalWidth)) {
1015 // New piece fits
1016 totalWidth += width;
1018 // If there's a trailing space then see if it fits as well
1019 if (trailingSpace) {
1020 if ((totalWidth + spaceWidth) <= aMaxWidth) {
1021 totalWidth += spaceWidth;
1022 } else {
1023 // Space won't fit. Leave it at the end but don't include it in
1024 // the width
1025 fits = false;
1028 len++;
1031 aMaxFit += len;
1032 aString += len;
1033 aLength -= len;
1036 if (!fits) {
1037 break;
1040 return totalWidth;
1043 // Formats the alt-text to fit within the specified rectangle. Breaks lines
1044 // between words if a word would extend past the edge of the rectangle
1045 void
1046 nsImageFrame::DisplayAltText(nsPresContext* aPresContext,
1047 nsRenderingContext& aRenderingContext,
1048 const nsString& aAltText,
1049 const nsRect& aRect)
1051 // Set font and color
1052 aRenderingContext.ThebesContext()->SetColor(StyleColor()->mColor);
1053 nsRefPtr<nsFontMetrics> fm;
1054 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm),
1055 nsLayoutUtils::FontSizeInflationFor(this));
1057 // Format the text to display within the formatting rect
1059 nscoord maxAscent = fm->MaxAscent();
1060 nscoord maxDescent = fm->MaxDescent();
1061 nscoord lineHeight = fm->MaxHeight(); // line-relative, so an x-coordinate
1062 // length if writing mode is vertical
1064 WritingMode wm = GetWritingMode();
1065 bool isVertical = wm.IsVertical();
1067 fm->SetVertical(isVertical);
1068 fm->SetTextOrientation(StyleVisibility()->mTextOrientation);
1070 // XXX It would be nice if there was a way to have the font metrics tell
1071 // use where to break the text given a maximum width. At a minimum we need
1072 // to be able to get the break character...
1073 const char16_t* str = aAltText.get();
1074 int32_t strLen = aAltText.Length();
1075 nsPoint pt = wm.IsVerticalRL() ? aRect.TopRight() - nsPoint(lineHeight, 0)
1076 : aRect.TopLeft();
1077 nscoord iSize = isVertical ? aRect.height : aRect.width;
1079 if (!aPresContext->BidiEnabled() && HasRTLChars(aAltText)) {
1080 aPresContext->SetBidiEnabled();
1083 // Always show the first line, even if we have to clip it below
1084 bool firstLine = true;
1085 while (strLen > 0) {
1086 if (!firstLine) {
1087 // If we've run out of space, break out of the loop
1088 if ((!isVertical && (pt.y + maxDescent) >= aRect.YMost()) ||
1089 (wm.IsVerticalRL() && (pt.x + maxDescent < aRect.x)) ||
1090 (wm.IsVerticalLR() && (pt.x + maxDescent >= aRect.XMost()))) {
1091 break;
1095 // Determine how much of the text to display on this line
1096 uint32_t maxFit; // number of characters that fit
1097 nscoord strWidth = MeasureString(str, strLen, iSize, maxFit,
1098 aRenderingContext, *fm);
1100 // Display the text
1101 nsresult rv = NS_ERROR_FAILURE;
1103 if (aPresContext->BidiEnabled()) {
1104 nsBidiDirection dir;
1105 nscoord x, y;
1107 if (isVertical) {
1108 x = pt.x + maxDescent; // XXX will need update for sideways-left
1109 if (wm.IsBidiLTR()) {
1110 y = aRect.y;
1111 dir = NSBIDI_LTR;
1112 } else {
1113 y = aRect.YMost() - strWidth;
1114 dir = NSBIDI_RTL;
1116 } else {
1117 y = pt.y + maxAscent;
1118 if (wm.IsBidiLTR()) {
1119 x = aRect.x;
1120 dir = NSBIDI_LTR;
1121 } else {
1122 x = aRect.XMost() - strWidth;
1123 dir = NSBIDI_RTL;
1127 rv = nsBidiPresUtils::RenderText(str, maxFit, dir,
1128 aPresContext, aRenderingContext,
1129 aRenderingContext, *fm, x, y);
1131 if (NS_FAILED(rv)) {
1132 nsLayoutUtils::DrawUniDirString(str, maxFit,
1133 isVertical
1134 ? nsPoint(pt.x + maxDescent, pt.y)
1135 : nsPoint(pt.x, pt.y + maxAscent),
1136 *fm, aRenderingContext);
1139 // Move to the next line
1140 str += maxFit;
1141 strLen -= maxFit;
1142 if (wm.IsVerticalRL()) {
1143 pt.x -= lineHeight;
1144 } else if (wm.IsVerticalLR()) {
1145 pt.x += lineHeight;
1146 } else {
1147 pt.y += lineHeight;
1150 firstLine = false;
1154 struct nsRecessedBorder : public nsStyleBorder {
1155 nsRecessedBorder(nscoord aBorderWidth, nsPresContext* aPresContext)
1156 : nsStyleBorder(aPresContext)
1158 NS_FOR_CSS_SIDES(side) {
1159 // Note: use SetBorderColor here because we want to make sure
1160 // the "special" flags are unset.
1161 SetBorderColor(side, NS_RGB(0, 0, 0));
1162 mBorder.Side(side) = aBorderWidth;
1163 // Note: use SetBorderStyle here because we want to affect
1164 // mComputedBorder
1165 SetBorderStyle(side, NS_STYLE_BORDER_STYLE_INSET);
1170 class nsDisplayAltFeedback : public nsDisplayItem {
1171 public:
1172 nsDisplayAltFeedback(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
1173 : nsDisplayItem(aBuilder, aFrame) {}
1175 virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
1176 bool* aSnap) MOZ_OVERRIDE
1178 *aSnap = false;
1179 return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
1182 virtual void Paint(nsDisplayListBuilder* aBuilder,
1183 nsRenderingContext* aCtx) MOZ_OVERRIDE
1185 nsImageFrame* f = static_cast<nsImageFrame*>(mFrame);
1186 EventStates state = f->GetContent()->AsElement()->State();
1187 f->DisplayAltFeedback(*aCtx,
1188 mVisibleRect,
1189 IMAGE_OK(state, true)
1190 ? nsImageFrame::gIconLoad->mLoadingImage
1191 : nsImageFrame::gIconLoad->mBrokenImage,
1192 ToReferenceFrame());
1196 NS_DISPLAY_DECL_NAME("AltFeedback", TYPE_ALT_FEEDBACK)
1199 void
1200 nsImageFrame::DisplayAltFeedback(nsRenderingContext& aRenderingContext,
1201 const nsRect& aDirtyRect,
1202 imgIRequest* aRequest,
1203 nsPoint aPt)
1205 // We should definitely have a gIconLoad here.
1206 NS_ABORT_IF_FALSE(gIconLoad, "How did we succeed in Init then?");
1208 // Calculate the inner area
1209 nsRect inner = GetInnerArea() + aPt;
1211 // Display a recessed one pixel border
1212 nscoord borderEdgeWidth = nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH);
1214 // if inner area is empty, then make it big enough for at least the icon
1215 if (inner.IsEmpty()){
1216 inner.SizeTo(2*(nsPresContext::CSSPixelsToAppUnits(ICON_SIZE+ICON_PADDING+ALT_BORDER_WIDTH)),
1217 2*(nsPresContext::CSSPixelsToAppUnits(ICON_SIZE+ICON_PADDING+ALT_BORDER_WIDTH)));
1220 // Make sure we have enough room to actually render the border within
1221 // our frame bounds
1222 if ((inner.width < 2 * borderEdgeWidth) || (inner.height < 2 * borderEdgeWidth)) {
1223 return;
1226 // Paint the border
1227 nsRecessedBorder recessedBorder(borderEdgeWidth, PresContext());
1228 nsCSSRendering::PaintBorderWithStyleBorder(PresContext(), aRenderingContext,
1229 this, inner, inner,
1230 recessedBorder, mStyleContext);
1232 // Adjust the inner rect to account for the one pixel recessed border,
1233 // and a six pixel padding on each edge
1234 inner.Deflate(nsPresContext::CSSPixelsToAppUnits(ICON_PADDING+ALT_BORDER_WIDTH),
1235 nsPresContext::CSSPixelsToAppUnits(ICON_PADDING+ALT_BORDER_WIDTH));
1236 if (inner.IsEmpty()) {
1237 return;
1240 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
1241 gfxContext* gfx = aRenderingContext.ThebesContext();
1243 // Clip so we don't render outside the inner rect
1244 gfx->Save();
1245 gfx->Clip(NSRectToSnappedRect(inner, PresContext()->AppUnitsPerDevPixel(),
1246 *drawTarget));
1248 // Check if we should display image placeholders
1249 if (gIconLoad->mPrefShowPlaceholders) {
1250 nscoord size = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE);
1252 bool iconUsed = false;
1254 // If we weren't previously displaying an icon, register ourselves
1255 // as an observer for load and animation updates and flag that we're
1256 // doing so now.
1257 if (aRequest && !mDisplayingIcon) {
1258 gIconLoad->AddIconObserver(this);
1259 mDisplayingIcon = true;
1262 WritingMode wm = GetWritingMode();
1263 bool flushRight =
1264 (!wm.IsVertical() && !wm.IsBidiLTR()) || wm.IsVerticalRL();
1266 // If the icon in question is loaded and decoded, draw it
1267 uint32_t imageStatus = 0;
1268 if (aRequest)
1269 aRequest->GetImageStatus(&imageStatus);
1270 if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE) {
1271 nsCOMPtr<imgIContainer> imgCon;
1272 aRequest->GetImage(getter_AddRefs(imgCon));
1273 MOZ_ASSERT(imgCon, "Load complete, but no image container?");
1274 nsRect dest(flushRight ? inner.XMost() - size : inner.x,
1275 inner.y, size, size);
1276 nsLayoutUtils::DrawSingleImage(*gfx, PresContext(), imgCon,
1277 nsLayoutUtils::GetGraphicsFilterForFrame(this), dest, aDirtyRect,
1278 nullptr, imgIContainer::FLAG_SYNC_DECODE);
1279 iconUsed = true;
1282 // if we could not draw the icon, flag that we're waiting for it and
1283 // just draw some graffiti in the mean time
1284 if (!iconUsed) {
1285 ColorPattern color(ToDeviceColor(Color(1.f, 0.f, 0.f, 1.f)));
1287 nscoord iconXPos = flushRight ? inner.XMost() - size : inner.x;
1289 // stroked rect:
1290 nsRect rect(iconXPos, inner.y, size, size);
1291 Rect devPxRect =
1292 ToRect(nsLayoutUtils::RectToGfxRect(rect, PresContext()->AppUnitsPerDevPixel()));
1293 drawTarget->StrokeRect(devPxRect, color);
1295 // filled circle in bottom right quadrant of stroked rect:
1296 nscoord twoPX = nsPresContext::CSSPixelsToAppUnits(2);
1297 rect = nsRect(iconXPos + size/2, inner.y + size/2,
1298 size/2 - twoPX, size/2 - twoPX);
1299 devPxRect =
1300 ToRect(nsLayoutUtils::RectToGfxRect(rect, PresContext()->AppUnitsPerDevPixel()));
1301 RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder();
1302 AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size());
1303 RefPtr<Path> ellipse = builder->Finish();
1304 drawTarget->Fill(ellipse, color);
1307 // Reduce the inner rect by the width of the icon, and leave an
1308 // additional ICON_PADDING pixels for padding
1309 int32_t paddedIconSize =
1310 nsPresContext::CSSPixelsToAppUnits(ICON_SIZE + ICON_PADDING);
1311 if (wm.IsVertical()) {
1312 inner.y += paddedIconSize;
1313 inner.height -= paddedIconSize;
1314 } else {
1315 if (!flushRight) {
1316 inner.x += paddedIconSize;
1318 inner.width -= paddedIconSize;
1322 // If there's still room, display the alt-text
1323 if (!inner.IsEmpty()) {
1324 nsIContent* content = GetContent();
1325 if (content) {
1326 nsXPIDLString altText;
1327 nsCSSFrameConstructor::GetAlternateTextFor(content, content->Tag(),
1328 altText);
1329 DisplayAltText(PresContext(), aRenderingContext, altText, inner);
1333 aRenderingContext.ThebesContext()->Restore();
1336 #ifdef DEBUG
1337 static void PaintDebugImageMap(nsIFrame* aFrame, nsRenderingContext* aCtx,
1338 const nsRect& aDirtyRect, nsPoint aPt)
1340 nsImageFrame* f = static_cast<nsImageFrame*>(aFrame);
1341 nsRect inner = f->GetInnerArea() + aPt;
1342 gfxPoint devPixelOffset =
1343 nsLayoutUtils::PointToGfxPoint(inner.TopLeft(),
1344 aFrame->PresContext()->AppUnitsPerDevPixel());
1345 DrawTarget* drawTarget = aCtx->GetDrawTarget();
1346 AutoRestoreTransform autoRestoreTransform(drawTarget);
1347 drawTarget->SetTransform(
1348 drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
1349 f->GetImageMap()->Draw(aFrame, *drawTarget,
1350 ColorPattern(ToDeviceColor(Color(0.f, 0.f, 0.f, 1.f))));
1352 #endif
1354 void
1355 nsDisplayImage::Paint(nsDisplayListBuilder* aBuilder,
1356 nsRenderingContext* aCtx) {
1357 uint32_t flags = imgIContainer::FLAG_NONE;
1358 if (aBuilder->ShouldSyncDecodeImages()) {
1359 flags |= imgIContainer::FLAG_SYNC_DECODE;
1361 if (aBuilder->IsPaintingToWindow()) {
1362 flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
1365 DrawResult result = static_cast<nsImageFrame*>(mFrame)->
1366 PaintImage(*aCtx, ToReferenceFrame(), mVisibleRect, mImage, flags);
1368 nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
1371 nsDisplayItemGeometry*
1372 nsDisplayImage::AllocateGeometry(nsDisplayListBuilder* aBuilder)
1374 return new nsDisplayItemGenericImageGeometry(this, aBuilder);
1377 void
1378 nsDisplayImage::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
1379 const nsDisplayItemGeometry* aGeometry,
1380 nsRegion* aInvalidRegion)
1382 auto geometry =
1383 static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
1385 if (aBuilder->ShouldSyncDecodeImages() &&
1386 geometry->ShouldInvalidateToSyncDecodeImages()) {
1387 bool snap;
1388 aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
1391 nsDisplayImageContainer::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
1394 already_AddRefed<ImageContainer>
1395 nsDisplayImage::GetContainer(LayerManager* aManager,
1396 nsDisplayListBuilder* aBuilder)
1398 nsRefPtr<ImageContainer> container;
1399 mImage->GetImageContainer(aManager, getter_AddRefs(container));
1400 return container.forget();
1403 gfxRect
1404 nsDisplayImage::GetDestRect()
1406 int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
1408 bool snap;
1409 nsRect dest = GetBounds(&snap);
1410 gfxRect destRect(dest.x, dest.y, dest.width, dest.height);
1411 destRect.ScaleInverse(factor);
1413 return destRect;
1416 LayerState
1417 nsDisplayImage::GetLayerState(nsDisplayListBuilder* aBuilder,
1418 LayerManager* aManager,
1419 const ContainerLayerParameters& aParameters)
1421 bool animated = false;
1422 if (!nsLayoutUtils::AnimatedImageLayersEnabled() ||
1423 mImage->GetType() != imgIContainer::TYPE_RASTER ||
1424 NS_FAILED(mImage->GetAnimated(&animated)) ||
1425 !animated) {
1426 if (!aManager->IsCompositingCheap() ||
1427 !nsLayoutUtils::GPUImageScalingEnabled()) {
1428 return LAYER_NONE;
1432 if (!animated) {
1433 int32_t imageWidth;
1434 int32_t imageHeight;
1435 mImage->GetWidth(&imageWidth);
1436 mImage->GetHeight(&imageHeight);
1438 NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!");
1440 gfxRect destRect = GetDestRect();
1442 destRect.width *= aParameters.mXScale;
1443 destRect.height *= aParameters.mYScale;
1445 // Calculate the scaling factor for the frame.
1446 gfxSize scale = gfxSize(destRect.width / imageWidth,
1447 destRect.height / imageHeight);
1449 // If we are not scaling at all, no point in separating this into a layer.
1450 if (scale.width == 1.0f && scale.height == 1.0f) {
1451 return LAYER_NONE;
1454 // If the target size is pretty small, no point in using a layer.
1455 if (destRect.width * destRect.height < 64 * 64) {
1456 return LAYER_NONE;
1460 nsRefPtr<ImageContainer> container;
1461 mImage->GetImageContainer(aManager, getter_AddRefs(container));
1462 if (!container) {
1463 return LAYER_NONE;
1466 return LAYER_ACTIVE;
1470 /* virtual */ nsRegion
1471 nsDisplayImage::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
1472 bool* aSnap)
1474 *aSnap = true;
1475 if (mImage && mImage->IsOpaque()) {
1476 // OK, the entire region painted by the image is opaque. But what is that
1477 // region? It's the image's "dest rect" (the rect where a full copy of
1478 // the image is mapped), clipped to the container's content box (which is
1479 // what GetBounds() returns). So, we grab those rects and intersect them.
1480 const nsRect frameContentBox = GetBounds(aSnap);
1482 // Note: To get the "dest rect", we have to provide the "constraint rect"
1483 // (which is the content-box, with the effects of fragmentation undone).
1484 nsImageFrame* imageFrame = static_cast<nsImageFrame*>(mFrame);
1485 nsRect constraintRect(frameContentBox.TopLeft(),
1486 imageFrame->mComputedSize);
1487 constraintRect.y -= imageFrame->GetContinuationOffset();
1489 const nsRect destRect =
1490 nsLayoutUtils::ComputeObjectDestRect(constraintRect,
1491 imageFrame->mIntrinsicSize,
1492 imageFrame->mIntrinsicRatio,
1493 imageFrame->StylePosition());
1495 return nsRegion(destRect.Intersect(frameContentBox));
1497 return nsRegion();
1500 already_AddRefed<Layer>
1501 nsDisplayImage::BuildLayer(nsDisplayListBuilder* aBuilder,
1502 LayerManager* aManager,
1503 const ContainerLayerParameters& aParameters)
1505 nsRefPtr<ImageContainer> container;
1506 nsresult rv = mImage->GetImageContainer(aManager, getter_AddRefs(container));
1507 NS_ENSURE_SUCCESS(rv, nullptr);
1509 nsRefPtr<ImageLayer> layer = static_cast<ImageLayer*>
1510 (aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this));
1511 if (!layer) {
1512 layer = aManager->CreateImageLayer();
1513 if (!layer)
1514 return nullptr;
1516 layer->SetContainer(container);
1517 ConfigureLayer(layer, aParameters.mOffset);
1518 return layer.forget();
1521 void
1522 nsDisplayImage::ConfigureLayer(ImageLayer *aLayer, const nsIntPoint& aOffset)
1524 aLayer->SetFilter(nsLayoutUtils::GetGraphicsFilterForFrame(mFrame));
1526 int32_t imageWidth;
1527 int32_t imageHeight;
1528 mImage->GetWidth(&imageWidth);
1529 mImage->GetHeight(&imageHeight);
1531 NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!");
1532 if (imageWidth > 0 && imageHeight > 0) {
1533 // We're actually using the ImageContainer. Let our frame know that it
1534 // should consider itself to have painted successfully.
1535 nsDisplayItemGenericImageGeometry::UpdateDrawResult(this,
1536 DrawResult::SUCCESS);
1539 const gfxRect destRect = GetDestRect();
1541 gfxPoint p = destRect.TopLeft() + aOffset;
1542 Matrix transform = Matrix::Translation(p.x, p.y);
1543 transform.PreScale(destRect.Width() / imageWidth,
1544 destRect.Height() / imageHeight);
1545 aLayer->SetBaseTransform(gfx::Matrix4x4::From2D(transform));
1548 DrawResult
1549 nsImageFrame::PaintImage(nsRenderingContext& aRenderingContext, nsPoint aPt,
1550 const nsRect& aDirtyRect, imgIContainer* aImage,
1551 uint32_t aFlags)
1553 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
1555 // Render the image into our content area (the area inside
1556 // the borders and padding)
1557 NS_ASSERTION(GetInnerArea().width == mComputedSize.width, "bad width");
1559 // NOTE: We use mComputedSize instead of just GetInnerArea()'s own size here,
1560 // because GetInnerArea() might be smaller if we're fragmented, whereas
1561 // mComputedSize has our full content-box size (which we need for
1562 // ComputeObjectDestRect to work correctly).
1563 nsRect constraintRect(aPt + GetInnerArea().TopLeft(), mComputedSize);
1564 constraintRect.y -= GetContinuationOffset();
1566 nsPoint anchorPoint;
1567 nsRect dest = nsLayoutUtils::ComputeObjectDestRect(constraintRect,
1568 mIntrinsicSize,
1569 mIntrinsicRatio,
1570 StylePosition(),
1571 &anchorPoint);
1573 DrawResult result =
1574 nsLayoutUtils::DrawSingleImage(*aRenderingContext.ThebesContext(),
1575 PresContext(), aImage,
1576 nsLayoutUtils::GetGraphicsFilterForFrame(this), dest, aDirtyRect,
1577 nullptr, aFlags, &anchorPoint);
1579 nsImageMap* map = GetImageMap();
1580 if (map) {
1581 gfxPoint devPixelOffset =
1582 nsLayoutUtils::PointToGfxPoint(dest.TopLeft(),
1583 PresContext()->AppUnitsPerDevPixel());
1584 AutoRestoreTransform autoRestoreTransform(drawTarget);
1585 drawTarget->SetTransform(
1586 drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
1588 // solid white stroke:
1589 ColorPattern white(ToDeviceColor(Color(1.f, 1.f, 1.f, 1.f)));
1590 map->Draw(this, *drawTarget, white);
1592 // then dashed black stroke over the top:
1593 ColorPattern black(ToDeviceColor(Color(0.f, 0.f, 0.f, 1.f)));
1594 StrokeOptions strokeOptions;
1595 nsLayoutUtils::InitDashPattern(strokeOptions, NS_STYLE_BORDER_STYLE_DOTTED);
1596 map->Draw(this, *drawTarget, black, strokeOptions);
1599 return result;
1602 void
1603 nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1604 const nsRect& aDirtyRect,
1605 const nsDisplayListSet& aLists)
1607 if (!IsVisibleForPainting(aBuilder))
1608 return;
1610 DisplayBorderBackgroundOutline(aBuilder, aLists);
1612 uint32_t clipFlags =
1613 nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition()) ?
1614 0 : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
1616 DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox
1617 clip(aBuilder, this, clipFlags);
1619 if (mComputedSize.width != 0 && mComputedSize.height != 0) {
1620 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
1621 NS_ASSERTION(imageLoader, "Not an image loading content?");
1623 nsCOMPtr<imgIRequest> currentRequest;
1624 if (imageLoader) {
1625 imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
1626 getter_AddRefs(currentRequest));
1629 EventStates contentState = mContent->AsElement()->State();
1630 bool imageOK = IMAGE_OK(contentState, true);
1632 // XXX(seth): The SizeIsAvailable check here should not be necessary - the
1633 // intention is that a non-null mImage means we have a size, but there is
1634 // currently some code that violates this invariant.
1635 if (!imageOK || !mImage || !SizeIsAvailable(currentRequest)) {
1636 // No image yet, or image load failed. Draw the alt-text and an icon
1637 // indicating the status
1638 aLists.Content()->AppendNewToTop(new (aBuilder)
1639 nsDisplayAltFeedback(aBuilder, this));
1640 } else {
1641 aLists.Content()->AppendNewToTop(new (aBuilder)
1642 nsDisplayImage(aBuilder, this, mImage));
1644 // If we were previously displaying an icon, we're not anymore
1645 if (mDisplayingIcon) {
1646 gIconLoad->RemoveIconObserver(this);
1647 mDisplayingIcon = false;
1650 #ifdef DEBUG
1651 if (GetShowFrameBorders() && GetImageMap()) {
1652 aLists.Outlines()->AppendNewToTop(new (aBuilder)
1653 nsDisplayGeneric(aBuilder, this, PaintDebugImageMap, "DebugImageMap",
1654 nsDisplayItem::TYPE_DEBUG_IMAGE_MAP));
1656 #endif
1660 if (ShouldDisplaySelection()) {
1661 DisplaySelectionOverlay(aBuilder, aLists.Content(),
1662 nsISelectionDisplay::DISPLAY_IMAGES);
1666 bool
1667 nsImageFrame::ShouldDisplaySelection()
1669 // XXX what on EARTH is this code for?
1670 nsresult result;
1671 nsPresContext* presContext = PresContext();
1672 int16_t displaySelection = presContext->PresShell()->GetSelectionFlags();
1673 if (!(displaySelection & nsISelectionDisplay::DISPLAY_IMAGES))
1674 return false;//no need to check the blue border, we cannot be drawn selected
1675 //insert hook here for image selection drawing
1676 #if IMAGE_EDITOR_CHECK
1677 //check to see if this frame is in an editor context
1678 //isEditor check. this needs to be changed to have better way to check
1679 if (displaySelection == nsISelectionDisplay::DISPLAY_ALL)
1681 nsCOMPtr<nsISelectionController> selCon;
1682 result = GetSelectionController(presContext, getter_AddRefs(selCon));
1683 if (NS_SUCCEEDED(result) && selCon)
1685 nsCOMPtr<nsISelection> selection;
1686 result = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
1687 if (NS_SUCCEEDED(result) && selection)
1689 int32_t rangeCount;
1690 selection->GetRangeCount(&rangeCount);
1691 if (rangeCount == 1) //if not one then let code drop to nsFrame::Paint
1693 nsCOMPtr<nsIContent> parentContent = mContent->GetParent();
1694 if (parentContent)
1696 int32_t thisOffset = parentContent->IndexOf(mContent);
1697 nsCOMPtr<nsIDOMNode> parentNode = do_QueryInterface(parentContent);
1698 nsCOMPtr<nsIDOMNode> rangeNode;
1699 int32_t rangeOffset;
1700 nsCOMPtr<nsIDOMRange> range;
1701 selection->GetRangeAt(0,getter_AddRefs(range));
1702 if (range)
1704 range->GetStartContainer(getter_AddRefs(rangeNode));
1705 range->GetStartOffset(&rangeOffset);
1707 if (parentNode && rangeNode && (rangeNode == parentNode) && rangeOffset == thisOffset)
1709 range->GetEndContainer(getter_AddRefs(rangeNode));
1710 range->GetEndOffset(&rangeOffset);
1711 if ((rangeNode == parentNode) && (rangeOffset == (thisOffset +1))) //+1 since that would mean this whole content is selected only
1712 return false; //do not allow nsFrame do draw any further selection
1720 #endif
1721 return true;
1724 nsImageMap*
1725 nsImageFrame::GetImageMap()
1727 if (!mImageMap) {
1728 nsIContent* map = GetMapElement();
1729 if (map) {
1730 mImageMap = new nsImageMap();
1731 NS_ADDREF(mImageMap);
1732 mImageMap->Init(this, map);
1736 return mImageMap;
1739 bool
1740 nsImageFrame::IsServerImageMap()
1742 return mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::ismap);
1745 // Translate an point that is relative to our frame
1746 // into a localized pixel coordinate that is relative to the
1747 // content area of this frame (inside the border+padding).
1748 void
1749 nsImageFrame::TranslateEventCoords(const nsPoint& aPoint,
1750 nsIntPoint& aResult)
1752 nscoord x = aPoint.x;
1753 nscoord y = aPoint.y;
1755 // Subtract out border and padding here so that the coordinates are
1756 // now relative to the content area of this frame.
1757 nsRect inner = GetInnerArea();
1758 x -= inner.x;
1759 y -= inner.y;
1761 aResult.x = nsPresContext::AppUnitsToIntCSSPixels(x);
1762 aResult.y = nsPresContext::AppUnitsToIntCSSPixels(y);
1765 bool
1766 nsImageFrame::GetAnchorHREFTargetAndNode(nsIURI** aHref, nsString& aTarget,
1767 nsIContent** aNode)
1769 bool status = false;
1770 aTarget.Truncate();
1771 *aHref = nullptr;
1772 *aNode = nullptr;
1774 // Walk up the content tree, looking for an nsIDOMAnchorElement
1775 for (nsIContent* content = mContent->GetParent();
1776 content; content = content->GetParent()) {
1777 nsCOMPtr<dom::Link> link(do_QueryInterface(content));
1778 if (link) {
1779 nsCOMPtr<nsIURI> href = content->GetHrefURI();
1780 if (href) {
1781 href->Clone(aHref);
1783 status = (*aHref != nullptr);
1785 nsCOMPtr<nsIDOMHTMLAnchorElement> anchor(do_QueryInterface(content));
1786 if (anchor) {
1787 anchor->GetTarget(aTarget);
1789 NS_ADDREF(*aNode = content);
1790 break;
1793 return status;
1796 nsresult
1797 nsImageFrame::GetContentForEvent(WidgetEvent* aEvent,
1798 nsIContent** aContent)
1800 NS_ENSURE_ARG_POINTER(aContent);
1802 nsIFrame* f = nsLayoutUtils::GetNonGeneratedAncestor(this);
1803 if (f != this) {
1804 return f->GetContentForEvent(aEvent, aContent);
1807 // XXX We need to make this special check for area element's capturing the
1808 // mouse due to bug 135040. Remove it once that's fixed.
1809 nsIContent* capturingContent =
1810 aEvent->HasMouseEventMessage() ? nsIPresShell::GetCapturingContent() :
1811 nullptr;
1812 if (capturingContent && capturingContent->GetPrimaryFrame() == this) {
1813 *aContent = capturingContent;
1814 NS_IF_ADDREF(*aContent);
1815 return NS_OK;
1818 nsImageMap* map = GetImageMap();
1820 if (nullptr != map) {
1821 nsIntPoint p;
1822 TranslateEventCoords(
1823 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this), p);
1824 nsCOMPtr<nsIContent> area = map->GetArea(p.x, p.y);
1825 if (area) {
1826 area.forget(aContent);
1827 return NS_OK;
1831 *aContent = GetContent();
1832 NS_IF_ADDREF(*aContent);
1833 return NS_OK;
1836 // XXX what should clicks on transparent pixels do?
1837 nsresult
1838 nsImageFrame::HandleEvent(nsPresContext* aPresContext,
1839 WidgetGUIEvent* aEvent,
1840 nsEventStatus* aEventStatus)
1842 NS_ENSURE_ARG_POINTER(aEventStatus);
1844 if ((aEvent->message == NS_MOUSE_BUTTON_UP &&
1845 aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) ||
1846 aEvent->message == NS_MOUSE_MOVE) {
1847 nsImageMap* map = GetImageMap();
1848 bool isServerMap = IsServerImageMap();
1849 if ((nullptr != map) || isServerMap) {
1850 nsIntPoint p;
1851 TranslateEventCoords(
1852 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this), p);
1853 bool inside = false;
1854 // Even though client-side image map triggering happens
1855 // through content, we need to make sure we're not inside
1856 // (in case we deal with a case of both client-side and
1857 // sever-side on the same image - it happens!)
1858 if (nullptr != map) {
1859 inside = !!map->GetArea(p.x, p.y);
1862 if (!inside && isServerMap) {
1864 // Server side image maps use the href in a containing anchor
1865 // element to provide the basis for the destination url.
1866 nsCOMPtr<nsIURI> uri;
1867 nsAutoString target;
1868 nsCOMPtr<nsIContent> anchorNode;
1869 if (GetAnchorHREFTargetAndNode(getter_AddRefs(uri), target,
1870 getter_AddRefs(anchorNode))) {
1871 // XXX if the mouse is over/clicked in the border/padding area
1872 // we should probably just pretend nothing happened. Nav4
1873 // keeps the x,y coordinates positive as we do; IE doesn't
1874 // bother. Both of them send the click through even when the
1875 // mouse is over the border.
1876 if (p.x < 0) p.x = 0;
1877 if (p.y < 0) p.y = 0;
1878 nsAutoCString spec;
1879 uri->GetSpec(spec);
1880 spec += nsPrintfCString("?%d,%d", p.x, p.y);
1881 uri->SetSpec(spec);
1883 bool clicked = false;
1884 if (aEvent->message == NS_MOUSE_BUTTON_UP) {
1885 *aEventStatus = nsEventStatus_eConsumeDoDefault;
1886 clicked = true;
1888 nsContentUtils::TriggerLink(anchorNode, aPresContext, uri, target,
1889 clicked, true, true);
1895 return nsSplittableFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
1898 nsresult
1899 nsImageFrame::GetCursor(const nsPoint& aPoint,
1900 nsIFrame::Cursor& aCursor)
1902 nsImageMap* map = GetImageMap();
1903 if (nullptr != map) {
1904 nsIntPoint p;
1905 TranslateEventCoords(aPoint, p);
1906 nsCOMPtr<nsIContent> area = map->GetArea(p.x, p.y);
1907 if (area) {
1908 // Use the cursor from the style of the *area* element.
1909 // XXX Using the image as the parent style context isn't
1910 // technically correct, but it's probably the right thing to do
1911 // here, since it means that areas on which the cursor isn't
1912 // specified will inherit the style from the image.
1913 nsRefPtr<nsStyleContext> areaStyle =
1914 PresContext()->PresShell()->StyleSet()->
1915 ResolveStyleFor(area->AsElement(), StyleContext());
1916 FillCursorInformationFromStyle(areaStyle->StyleUserInterface(),
1917 aCursor);
1918 if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) {
1919 aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT;
1921 return NS_OK;
1924 return nsFrame::GetCursor(aPoint, aCursor);
1927 nsresult
1928 nsImageFrame::AttributeChanged(int32_t aNameSpaceID,
1929 nsIAtom* aAttribute,
1930 int32_t aModType)
1932 nsresult rv = nsSplittableFrame::AttributeChanged(aNameSpaceID,
1933 aAttribute, aModType);
1934 if (NS_FAILED(rv)) {
1935 return rv;
1937 if (nsGkAtoms::alt == aAttribute)
1939 PresContext()->PresShell()->FrameNeedsReflow(this,
1940 nsIPresShell::eStyleChange,
1941 NS_FRAME_IS_DIRTY);
1944 return NS_OK;
1947 nsIAtom*
1948 nsImageFrame::GetType() const
1950 return nsGkAtoms::imageFrame;
1953 #ifdef DEBUG_FRAME_DUMP
1954 nsresult
1955 nsImageFrame::GetFrameName(nsAString& aResult) const
1957 return MakeFrameName(NS_LITERAL_STRING("ImageFrame"), aResult);
1960 void
1961 nsImageFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const
1963 nsCString str;
1964 ListGeneric(str, aPrefix, aFlags);
1966 // output the img src url
1967 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
1968 if (imageLoader) {
1969 nsCOMPtr<imgIRequest> currentRequest;
1970 imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
1971 getter_AddRefs(currentRequest));
1972 if (currentRequest) {
1973 nsCOMPtr<nsIURI> uri;
1974 currentRequest->GetURI(getter_AddRefs(uri));
1975 nsAutoCString uristr;
1976 uri->GetAsciiSpec(uristr);
1977 str += nsPrintfCString(" [src=%s]", uristr.get());
1980 fprintf_stderr(out, "%s\n", str.get());
1982 #endif
1984 nsIFrame::LogicalSides
1985 nsImageFrame::GetLogicalSkipSides(const nsHTMLReflowState* aReflowState) const
1987 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
1988 NS_STYLE_BOX_DECORATION_BREAK_CLONE)) {
1989 return LogicalSides();
1991 LogicalSides skip;
1992 if (nullptr != GetPrevInFlow()) {
1993 skip |= eLogicalSideBitsBStart;
1995 if (nullptr != GetNextInFlow()) {
1996 skip |= eLogicalSideBitsBEnd;
1998 return skip;
2001 nsresult
2002 nsImageFrame::GetIntrinsicImageSize(nsSize& aSize)
2004 if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord &&
2005 mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord) {
2006 aSize.SizeTo(mIntrinsicSize.width.GetCoordValue(),
2007 mIntrinsicSize.height.GetCoordValue());
2008 return NS_OK;
2011 return NS_ERROR_FAILURE;
2014 nsresult
2015 nsImageFrame::LoadIcon(const nsAString& aSpec,
2016 nsPresContext *aPresContext,
2017 imgRequestProxy** aRequest)
2019 nsresult rv = NS_OK;
2020 NS_PRECONDITION(!aSpec.IsEmpty(), "What happened??");
2022 if (!sIOService) {
2023 rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService);
2024 NS_ENSURE_SUCCESS(rv, rv);
2027 nsCOMPtr<nsIURI> realURI;
2028 SpecToURI(aSpec, sIOService, getter_AddRefs(realURI));
2030 nsRefPtr<imgLoader> il =
2031 nsContentUtils::GetImgLoaderForDocument(aPresContext->Document());
2033 nsCOMPtr<nsILoadGroup> loadGroup;
2034 GetLoadGroup(aPresContext, getter_AddRefs(loadGroup));
2036 // For icon loads, we don't need to merge with the loadgroup flags
2037 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
2038 nsContentPolicyType contentPolicyType = nsIContentPolicy::TYPE_IMAGE;
2040 return il->LoadImage(realURI, /* icon URI */
2041 nullptr, /* initial document URI; this is only
2042 relevant for cookies, so does not
2043 apply to icons. */
2044 nullptr, /* referrer (not relevant for icons) */
2045 mozilla::net::RP_Default,
2046 nullptr, /* principal (not relevant for icons) */
2047 loadGroup,
2048 gIconLoad,
2049 nullptr, /* Not associated with any particular document */
2050 loadFlags,
2051 nullptr,
2052 contentPolicyType,
2053 EmptyString(),
2054 aRequest);
2057 void
2058 nsImageFrame::GetDocumentCharacterSet(nsACString& aCharset) const
2060 if (mContent) {
2061 NS_ASSERTION(mContent->GetComposedDoc(),
2062 "Frame still alive after content removed from document!");
2063 aCharset = mContent->GetComposedDoc()->GetDocumentCharacterSet();
2067 void
2068 nsImageFrame::SpecToURI(const nsAString& aSpec, nsIIOService *aIOService,
2069 nsIURI **aURI)
2071 nsCOMPtr<nsIURI> baseURI;
2072 if (mContent) {
2073 baseURI = mContent->GetBaseURI();
2075 nsAutoCString charset;
2076 GetDocumentCharacterSet(charset);
2077 NS_NewURI(aURI, aSpec,
2078 charset.IsEmpty() ? nullptr : charset.get(),
2079 baseURI, aIOService);
2082 void
2083 nsImageFrame::GetLoadGroup(nsPresContext *aPresContext, nsILoadGroup **aLoadGroup)
2085 if (!aPresContext)
2086 return;
2088 NS_PRECONDITION(nullptr != aLoadGroup, "null OUT parameter pointer");
2090 nsIPresShell *shell = aPresContext->GetPresShell();
2092 if (!shell)
2093 return;
2095 nsIDocument *doc = shell->GetDocument();
2096 if (!doc)
2097 return;
2099 *aLoadGroup = doc->GetDocumentLoadGroup().take();
2102 nsresult nsImageFrame::LoadIcons(nsPresContext *aPresContext)
2104 NS_ASSERTION(!gIconLoad, "called LoadIcons twice");
2106 NS_NAMED_LITERAL_STRING(loadingSrc,"resource://gre-resources/loading-image.png");
2107 NS_NAMED_LITERAL_STRING(brokenSrc,"resource://gre-resources/broken-image.png");
2109 gIconLoad = new IconLoad();
2110 NS_ADDREF(gIconLoad);
2112 nsresult rv;
2113 // create a loader and load the images
2114 rv = LoadIcon(loadingSrc,
2115 aPresContext,
2116 getter_AddRefs(gIconLoad->mLoadingImage));
2117 if (NS_FAILED(rv)) {
2118 return rv;
2120 gIconLoad->mLoadingImage->RequestDecode();
2122 rv = LoadIcon(brokenSrc,
2123 aPresContext,
2124 getter_AddRefs(gIconLoad->mBrokenImage));
2125 if (NS_FAILED(rv)) {
2126 return rv;
2128 gIconLoad->mBrokenImage->RequestDecode();
2130 return rv;
2133 NS_IMPL_ISUPPORTS(nsImageFrame::IconLoad, nsIObserver,
2134 imgINotificationObserver)
2136 static const char* kIconLoadPrefs[] = {
2137 "browser.display.force_inline_alttext",
2138 "browser.display.show_image_placeholders",
2139 nullptr
2142 nsImageFrame::IconLoad::IconLoad()
2144 // register observers
2145 Preferences::AddStrongObservers(this, kIconLoadPrefs);
2146 GetPrefs();
2149 void
2150 nsImageFrame::IconLoad::Shutdown()
2152 Preferences::RemoveObservers(this, kIconLoadPrefs);
2153 // in case the pref service releases us later
2154 if (mLoadingImage) {
2155 mLoadingImage->CancelAndForgetObserver(NS_ERROR_FAILURE);
2156 mLoadingImage = nullptr;
2158 if (mBrokenImage) {
2159 mBrokenImage->CancelAndForgetObserver(NS_ERROR_FAILURE);
2160 mBrokenImage = nullptr;
2164 NS_IMETHODIMP
2165 nsImageFrame::IconLoad::Observe(nsISupports *aSubject, const char* aTopic,
2166 const char16_t* aData)
2168 NS_ASSERTION(!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID),
2169 "wrong topic");
2170 #ifdef DEBUG
2171 // assert |aData| is one of our prefs.
2172 for (uint32_t i = 0; i < ArrayLength(kIconLoadPrefs) ||
2173 (NS_NOTREACHED("wrong pref"), false); ++i)
2174 if (NS_ConvertASCIItoUTF16(kIconLoadPrefs[i]) == nsDependentString(aData))
2175 break;
2176 #endif
2178 GetPrefs();
2179 return NS_OK;
2182 void nsImageFrame::IconLoad::GetPrefs()
2184 mPrefForceInlineAltText =
2185 Preferences::GetBool("browser.display.force_inline_alttext");
2187 mPrefShowPlaceholders =
2188 Preferences::GetBool("browser.display.show_image_placeholders", true);
2191 NS_IMETHODIMP
2192 nsImageFrame::IconLoad::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
2194 if (aType != imgINotificationObserver::LOAD_COMPLETE &&
2195 aType != imgINotificationObserver::FRAME_UPDATE) {
2196 return NS_OK;
2199 nsTObserverArray<nsImageFrame*>::ForwardIterator iter(mIconObservers);
2200 nsImageFrame *frame;
2201 while (iter.HasMore()) {
2202 frame = iter.GetNext();
2203 frame->InvalidateFrame();
2206 return NS_OK;
2209 NS_IMPL_ISUPPORTS(nsImageListener, imgINotificationObserver)
2211 nsImageListener::nsImageListener(nsImageFrame *aFrame) :
2212 mFrame(aFrame)
2216 nsImageListener::~nsImageListener()
2220 NS_IMETHODIMP
2221 nsImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
2223 if (!mFrame)
2224 return NS_ERROR_FAILURE;
2226 return mFrame->Notify(aRequest, aType, aData);
2229 static bool
2230 IsInAutoWidthTableCellForQuirk(nsIFrame *aFrame)
2232 if (eCompatibility_NavQuirks != aFrame->PresContext()->CompatibilityMode())
2233 return false;
2234 // Check if the parent of the closest nsBlockFrame has auto width.
2235 nsBlockFrame *ancestor = nsLayoutUtils::FindNearestBlockAncestor(aFrame);
2236 if (ancestor->StyleContext()->GetPseudo() == nsCSSAnonBoxes::cellContent) {
2237 // Assume direct parent is a table cell frame.
2238 nsFrame *grandAncestor = static_cast<nsFrame*>(ancestor->GetParent());
2239 return grandAncestor &&
2240 grandAncestor->StylePosition()->mWidth.GetUnit() == eStyleUnit_Auto;
2242 return false;
2245 /* virtual */ void
2246 nsImageFrame::AddInlineMinISize(nsRenderingContext *aRenderingContext,
2247 nsIFrame::InlineMinISizeData *aData)
2250 NS_ASSERTION(GetParent(), "Must have a parent if we get here!");
2252 nsIFrame* parent = GetParent();
2253 bool canBreak =
2254 !CanContinueTextRun() &&
2255 parent->StyleText()->WhiteSpaceCanWrap(parent) &&
2256 !IsInAutoWidthTableCellForQuirk(this);
2258 if (canBreak)
2259 aData->OptionallyBreak(aRenderingContext);
2261 aData->trailingWhitespace = 0;
2262 aData->skipWhitespace = false;
2263 aData->trailingTextFrame = nullptr;
2264 aData->currentLine += nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
2265 this, nsLayoutUtils::MIN_ISIZE);
2266 aData->atStartOfLine = false;
2268 if (canBreak)
2269 aData->OptionallyBreak(aRenderingContext);