gecko-dev/layout/generic/nsImageFrame.cpp

2448 lines
80 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* rendering object for replaced elements with image data */
#include "nsImageFrame.h"
#include "gfx2DGlue.h"
#include "gfxUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/EventStates.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Helpers.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/MouseEvents.h"
#include "nsCOMPtr.h"
#include "nsFontMetrics.h"
#include "nsIImageLoadingContent.h"
#include "nsString.h"
#include "nsPrintfCString.h"
#include "nsPresContext.h"
#include "nsRenderingContext.h"
#include "nsIPresShell.h"
#include "nsGkAtoms.h"
#include "nsIDocument.h"
#include "nsContentUtils.h"
#include "nsCSSAnonBoxes.h"
#include "nsStyleContext.h"
#include "nsStyleConsts.h"
#include "nsStyleCoord.h"
#include "nsStyleUtil.h"
#include "nsTransform2D.h"
#include "nsImageMap.h"
#include "nsIIOService.h"
#include "nsILoadGroup.h"
#include "nsISupportsPriority.h"
#include "nsNetUtil.h"
#include "nsNetCID.h"
#include "nsCSSRendering.h"
#include "nsIDOMHTMLAnchorElement.h"
#include "nsNameSpaceManager.h"
#include <algorithm>
#ifdef ACCESSIBILITY
#include "nsAccessibilityService.h"
#endif
#include "nsIDOMNode.h"
#include "nsLayoutUtils.h"
#include "nsDisplayList.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "FrameLayerBuilder.h"
#include "nsISelectionController.h"
#include "nsISelection.h"
#include "imgIContainer.h"
#include "imgLoader.h"
#include "imgRequestProxy.h"
#include "nsCSSFrameConstructor.h"
#include "nsIDOMRange.h"
#include "nsError.h"
#include "nsBidiUtils.h"
#include "nsBidiPresUtils.h"
#include "gfxRect.h"
#include "ImageLayers.h"
#include "ImageContainer.h"
#include "nsStyleSet.h"
#include "nsBlockFrame.h"
#include "nsStyleStructInlines.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/Link.h"
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::gfx;
using namespace mozilla::image;
using namespace mozilla::layers;
// sizes (pixels) for image icon, padding and border frame
#define ICON_SIZE (16)
#define ICON_PADDING (3)
#define ALT_BORDER_WIDTH (1)
//we must add hooks soon
#define IMAGE_EDITOR_CHECK 1
// Default alignment value (so we can tell an unset value from a set value)
#define ALIGN_UNSET uint8_t(-1)
// static icon information
nsImageFrame::IconLoad* nsImageFrame::gIconLoad = nullptr;
// cached IO service for loading icons
nsIIOService* nsImageFrame::sIOService;
// test if the width and height are fixed, looking at the style data
// This is used by nsImageFrame::ShouldCreateImageFrameFor and should
// not be used for layout decisions.
static bool HaveSpecifiedSize(const nsStylePosition* aStylePosition)
{
// check the width and height values in the reflow state's style struct
// - if width and height are specified as either coord or percentage, then
// the size of the image frame is constrained
return aStylePosition->mWidth.IsCoordPercentCalcUnit() &&
aStylePosition->mHeight.IsCoordPercentCalcUnit();
}
// Decide whether we can optimize away reflows that result from the
// image's intrinsic size changing.
inline bool HaveFixedSize(const nsHTMLReflowState& aReflowState)
{
NS_ASSERTION(aReflowState.mStylePosition, "crappy reflowState - null stylePosition");
// Don't try to make this optimization when an image has percentages
// in its 'width' or 'height'. The percentages might be treated like
// auto (especially for intrinsic width calculations and for heights).
return aReflowState.mStylePosition->mHeight.ConvertsToLength() &&
aReflowState.mStylePosition->mWidth.ConvertsToLength();
}
nsIFrame*
NS_NewImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
{
return new (aPresShell) nsImageFrame(aContext);
}
NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame)
nsImageFrame::nsImageFrame(nsStyleContext* aContext) :
ImageFrameSuper(aContext),
mComputedSize(0, 0),
mIntrinsicRatio(0, 0),
mDisplayingIcon(false),
mFirstFrameComplete(false),
mReflowCallbackPosted(false),
mForceSyncDecoding(false)
{
// We assume our size is not constrained and we haven't gotten an
// initial reflow yet, so don't touch those flags.
mIntrinsicSize.width.SetCoordValue(0);
mIntrinsicSize.height.SetCoordValue(0);
}
nsImageFrame::~nsImageFrame()
{
}
NS_QUERYFRAME_HEAD(nsImageFrame)
NS_QUERYFRAME_ENTRY(nsImageFrame)
NS_QUERYFRAME_TAIL_INHERITING(ImageFrameSuper)
#ifdef ACCESSIBILITY
a11y::AccType
nsImageFrame::AccessibleType()
{
// Don't use GetImageMap() to avoid reentrancy into accessibility.
if (HasImageMap()) {
return a11y::eHTMLImageMapType;
}
return a11y::eImageType;
}
#endif
void
nsImageFrame::DisconnectMap()
{
if (mImageMap) {
mImageMap->Destroy();
mImageMap = nullptr;
#ifdef ACCESSIBILITY
nsAccessibilityService* accService = GetAccService();
if (accService) {
accService->RecreateAccessible(PresContext()->PresShell(), mContent);
}
#endif
}
}
void
nsImageFrame::DestroyFrom(nsIFrame* aDestructRoot)
{
if (mReflowCallbackPosted) {
PresContext()->PresShell()->CancelReflowCallback(this);
mReflowCallbackPosted = false;
}
// Tell our image map, if there is one, to clean up
// This causes the nsImageMap to unregister itself as
// a DOM listener.
DisconnectMap();
// set the frame to null so we don't send messages to a dead object.
if (mListener) {
nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
if (imageLoader) {
// Notify our image loading content that we are going away so it can
// deregister with our refresh driver.
imageLoader->FrameDestroyed(this);
imageLoader->RemoveObserver(mListener);
}
reinterpret_cast<nsImageListener*>(mListener.get())->SetFrame(nullptr);
}
mListener = nullptr;
// If we were displaying an icon, take ourselves off the list
if (mDisplayingIcon)
gIconLoad->RemoveIconObserver(this);
nsSplittableFrame::DestroyFrom(aDestructRoot);
}
void
nsImageFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
{
ImageFrameSuper::DidSetStyleContext(aOldStyleContext);
if (!mImage) {
// We'll pick this change up whenever we do get an image.
return;
}
nsStyleImageOrientation newOrientation = StyleVisibility()->mImageOrientation;
// We need to update our orientation either if we had no style context before
// because this is the first time it's been set, or if the image-orientation
// property changed from its previous value.
bool shouldUpdateOrientation =
!aOldStyleContext ||
aOldStyleContext->StyleVisibility()->mImageOrientation != newOrientation;
if (shouldUpdateOrientation) {
nsCOMPtr<imgIContainer> image(mImage->Unwrap());
mImage = nsLayoutUtils::OrientImage(image, newOrientation);
UpdateIntrinsicSize(mImage);
UpdateIntrinsicRatio(mImage);
}
}
void
nsImageFrame::Init(nsIContent* aContent,
nsContainerFrame* aParent,
nsIFrame* aPrevInFlow)
{
nsSplittableFrame::Init(aContent, aParent, aPrevInFlow);
mListener = new nsImageListener(this);
nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aContent);
if (!imageLoader) {
NS_RUNTIMEABORT("Why do we have an nsImageFrame here at all?");
}
imageLoader->AddObserver(mListener);
nsPresContext *aPresContext = PresContext();
if (!gIconLoad)
LoadIcons(aPresContext);
// We have a PresContext now, so we need to notify the image content node
// that it can register images.
imageLoader->FrameCreated(this);
// Give image loads associated with an image frame a small priority boost!
nsCOMPtr<imgIRequest> currentRequest;
imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
getter_AddRefs(currentRequest));
nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(currentRequest);
if (p)
p->AdjustPriority(-1);
}
bool
nsImageFrame::UpdateIntrinsicSize(imgIContainer* aImage)
{
NS_PRECONDITION(aImage, "null image");
if (!aImage)
return false;
IntrinsicSize oldIntrinsicSize = mIntrinsicSize;
mIntrinsicSize = IntrinsicSize();
// Set intrinsic size to match aImage's reported intrinsic width & height.
nsSize intrinsicSize;
if (NS_SUCCEEDED(aImage->GetIntrinsicSize(&intrinsicSize))) {
// If the image has no intrinsic width, intrinsicSize.width will be -1, and
// we can leave mIntrinsicSize.width at its default value of eStyleUnit_None.
// Otherwise we use intrinsicSize.width. Height works the same way.
if (intrinsicSize.width != -1)
mIntrinsicSize.width.SetCoordValue(intrinsicSize.width);
if (intrinsicSize.height != -1)
mIntrinsicSize.height.SetCoordValue(intrinsicSize.height);
} else {
// Failure means that the image hasn't loaded enough to report a result. We
// treat this case as if the image's intrinsic size was 0x0.
mIntrinsicSize.width.SetCoordValue(0);
mIntrinsicSize.height.SetCoordValue(0);
}
return mIntrinsicSize != oldIntrinsicSize;
}
bool
nsImageFrame::UpdateIntrinsicRatio(imgIContainer* aImage)
{
NS_PRECONDITION(aImage, "null image");
if (!aImage)
return false;
nsSize oldIntrinsicRatio = mIntrinsicRatio;
// Set intrinsic ratio to match aImage's reported intrinsic ratio.
if (NS_FAILED(aImage->GetIntrinsicRatio(&mIntrinsicRatio)))
mIntrinsicRatio.SizeTo(0, 0);
return mIntrinsicRatio != oldIntrinsicRatio;
}
bool
nsImageFrame::GetSourceToDestTransform(nsTransform2D& aTransform)
{
// First, figure out destRect (the rect we're rendering into).
// NOTE: We use mComputedSize instead of just GetInnerArea()'s own size here,
// because GetInnerArea() might be smaller if we're fragmented, whereas
// mComputedSize has our full content-box size (which we need for
// ComputeObjectDestRect to work correctly).
nsRect constraintRect(GetInnerArea().TopLeft(), mComputedSize);
constraintRect.y -= GetContinuationOffset();
nsRect destRect = nsLayoutUtils::ComputeObjectDestRect(constraintRect,
mIntrinsicSize,
mIntrinsicRatio,
StylePosition());
// Set the translation components, based on destRect
// XXXbz does this introduce rounding errors because of the cast to
// float? Should we just manually add that stuff in every time
// instead?
aTransform.SetToTranslate(float(destRect.x),
float(destRect.y));
// Set the scale factors, based on destRect and intrinsic size.
if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord &&
mIntrinsicSize.width.GetCoordValue() != 0 &&
mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord &&
mIntrinsicSize.height.GetCoordValue() != 0 &&
mIntrinsicSize.width.GetCoordValue() != destRect.width &&
mIntrinsicSize.height.GetCoordValue() != destRect.height) {
aTransform.SetScale(float(destRect.width) /
float(mIntrinsicSize.width.GetCoordValue()),
float(destRect.height) /
float(mIntrinsicSize.height.GetCoordValue()));
return true;
}
return false;
}
// This function checks whether the given request is the current request for our
// mContent.
bool
nsImageFrame::IsPendingLoad(imgIRequest* aRequest) const
{
// Default to pending load in case of errors
nsCOMPtr<nsIImageLoadingContent> imageLoader(do_QueryInterface(mContent));
NS_ASSERTION(imageLoader, "No image loading content?");
int32_t requestType = nsIImageLoadingContent::UNKNOWN_REQUEST;
imageLoader->GetRequestType(aRequest, &requestType);
return requestType != nsIImageLoadingContent::CURRENT_REQUEST;
}
nsRect
nsImageFrame::SourceRectToDest(const nsIntRect& aRect)
{
// When scaling the image, row N of the source image may (depending on
// the scaling function) be used to draw any row in the destination image
// between floor(F * (N-1)) and ceil(F * (N+1)), where F is the
// floating-point scaling factor. The same holds true for columns.
// So, we start by computing that bound without the floor and ceiling.
nsRect r(nsPresContext::CSSPixelsToAppUnits(aRect.x - 1),
nsPresContext::CSSPixelsToAppUnits(aRect.y - 1),
nsPresContext::CSSPixelsToAppUnits(aRect.width + 2),
nsPresContext::CSSPixelsToAppUnits(aRect.height + 2));
nsTransform2D sourceToDest;
if (!GetSourceToDestTransform(sourceToDest)) {
// Failed to generate transform matrix. Return our whole inner area,
// to be on the safe side (since this method is used for generating
// invalidation rects).
return GetInnerArea();
}
sourceToDest.TransformCoord(&r.x, &r.y, &r.width, &r.height);
// Now, round the edges out to the pixel boundary.
nscoord scale = nsPresContext::CSSPixelsToAppUnits(1);
nscoord right = r.x + r.width;
nscoord bottom = r.y + r.height;
r.x -= (scale + (r.x % scale)) % scale;
r.y -= (scale + (r.y % scale)) % scale;
r.width = right + ((scale - (right % scale)) % scale) - r.x;
r.height = bottom + ((scale - (bottom % scale)) % scale) - r.y;
return r;
}
// Note that we treat NS_EVENT_STATE_SUPPRESSED images as "OK". This means
// that we'll construct image frames for them as needed if their display is
// toggled from "none" (though we won't paint them, unless their visibility
// is changed too).
#define BAD_STATES (NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_USERDISABLED | \
NS_EVENT_STATE_LOADING)
// This is a macro so that we don't evaluate the boolean last arg
// unless we have to; it can be expensive
#define IMAGE_OK(_state, _loadingOK) \
(!(_state).HasAtLeastOneOfStates(BAD_STATES) || \
(!(_state).HasAtLeastOneOfStates(NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_USERDISABLED) && \
(_state).HasState(NS_EVENT_STATE_LOADING) && (_loadingOK)))
/* static */
bool
nsImageFrame::ShouldCreateImageFrameFor(Element* aElement,
nsStyleContext* aStyleContext)
{
EventStates state = aElement->State();
if (IMAGE_OK(state,
HaveSpecifiedSize(aStyleContext->StylePosition()))) {
// Image is fine; do the image frame thing
return true;
}
// Check if we want to use a placeholder box with an icon or just
// let the presShell make us into inline text. Decide as follows:
//
// - if our special "force icons" style is set, show an icon
// - else if our "do not show placeholders" pref is set, skip the icon
// - else:
// - if there is a src attribute, there is no alt attribute,
// and this is not an <object> (which could not possibly have
// such an attribute), show an icon.
// - if QuirksMode, and the IMG has a size show an icon.
// - otherwise, skip the icon
bool useSizedBox;
if (aStyleContext->StyleUIReset()->mForceBrokenImageIcon) {
useSizedBox = true;
}
else if (gIconLoad && gIconLoad->mPrefForceInlineAltText) {
useSizedBox = false;
}
else if (aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::src) &&
!aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::alt) &&
!aElement->IsHTMLElement(nsGkAtoms::object) &&
!aElement->IsHTMLElement(nsGkAtoms::input)) {
// Use a sized box if we have no alt text. This means no alt attribute
// and the node is not an object or an input (since those always have alt
// text).
useSizedBox = true;
}
else if (aStyleContext->PresContext()->CompatibilityMode() !=
eCompatibility_NavQuirks) {
useSizedBox = false;
}
else {
// check whether we have specified size
useSizedBox = HaveSpecifiedSize(aStyleContext->StylePosition());
}
return useSizedBox;
}
nsresult
nsImageFrame::Notify(imgIRequest* aRequest,
int32_t aType,
const nsIntRect* aRect)
{
if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
nsCOMPtr<imgIContainer> image;
aRequest->GetImage(getter_AddRefs(image));
return OnSizeAvailable(aRequest, image);
}
if (aType == imgINotificationObserver::FRAME_UPDATE) {
return OnFrameUpdate(aRequest, aRect);
}
if (aType == imgINotificationObserver::FRAME_COMPLETE) {
mFirstFrameComplete = true;
}
if (aType == imgINotificationObserver::LOAD_COMPLETE) {
uint32_t imgStatus;
aRequest->GetImageStatus(&imgStatus);
nsresult status =
imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
return OnLoadComplete(aRequest, status);
}
return NS_OK;
}
static bool
SizeIsAvailable(imgIRequest* aRequest)
{
if (!aRequest)
return false;
uint32_t imageStatus = 0;
nsresult rv = aRequest->GetImageStatus(&imageStatus);
return NS_SUCCEEDED(rv) && (imageStatus & imgIRequest::STATUS_SIZE_AVAILABLE);
}
nsresult
nsImageFrame::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage)
{
if (!aImage) return NS_ERROR_INVALID_ARG;
/* Get requested animation policy from the pres context:
* normal = 0
* one frame = 1
* one loop = 2
*/
nsPresContext *presContext = PresContext();
aImage->SetAnimationMode(presContext->ImageAnimationMode());
if (IsPendingLoad(aRequest)) {
// We don't care
return NS_OK;
}
bool intrinsicSizeChanged = false;
if (SizeIsAvailable(aRequest)) {
// This is valid and for the current request, so update our stored image
// container, orienting according to our style.
mImage = nsLayoutUtils::OrientImage(aImage, StyleVisibility()->mImageOrientation);
intrinsicSizeChanged = UpdateIntrinsicSize(mImage);
intrinsicSizeChanged = UpdateIntrinsicRatio(mImage) || intrinsicSizeChanged;
} else {
// We no longer have a valid image, so release our stored image container.
mImage = nullptr;
// Have to size to 0,0 so that GetDesiredSize recalculates the size.
mIntrinsicSize.width.SetCoordValue(0);
mIntrinsicSize.height.SetCoordValue(0);
mIntrinsicRatio.SizeTo(0, 0);
intrinsicSizeChanged = true;
}
if (intrinsicSizeChanged && (mState & IMAGE_GOTINITIALREFLOW)) {
// Now we need to reflow if we have an unconstrained size and have
// already gotten the initial reflow
if (!(mState & IMAGE_SIZECONSTRAINED)) {
nsIPresShell *presShell = presContext->GetPresShell();
NS_ASSERTION(presShell, "No PresShell.");
if (presShell) {
presShell->FrameNeedsReflow(this, nsIPresShell::eStyleChange,
NS_FRAME_IS_DIRTY);
}
} else {
// We've already gotten the initial reflow, and our size hasn't changed,
// so we're ready to request a decode.
MaybeDecodeForPredictedSize();
}
}
return NS_OK;
}
nsresult
nsImageFrame::OnFrameUpdate(imgIRequest* aRequest, const nsIntRect* aRect)
{
NS_ENSURE_ARG_POINTER(aRect);
if (!(mState & IMAGE_GOTINITIALREFLOW)) {
// Don't bother to do anything; we have a reflow coming up!
return NS_OK;
}
if (mFirstFrameComplete && !StyleVisibility()->IsVisible()) {
return NS_OK;
}
if (IsPendingLoad(aRequest)) {
// We don't care
return NS_OK;
}
nsIntRect layerInvalidRect = mImage
? mImage->GetImageSpaceInvalidationRect(*aRect)
: *aRect;
if (layerInvalidRect.IsEqualInterior(GetMaxSizedIntRect())) {
// Invalidate our entire area.
InvalidateSelf(nullptr, nullptr);
return NS_OK;
}
nsRect frameInvalidRect = SourceRectToDest(layerInvalidRect);
InvalidateSelf(&layerInvalidRect, &frameInvalidRect);
return NS_OK;
}
void
nsImageFrame::InvalidateSelf(const nsIntRect* aLayerInvalidRect,
const nsRect* aFrameInvalidRect)
{
InvalidateLayer(nsDisplayItem::TYPE_IMAGE,
aLayerInvalidRect,
aFrameInvalidRect);
if (!mFirstFrameComplete) {
InvalidateLayer(nsDisplayItem::TYPE_ALT_FEEDBACK,
aLayerInvalidRect,
aFrameInvalidRect);
}
}
nsresult
nsImageFrame::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus)
{
// Check what request type we're dealing with
nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
NS_ASSERTION(imageLoader, "Who's notifying us??");
int32_t loadType = nsIImageLoadingContent::UNKNOWN_REQUEST;
imageLoader->GetRequestType(aRequest, &loadType);
if (loadType != nsIImageLoadingContent::CURRENT_REQUEST &&
loadType != nsIImageLoadingContent::PENDING_REQUEST) {
return NS_ERROR_FAILURE;
}
NotifyNewCurrentRequest(aRequest, aStatus);
return NS_OK;
}
void
nsImageFrame::NotifyNewCurrentRequest(imgIRequest *aRequest,
nsresult aStatus)
{
nsCOMPtr<imgIContainer> image;
aRequest->GetImage(getter_AddRefs(image));
NS_ASSERTION(image || NS_FAILED(aStatus), "Successful load with no container?");
// May have to switch sizes here!
bool intrinsicSizeChanged = true;
if (NS_SUCCEEDED(aStatus) && image && SizeIsAvailable(aRequest)) {
// Update our stored image container, orienting according to our style.
mImage = nsLayoutUtils::OrientImage(image, StyleVisibility()->mImageOrientation);
intrinsicSizeChanged = UpdateIntrinsicSize(mImage);
intrinsicSizeChanged = UpdateIntrinsicRatio(mImage) || intrinsicSizeChanged;
} else {
// We no longer have a valid image, so release our stored image container.
mImage = nullptr;
// Have to size to 0,0 so that GetDesiredSize recalculates the size
mIntrinsicSize.width.SetCoordValue(0);
mIntrinsicSize.height.SetCoordValue(0);
mIntrinsicRatio.SizeTo(0, 0);
}
if (mState & IMAGE_GOTINITIALREFLOW) { // do nothing if we haven't gotten the initial reflow yet
if (!(mState & IMAGE_SIZECONSTRAINED) && intrinsicSizeChanged) {
nsIPresShell *presShell = PresContext()->GetPresShell();
if (presShell) {
presShell->FrameNeedsReflow(this, nsIPresShell::eStyleChange,
NS_FRAME_IS_DIRTY);
}
} else {
// We've already gotten the initial reflow, and our size hasn't changed,
// so we're ready to request a decode.
MaybeDecodeForPredictedSize();
}
// Update border+content to account for image change
InvalidateFrame();
}
}
void
nsImageFrame::MaybeDecodeForPredictedSize()
{
// Check that we're ready to decode.
if (!mImage) {
return; // Nothing to do yet.
}
if (mComputedSize.IsEmpty()) {
return; // We won't draw anything, so no point in decoding.
}
nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
MOZ_ASSERT(imageLoader);
if (imageLoader->GetVisibleCount() == 0) {
return; // We're not visible, so don't decode.
}
// OK, we're ready to decode. Compute the scale to the screen...
nsIPresShell* presShell = PresContext()->GetPresShell();
LayoutDeviceToScreenScale2D resolutionToScreen(
presShell->GetCumulativeResolution()
* nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(this));
// ...and this frame's content box...
const nsPoint offset =
GetOffsetToCrossDoc(nsLayoutUtils::GetReferenceFrame(this));
const nsRect frameContentBox = GetInnerArea() + offset;
// ...and our predicted dest rect...
const int32_t factor = PresContext()->AppUnitsPerDevPixel();
const LayoutDeviceRect destRect =
LayoutDeviceRect::FromAppUnits(PredictedDestRect(frameContentBox), factor);
// ...and use them to compute our predicted size in screen pixels.
const ScreenSize predictedScreenSize = destRect.Size() * resolutionToScreen;
const ScreenIntSize predictedScreenIntSize = RoundedToInt(predictedScreenSize);
if (predictedScreenIntSize.IsEmpty()) {
return;
}
// Determine the optimal image size to use.
uint32_t flags = imgIContainer::FLAG_HIGH_QUALITY_SCALING
| imgIContainer::FLAG_ASYNC_NOTIFY;
Filter filter = nsLayoutUtils::GetGraphicsFilterForFrame(this);
gfxSize gfxPredictedScreenSize = gfxSize(predictedScreenIntSize.width,
predictedScreenIntSize.height);
nsIntSize predictedImageSize =
mImage->OptimalImageSizeForDest(gfxPredictedScreenSize,
imgIContainer::FRAME_CURRENT,
filter, flags);
// Request a decode.
mImage->RequestDecodeForSize(predictedImageSize, flags);
}
nsRect
nsImageFrame::PredictedDestRect(const nsRect& aFrameContentBox)
{
// What is the rect painted by the image? It's the image's "dest rect" (the
// rect where a full copy of the image is mapped), clipped to the container's
// content box. So, we intersect those rects.
// Note: To get the "dest rect", we have to provide the "constraint rect"
// (which is the content-box, with the effects of fragmentation undone).
nsRect constraintRect(aFrameContentBox.TopLeft(), mComputedSize);
constraintRect.y -= GetContinuationOffset();
const nsRect destRect =
nsLayoutUtils::ComputeObjectDestRect(constraintRect,
mIntrinsicSize,
mIntrinsicRatio,
StylePosition());
return destRect.Intersect(aFrameContentBox);
}
void
nsImageFrame::EnsureIntrinsicSizeAndRatio()
{
// If mIntrinsicSize.width and height are 0, then we need to update from the
// image container.
if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord &&
mIntrinsicSize.width.GetCoordValue() == 0 &&
mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord &&
mIntrinsicSize.height.GetCoordValue() == 0) {
if (mImage) {
UpdateIntrinsicSize(mImage);
UpdateIntrinsicRatio(mImage);
} else {
// image request is null or image size not known, probably an
// invalid image specified
// - make the image big enough for the icon (it may not be
// used if inline alt expansion is used instead)
if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
nscoord edgeLengthToUse =
nsPresContext::CSSPixelsToAppUnits(
ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH)));
mIntrinsicSize.width.SetCoordValue(edgeLengthToUse);
mIntrinsicSize.height.SetCoordValue(edgeLengthToUse);
mIntrinsicRatio.SizeTo(1, 1);
}
}
}
}
/* virtual */
LogicalSize
nsImageFrame::ComputeSize(nsRenderingContext *aRenderingContext,
WritingMode aWM,
const LogicalSize& aCBSize,
nscoord aAvailableISize,
const LogicalSize& aMargin,
const LogicalSize& aBorder,
const LogicalSize& aPadding,
ComputeSizeFlags aFlags)
{
EnsureIntrinsicSizeAndRatio();
nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
NS_ASSERTION(imageLoader, "No content node??");
mozilla::IntrinsicSize intrinsicSize(mIntrinsicSize);
// XXX(seth): We may sometimes find ourselves in the situation where we have
// mImage, but imageLoader's current request does not have a size yet.
// This can happen when we load an image speculatively from cache, it fails
// to validate, and the new image load hasn't fired SIZE_AVAILABLE yet. In
// this situation we should always use mIntrinsicSize, because
// GetNaturalWidth/Height will return 0, so we check CurrentRequestHasSize()
// below. See bug 1019840. We will fix this in bug 1141395.
// Content may override our default dimensions. This is termed as overriding
// the intrinsic size by the spec, but all other consumers of mIntrinsic*
// values are being used to refer to the real/true size of the image data.
if (imageLoader && imageLoader->CurrentRequestHasSize() && mImage &&
intrinsicSize.width.GetUnit() == eStyleUnit_Coord &&
intrinsicSize.height.GetUnit() == eStyleUnit_Coord) {
uint32_t width;
uint32_t height;
if (NS_SUCCEEDED(imageLoader->GetNaturalWidth(&width)) &&
NS_SUCCEEDED(imageLoader->GetNaturalHeight(&height))) {
nscoord appWidth = nsPresContext::CSSPixelsToAppUnits((int32_t)width);
nscoord appHeight = nsPresContext::CSSPixelsToAppUnits((int32_t)height);
// If this image is rotated, we'll need to transpose the natural
// width/height.
bool coordFlip;
if (StyleVisibility()->mImageOrientation.IsFromImage()) {
coordFlip = mImage->GetOrientation().SwapsWidthAndHeight();
} else {
coordFlip = StyleVisibility()->mImageOrientation.SwapsWidthAndHeight();
}
intrinsicSize.width.SetCoordValue(coordFlip ? appHeight : appWidth);
intrinsicSize.height.SetCoordValue(coordFlip ? appWidth : appHeight);
}
}
return nsLayoutUtils::ComputeSizeWithIntrinsicDimensions(aWM,
aRenderingContext, this,
intrinsicSize, mIntrinsicRatio,
aCBSize,
aMargin,
aBorder,
aPadding);
}
// XXXdholbert This function's clients should probably just be calling
// GetContentRectRelativeToSelf() directly.
nsRect
nsImageFrame::GetInnerArea() const
{
return GetContentRectRelativeToSelf();
}
Element*
nsImageFrame::GetMapElement() const
{
nsAutoString usemap;
if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usemap, usemap)) {
return mContent->OwnerDoc()->FindImageMap(usemap);
}
return nullptr;
}
// get the offset into the content area of the image where aImg starts if it is a continuation.
nscoord
nsImageFrame::GetContinuationOffset() const
{
nscoord offset = 0;
for (nsIFrame *f = GetPrevInFlow(); f; f = f->GetPrevInFlow()) {
offset += f->GetContentRect().height;
}
NS_ASSERTION(offset >= 0, "bogus GetContentRect");
return offset;
}
/* virtual */ nscoord
nsImageFrame::GetMinISize(nsRenderingContext *aRenderingContext)
{
// XXX The caller doesn't account for constraints of the height,
// min-height, and max-height properties.
DebugOnly<nscoord> result;
DISPLAY_MIN_WIDTH(this, result);
EnsureIntrinsicSizeAndRatio();
return mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord ?
mIntrinsicSize.width.GetCoordValue() : 0;
}
/* virtual */ nscoord
nsImageFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
{
// XXX The caller doesn't account for constraints of the height,
// min-height, and max-height properties.
DebugOnly<nscoord> result;
DISPLAY_PREF_WIDTH(this, result);
EnsureIntrinsicSizeAndRatio();
// convert from normal twips to scaled twips (printing...)
return mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord ?
mIntrinsicSize.width.GetCoordValue() : 0;
}
/* virtual */ IntrinsicSize
nsImageFrame::GetIntrinsicSize()
{
return mIntrinsicSize;
}
/* virtual */ nsSize
nsImageFrame::GetIntrinsicRatio()
{
return mIntrinsicRatio;
}
void
nsImageFrame::Reflow(nsPresContext* aPresContext,
nsHTMLReflowMetrics& aMetrics,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus)
{
MarkInReflow();
DO_GLOBAL_REFLOW_COUNT("nsImageFrame");
DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus);
NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
("enter nsImageFrame::Reflow: availSize=%d,%d",
aReflowState.AvailableWidth(), aReflowState.AvailableHeight()));
NS_PRECONDITION(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow");
aStatus = NS_FRAME_COMPLETE;
// see if we have a frozen size (i.e. a fixed width and height)
if (HaveFixedSize(aReflowState)) {
mState |= IMAGE_SIZECONSTRAINED;
} else {
mState &= ~IMAGE_SIZECONSTRAINED;
}
// XXXldb These two bits are almost exact opposites (except in the
// middle of the initial reflow); remove IMAGE_GOTINITIALREFLOW.
if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
mState |= IMAGE_GOTINITIALREFLOW;
}
mComputedSize =
nsSize(aReflowState.ComputedWidth(), aReflowState.ComputedHeight());
aMetrics.Width() = mComputedSize.width;
aMetrics.Height() = mComputedSize.height;
// add borders and padding
aMetrics.Width() += aReflowState.ComputedPhysicalBorderPadding().LeftRight();
aMetrics.Height() += aReflowState.ComputedPhysicalBorderPadding().TopBottom();
if (GetPrevInFlow()) {
aMetrics.Width() = GetPrevInFlow()->GetSize().width;
nscoord y = GetContinuationOffset();
aMetrics.Height() -= y + aReflowState.ComputedPhysicalBorderPadding().top;
aMetrics.Height() = std::max(0, aMetrics.Height());
}
// we have to split images if we are:
// in Paginated mode, we need to have a constrained height, and have a height larger than our available height
uint32_t loadStatus = imgIRequest::STATUS_NONE;
nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
NS_ASSERTION(imageLoader, "No content node??");
if (imageLoader) {
nsCOMPtr<imgIRequest> currentRequest;
imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
getter_AddRefs(currentRequest));
if (currentRequest) {
currentRequest->GetImageStatus(&loadStatus);
}
}
if (aPresContext->IsPaginated() &&
((loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) || (mState & IMAGE_SIZECONSTRAINED)) &&
NS_UNCONSTRAINEDSIZE != aReflowState.AvailableHeight() &&
aMetrics.Height() > aReflowState.AvailableHeight()) {
// our desired height was greater than 0, so to avoid infinite
// splitting, use 1 pixel as the min
aMetrics.Height() = std::max(nsPresContext::CSSPixelsToAppUnits(1), aReflowState.AvailableHeight());
aStatus = NS_FRAME_NOT_COMPLETE;
}
aMetrics.SetOverflowAreasToDesiredBounds();
EventStates contentState = mContent->AsElement()->State();
bool imageOK = IMAGE_OK(contentState, true);
// Determine if the size is available
bool haveSize = false;
if (loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) {
haveSize = true;
}
if (!imageOK || !haveSize) {
nsRect altFeedbackSize(0, 0,
nsPresContext::CSSPixelsToAppUnits(ICON_SIZE+2*(ICON_PADDING+ALT_BORDER_WIDTH)),
nsPresContext::CSSPixelsToAppUnits(ICON_SIZE+2*(ICON_PADDING+ALT_BORDER_WIDTH)));
// We include the altFeedbackSize in our visual overflow, but not in our
// scrollable overflow, since it doesn't really need to be scrolled to
// outside the image.
static_assert(eOverflowType_LENGTH == 2, "Unknown overflow types?");
nsRect& visualOverflow = aMetrics.VisualOverflow();
visualOverflow.UnionRect(visualOverflow, altFeedbackSize);
} else {
// We've just reflowed and we should have an accurate size, so we're ready
// to request a decode.
MaybeDecodeForPredictedSize();
}
FinishAndStoreOverflow(&aMetrics);
if ((GetStateBits() & NS_FRAME_FIRST_REFLOW) && !mReflowCallbackPosted) {
nsIPresShell* shell = PresContext()->PresShell();
mReflowCallbackPosted = true;
shell->PostReflowCallback(this);
}
NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
("exit nsImageFrame::Reflow: size=%d,%d",
aMetrics.Width(), aMetrics.Height()));
NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics);
}
bool
nsImageFrame::ReflowFinished()
{
mReflowCallbackPosted = false;
nsLayoutUtils::UpdateImageVisibilityForFrame(this);
return false;
}
void
nsImageFrame::ReflowCallbackCanceled()
{
mReflowCallbackPosted = false;
}
// Computes the width of the specified string. aMaxWidth specifies the maximum
// width available. Once this limit is reached no more characters are measured.
// The number of characters that fit within the maximum width are returned in
// aMaxFit. NOTE: it is assumed that the fontmetrics have already been selected
// into the rendering context before this is called (for performance). MMP
nscoord
nsImageFrame::MeasureString(const char16_t* aString,
int32_t aLength,
nscoord aMaxWidth,
uint32_t& aMaxFit,
nsRenderingContext& aContext,
nsFontMetrics& aFontMetrics)
{
nscoord totalWidth = 0;
aFontMetrics.SetTextRunRTL(false);
nscoord spaceWidth = aFontMetrics.SpaceWidth();
aMaxFit = 0;
while (aLength > 0) {
// Find the next place we can line break
uint32_t len = aLength;
bool trailingSpace = false;
for (int32_t i = 0; i < aLength; i++) {
if (dom::IsSpaceCharacter(aString[i]) && (i > 0)) {
len = i; // don't include the space when measuring
trailingSpace = true;
break;
}
}
// Measure this chunk of text, and see if it fits
nscoord width =
nsLayoutUtils::AppUnitWidthOfStringBidi(aString, len, this, aFontMetrics,
aContext);
bool fits = (totalWidth + width) <= aMaxWidth;
// If it fits on the line, or it's the first word we've processed then
// include it
if (fits || (0 == totalWidth)) {
// New piece fits
totalWidth += width;
// If there's a trailing space then see if it fits as well
if (trailingSpace) {
if ((totalWidth + spaceWidth) <= aMaxWidth) {
totalWidth += spaceWidth;
} else {
// Space won't fit. Leave it at the end but don't include it in
// the width
fits = false;
}
len++;
}
aMaxFit += len;
aString += len;
aLength -= len;
}
if (!fits) {
break;
}
}
return totalWidth;
}
// Formats the alt-text to fit within the specified rectangle. Breaks lines
// between words if a word would extend past the edge of the rectangle
void
nsImageFrame::DisplayAltText(nsPresContext* aPresContext,
nsRenderingContext& aRenderingContext,
const nsString& aAltText,
const nsRect& aRect)
{
// Set font and color
aRenderingContext.ThebesContext()->
SetColor(Color::FromABGR(StyleColor()->mColor));
nsRefPtr<nsFontMetrics> fm;
nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm),
nsLayoutUtils::FontSizeInflationFor(this));
// Format the text to display within the formatting rect
nscoord maxAscent = fm->MaxAscent();
nscoord maxDescent = fm->MaxDescent();
nscoord lineHeight = fm->MaxHeight(); // line-relative, so an x-coordinate
// length if writing mode is vertical
WritingMode wm = GetWritingMode();
bool isVertical = wm.IsVertical();
fm->SetVertical(isVertical);
fm->SetTextOrientation(StyleVisibility()->mTextOrientation);
// XXX It would be nice if there was a way to have the font metrics tell
// use where to break the text given a maximum width. At a minimum we need
// to be able to get the break character...
const char16_t* str = aAltText.get();
int32_t strLen = aAltText.Length();
nsPoint pt = wm.IsVerticalRL() ? aRect.TopRight() - nsPoint(lineHeight, 0)
: aRect.TopLeft();
nscoord iSize = isVertical ? aRect.height : aRect.width;
if (!aPresContext->BidiEnabled() && HasRTLChars(aAltText)) {
aPresContext->SetBidiEnabled();
}
// Always show the first line, even if we have to clip it below
bool firstLine = true;
while (strLen > 0) {
if (!firstLine) {
// If we've run out of space, break out of the loop
if ((!isVertical && (pt.y + maxDescent) >= aRect.YMost()) ||
(wm.IsVerticalRL() && (pt.x + maxDescent < aRect.x)) ||
(wm.IsVerticalLR() && (pt.x + maxDescent >= aRect.XMost()))) {
break;
}
}
// Determine how much of the text to display on this line
uint32_t maxFit; // number of characters that fit
nscoord strWidth = MeasureString(str, strLen, iSize, maxFit,
aRenderingContext, *fm);
// Display the text
nsresult rv = NS_ERROR_FAILURE;
if (aPresContext->BidiEnabled()) {
nsBidiDirection dir;
nscoord x, y;
if (isVertical) {
x = pt.x + maxDescent;
if (wm.IsBidiLTR()) {
y = aRect.y;
dir = NSBIDI_LTR;
} else {
y = aRect.YMost() - strWidth;
dir = NSBIDI_RTL;
}
} else {
y = pt.y + maxAscent;
if (wm.IsBidiLTR()) {
x = aRect.x;
dir = NSBIDI_LTR;
} else {
x = aRect.XMost() - strWidth;
dir = NSBIDI_RTL;
}
}
rv = nsBidiPresUtils::RenderText(str, maxFit, dir,
aPresContext, aRenderingContext,
aRenderingContext, *fm, x, y);
}
if (NS_FAILED(rv)) {
nsLayoutUtils::DrawUniDirString(str, maxFit,
isVertical
? nsPoint(pt.x + maxDescent, pt.y)
: nsPoint(pt.x, pt.y + maxAscent),
*fm, aRenderingContext);
}
// Move to the next line
str += maxFit;
strLen -= maxFit;
if (wm.IsVerticalRL()) {
pt.x -= lineHeight;
} else if (wm.IsVerticalLR()) {
pt.x += lineHeight;
} else {
pt.y += lineHeight;
}
firstLine = false;
}
}
struct nsRecessedBorder : public nsStyleBorder {
nsRecessedBorder(nscoord aBorderWidth, nsPresContext* aPresContext)
: nsStyleBorder(aPresContext)
{
NS_FOR_CSS_SIDES(side) {
// Note: use SetBorderColor here because we want to make sure
// the "special" flags are unset.
SetBorderColor(side, NS_RGB(0, 0, 0));
mBorder.Side(side) = aBorderWidth;
// Note: use SetBorderStyle here because we want to affect
// mComputedBorder
SetBorderStyle(side, NS_STYLE_BORDER_STYLE_INSET);
}
}
};
class nsDisplayAltFeedback : public nsDisplayItem {
public:
nsDisplayAltFeedback(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
: nsDisplayItem(aBuilder, aFrame) {}
virtual nsDisplayItemGeometry*
AllocateGeometry(nsDisplayListBuilder* aBuilder) override
{
return new nsDisplayItemGenericImageGeometry(this, aBuilder);
}
virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
const nsDisplayItemGeometry* aGeometry,
nsRegion* aInvalidRegion) override
{
auto geometry =
static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
if (aBuilder->ShouldSyncDecodeImages() &&
geometry->ShouldInvalidateToSyncDecodeImages()) {
bool snap;
aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
}
nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
}
virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
bool* aSnap) override
{
*aSnap = false;
return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
}
virtual void Paint(nsDisplayListBuilder* aBuilder,
nsRenderingContext* aCtx) override
{
// Always sync decode, because these icons are UI, and since they're not
// discardable we'll pay the price of sync decoding at most once.
uint32_t flags = imgIContainer::FLAG_SYNC_DECODE;
nsImageFrame* f = static_cast<nsImageFrame*>(mFrame);
DrawResult result =
f->DisplayAltFeedback(*aCtx,
mVisibleRect,
ToReferenceFrame(),
flags);
nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
}
NS_DISPLAY_DECL_NAME("AltFeedback", TYPE_ALT_FEEDBACK)
};
DrawResult
nsImageFrame::DisplayAltFeedback(nsRenderingContext& aRenderingContext,
const nsRect& aDirtyRect,
nsPoint aPt,
uint32_t aFlags)
{
// We should definitely have a gIconLoad here.
MOZ_ASSERT(gIconLoad, "How did we succeed in Init then?");
// Whether we draw the broken or loading icon.
bool isLoading = IMAGE_OK(GetContent()->AsElement()->State(), true);
// Calculate the inner area
nsRect inner = GetInnerArea() + aPt;
// Display a recessed one pixel border
nscoord borderEdgeWidth = nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH);
// if inner area is empty, then make it big enough for at least the icon
if (inner.IsEmpty()){
inner.SizeTo(2*(nsPresContext::CSSPixelsToAppUnits(ICON_SIZE+ICON_PADDING+ALT_BORDER_WIDTH)),
2*(nsPresContext::CSSPixelsToAppUnits(ICON_SIZE+ICON_PADDING+ALT_BORDER_WIDTH)));
}
// Make sure we have enough room to actually render the border within
// our frame bounds
if ((inner.width < 2 * borderEdgeWidth) || (inner.height < 2 * borderEdgeWidth)) {
return DrawResult::SUCCESS;
}
// Paint the border
if (!isLoading || gIconLoad->mPrefShowLoadingPlaceholder) {
nsRecessedBorder recessedBorder(borderEdgeWidth, PresContext());
nsCSSRendering::PaintBorderWithStyleBorder(PresContext(), aRenderingContext,
this, inner, inner,
recessedBorder, mStyleContext);
}
// Adjust the inner rect to account for the one pixel recessed border,
// and a six pixel padding on each edge
inner.Deflate(nsPresContext::CSSPixelsToAppUnits(ICON_PADDING+ALT_BORDER_WIDTH),
nsPresContext::CSSPixelsToAppUnits(ICON_PADDING+ALT_BORDER_WIDTH));
if (inner.IsEmpty()) {
return DrawResult::SUCCESS;
}
DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
gfxContext* gfx = aRenderingContext.ThebesContext();
// Clip so we don't render outside the inner rect
gfx->Save();
gfx->Clip(NSRectToSnappedRect(inner, PresContext()->AppUnitsPerDevPixel(),
*drawTarget));
DrawResult result = DrawResult::NOT_READY;
// Check if we should display image placeholders
if (!gIconLoad->mPrefShowPlaceholders ||
(isLoading && !gIconLoad->mPrefShowLoadingPlaceholder)) {
result = DrawResult::SUCCESS;
} else {
nscoord size = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE);
imgIRequest* request = isLoading
? nsImageFrame::gIconLoad->mLoadingImage
: nsImageFrame::gIconLoad->mBrokenImage;
// If we weren't previously displaying an icon, register ourselves
// as an observer for load and animation updates and flag that we're
// doing so now.
if (request && !mDisplayingIcon) {
gIconLoad->AddIconObserver(this);
mDisplayingIcon = true;
}
WritingMode wm = GetWritingMode();
bool flushRight =
(!wm.IsVertical() && !wm.IsBidiLTR()) || wm.IsVerticalRL();
// If the icon in question is loaded, draw it.
uint32_t imageStatus = 0;
if (request)
request->GetImageStatus(&imageStatus);
if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE) {
nsCOMPtr<imgIContainer> imgCon;
request->GetImage(getter_AddRefs(imgCon));
MOZ_ASSERT(imgCon, "Load complete, but no image container?");
nsRect dest(flushRight ? inner.XMost() - size : inner.x,
inner.y, size, size);
result = nsLayoutUtils::DrawSingleImage(*gfx, PresContext(), imgCon,
nsLayoutUtils::GetGraphicsFilterForFrame(this), dest, aDirtyRect,
nullptr, aFlags);
}
// If we could not draw the icon, just draw some graffiti in the mean time.
if (result == DrawResult::NOT_READY) {
ColorPattern color(ToDeviceColor(Color(1.f, 0.f, 0.f, 1.f)));
nscoord iconXPos = flushRight ? inner.XMost() - size : inner.x;
// stroked rect:
nsRect rect(iconXPos, inner.y, size, size);
Rect devPxRect =
ToRect(nsLayoutUtils::RectToGfxRect(rect, PresContext()->AppUnitsPerDevPixel()));
drawTarget->StrokeRect(devPxRect, color);
// filled circle in bottom right quadrant of stroked rect:
nscoord twoPX = nsPresContext::CSSPixelsToAppUnits(2);
rect = nsRect(iconXPos + size/2, inner.y + size/2,
size/2 - twoPX, size/2 - twoPX);
devPxRect =
ToRect(nsLayoutUtils::RectToGfxRect(rect, PresContext()->AppUnitsPerDevPixel()));
RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder();
AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size());
RefPtr<Path> ellipse = builder->Finish();
drawTarget->Fill(ellipse, color);
}
// Reduce the inner rect by the width of the icon, and leave an
// additional ICON_PADDING pixels for padding
int32_t paddedIconSize =
nsPresContext::CSSPixelsToAppUnits(ICON_SIZE + ICON_PADDING);
if (wm.IsVertical()) {
inner.y += paddedIconSize;
inner.height -= paddedIconSize;
} else {
if (!flushRight) {
inner.x += paddedIconSize;
}
inner.width -= paddedIconSize;
}
}
// If there's still room, display the alt-text
if (!inner.IsEmpty()) {
nsIContent* content = GetContent();
if (content) {
nsXPIDLString altText;
nsCSSFrameConstructor::GetAlternateTextFor(content,
content->NodeInfo()->NameAtom(),
altText);
DisplayAltText(PresContext(), aRenderingContext, altText, inner);
}
}
aRenderingContext.ThebesContext()->Restore();
return result;
}
#ifdef DEBUG
static void PaintDebugImageMap(nsIFrame* aFrame, nsRenderingContext* aCtx,
const nsRect& aDirtyRect, nsPoint aPt)
{
nsImageFrame* f = static_cast<nsImageFrame*>(aFrame);
nsRect inner = f->GetInnerArea() + aPt;
gfxPoint devPixelOffset =
nsLayoutUtils::PointToGfxPoint(inner.TopLeft(),
aFrame->PresContext()->AppUnitsPerDevPixel());
DrawTarget* drawTarget = aCtx->GetDrawTarget();
AutoRestoreTransform autoRestoreTransform(drawTarget);
drawTarget->SetTransform(
drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
f->GetImageMap()->Draw(aFrame, *drawTarget,
ColorPattern(ToDeviceColor(Color(0.f, 0.f, 0.f, 1.f))));
}
#endif
void
nsDisplayImage::Paint(nsDisplayListBuilder* aBuilder,
nsRenderingContext* aCtx) {
uint32_t flags = imgIContainer::FLAG_NONE;
if (aBuilder->ShouldSyncDecodeImages()) {
flags |= imgIContainer::FLAG_SYNC_DECODE;
}
if (aBuilder->IsPaintingToWindow()) {
flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
}
DrawResult result = static_cast<nsImageFrame*>(mFrame)->
PaintImage(*aCtx, ToReferenceFrame(), mVisibleRect, mImage, flags);
nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
}
nsDisplayItemGeometry*
nsDisplayImage::AllocateGeometry(nsDisplayListBuilder* aBuilder)
{
return new nsDisplayItemGenericImageGeometry(this, aBuilder);
}
void
nsDisplayImage::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
const nsDisplayItemGeometry* aGeometry,
nsRegion* aInvalidRegion)
{
auto geometry =
static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
if (aBuilder->ShouldSyncDecodeImages() &&
geometry->ShouldInvalidateToSyncDecodeImages()) {
bool snap;
aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
}
nsDisplayImageContainer::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
}
bool
nsDisplayImage::CanOptimizeToImageLayer(LayerManager* aManager,
nsDisplayListBuilder* aBuilder)
{
uint32_t flags = aBuilder->ShouldSyncDecodeImages()
? imgIContainer::FLAG_SYNC_DECODE
: imgIContainer::FLAG_NONE;
if (!mImage->IsImageContainerAvailable(aManager, flags)) {
return false;
}
int32_t imageWidth;
int32_t imageHeight;
mImage->GetWidth(&imageWidth);
mImage->GetHeight(&imageHeight);
if (imageWidth == 0 || imageHeight == 0) {
NS_ASSERTION(false, "invalid image size");
return false;
}
const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
const LayoutDeviceRect destRect =
LayoutDeviceRect::FromAppUnits(GetDestRect(), factor);
// Calculate the scaling factor for the frame.
const gfxSize scale = gfxSize(destRect.width / imageWidth,
destRect.height / imageHeight);
if (scale.width < 0.2 || scale.height < 0.2) {
// This would look awful as long as we can't use high-quality downscaling
// for image layers (bug 803703), so don't turn this into an image layer.
return false;
}
return true;
}
already_AddRefed<ImageContainer>
nsDisplayImage::GetContainer(LayerManager* aManager,
nsDisplayListBuilder* aBuilder)
{
uint32_t flags = aBuilder->ShouldSyncDecodeImages()
? imgIContainer::FLAG_SYNC_DECODE
: imgIContainer::FLAG_NONE;
return mImage->GetImageContainer(aManager, flags);
}
nsRect
nsDisplayImage::GetDestRect(bool* aSnap)
{
bool snap = true;
const nsRect frameContentBox = GetBounds(&snap);
if (aSnap) {
*aSnap = snap;
}
nsImageFrame* imageFrame = static_cast<nsImageFrame*>(mFrame);
return imageFrame->PredictedDestRect(frameContentBox);
}
LayerState
nsDisplayImage::GetLayerState(nsDisplayListBuilder* aBuilder,
LayerManager* aManager,
const ContainerLayerParameters& aParameters)
{
bool animated = false;
if (!nsLayoutUtils::AnimatedImageLayersEnabled() ||
mImage->GetType() != imgIContainer::TYPE_RASTER ||
NS_FAILED(mImage->GetAnimated(&animated)) ||
!animated) {
if (!aManager->IsCompositingCheap() ||
!nsLayoutUtils::GPUImageScalingEnabled()) {
return LAYER_NONE;
}
}
if (!animated) {
int32_t imageWidth;
int32_t imageHeight;
mImage->GetWidth(&imageWidth);
mImage->GetHeight(&imageHeight);
NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!");
const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
const LayoutDeviceRect destRect =
LayoutDeviceRect::FromAppUnits(GetDestRect(), factor);
const LayerRect destLayerRect = destRect * aParameters.Scale();
// Calculate the scaling factor for the frame.
const gfxSize scale = gfxSize(destLayerRect.width / imageWidth,
destLayerRect.height / imageHeight);
// If we are not scaling at all, no point in separating this into a layer.
if (scale.width == 1.0f && scale.height == 1.0f) {
return LAYER_NONE;
}
// If the target size is pretty small, no point in using a layer.
if (destLayerRect.width * destLayerRect.height < 64 * 64) {
return LAYER_NONE;
}
}
uint32_t flags = aBuilder->ShouldSyncDecodeImages()
? imgIContainer::FLAG_SYNC_DECODE
: imgIContainer::FLAG_NONE;
if (!mImage->IsImageContainerAvailable(aManager, flags)) {
return LAYER_NONE;
}
return LAYER_ACTIVE;
}
/* virtual */ nsRegion
nsDisplayImage::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
bool* aSnap)
{
if (mImage && mImage->IsOpaque()) {
return nsRegion(GetDestRect(aSnap));
}
return nsRegion();
}
already_AddRefed<Layer>
nsDisplayImage::BuildLayer(nsDisplayListBuilder* aBuilder,
LayerManager* aManager,
const ContainerLayerParameters& aParameters)
{
uint32_t flags = aBuilder->ShouldSyncDecodeImages()
? imgIContainer::FLAG_SYNC_DECODE
: imgIContainer::FLAG_NONE;
nsRefPtr<ImageContainer> container =
mImage->GetImageContainer(aManager, flags);
if (!container) {
return nullptr;
}
nsRefPtr<ImageLayer> layer = static_cast<ImageLayer*>
(aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this));
if (!layer) {
layer = aManager->CreateImageLayer();
if (!layer)
return nullptr;
}
layer->SetContainer(container);
ConfigureLayer(layer, aParameters);
return layer.forget();
}
void
nsDisplayImage::ConfigureLayer(ImageLayer* aLayer,
const ContainerLayerParameters& aParameters)
{
aLayer->SetFilter(nsLayoutUtils::GetGraphicsFilterForFrame(mFrame));
int32_t imageWidth;
int32_t imageHeight;
mImage->GetWidth(&imageWidth);
mImage->GetHeight(&imageHeight);
NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!");
if (imageWidth > 0 && imageHeight > 0) {
// We're actually using the ImageContainer. Let our frame know that it
// should consider itself to have painted successfully.
nsDisplayItemGenericImageGeometry::UpdateDrawResult(this,
DrawResult::SUCCESS);
}
const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
const LayoutDeviceRect destRect =
LayoutDeviceRect::FromAppUnits(GetDestRect(), factor);
// XXX(seth): Right now we ignore aParameters.Scale() and
// aParameters.Offset(), because FrameLayerBuilder already applies
// aParameters.Scale() via the layer's post-transform, and
// aParameters.Offset() is always zero.
MOZ_ASSERT(aParameters.Offset() == LayerIntPoint(0,0));
const LayoutDevicePoint p = destRect.TopLeft();
Matrix transform = Matrix::Translation(p.x, p.y);
transform.PreScale(destRect.Width() / imageWidth,
destRect.Height() / imageHeight);
aLayer->SetBaseTransform(gfx::Matrix4x4::From2D(transform));
}
DrawResult
nsImageFrame::PaintImage(nsRenderingContext& aRenderingContext, nsPoint aPt,
const nsRect& aDirtyRect, imgIContainer* aImage,
uint32_t aFlags)
{
DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
// Render the image into our content area (the area inside
// the borders and padding)
NS_ASSERTION(GetInnerArea().width == mComputedSize.width, "bad width");
// NOTE: We use mComputedSize instead of just GetInnerArea()'s own size here,
// because GetInnerArea() might be smaller if we're fragmented, whereas
// mComputedSize has our full content-box size (which we need for
// ComputeObjectDestRect to work correctly).
nsRect constraintRect(aPt + GetInnerArea().TopLeft(), mComputedSize);
constraintRect.y -= GetContinuationOffset();
nsPoint anchorPoint;
nsRect dest = nsLayoutUtils::ComputeObjectDestRect(constraintRect,
mIntrinsicSize,
mIntrinsicRatio,
StylePosition(),
&anchorPoint);
uint32_t flags = aFlags;
if (mForceSyncDecoding) {
flags |= imgIContainer::FLAG_SYNC_DECODE;
}
DrawResult result =
nsLayoutUtils::DrawSingleImage(*aRenderingContext.ThebesContext(),
PresContext(), aImage,
nsLayoutUtils::GetGraphicsFilterForFrame(this), dest, aDirtyRect,
nullptr, flags, &anchorPoint);
nsImageMap* map = GetImageMap();
if (map) {
gfxPoint devPixelOffset =
nsLayoutUtils::PointToGfxPoint(dest.TopLeft(),
PresContext()->AppUnitsPerDevPixel());
AutoRestoreTransform autoRestoreTransform(drawTarget);
drawTarget->SetTransform(
drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
// solid white stroke:
ColorPattern white(ToDeviceColor(Color(1.f, 1.f, 1.f, 1.f)));
map->Draw(this, *drawTarget, white);
// then dashed black stroke over the top:
ColorPattern black(ToDeviceColor(Color(0.f, 0.f, 0.f, 1.f)));
StrokeOptions strokeOptions;
nsLayoutUtils::InitDashPattern(strokeOptions, NS_STYLE_BORDER_STYLE_DOTTED);
map->Draw(this, *drawTarget, black, strokeOptions);
}
return result;
}
void
nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsRect& aDirtyRect,
const nsDisplayListSet& aLists)
{
if (!IsVisibleForPainting(aBuilder))
return;
DisplayBorderBackgroundOutline(aBuilder, aLists);
uint32_t clipFlags =
nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition()) ?
0 : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox
clip(aBuilder, this, clipFlags);
if (mComputedSize.width != 0 && mComputedSize.height != 0) {
nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
NS_ASSERTION(imageLoader, "Not an image loading content?");
nsCOMPtr<imgIRequest> currentRequest;
if (imageLoader) {
imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
getter_AddRefs(currentRequest));
}
EventStates contentState = mContent->AsElement()->State();
bool imageOK = IMAGE_OK(contentState, true);
// XXX(seth): The SizeIsAvailable check here should not be necessary - the
// intention is that a non-null mImage means we have a size, but there is
// currently some code that violates this invariant.
if (!imageOK || !mImage || !SizeIsAvailable(currentRequest)) {
// No image yet, or image load failed. Draw the alt-text and an icon
// indicating the status
aLists.Content()->AppendNewToTop(new (aBuilder)
nsDisplayAltFeedback(aBuilder, this));
// This image is visible (we are being asked to paint it) but it's not
// decoded yet. And we are not going to ask the image to draw, so this
// may be the only chance to tell it that it should decode.
if (currentRequest) {
currentRequest->RequestDecode();
}
} else {
aLists.Content()->AppendNewToTop(new (aBuilder)
nsDisplayImage(aBuilder, this, mImage));
// If we were previously displaying an icon, we're not anymore
if (mDisplayingIcon) {
gIconLoad->RemoveIconObserver(this);
mDisplayingIcon = false;
}
#ifdef DEBUG
if (GetShowFrameBorders() && GetImageMap()) {
aLists.Outlines()->AppendNewToTop(new (aBuilder)
nsDisplayGeneric(aBuilder, this, PaintDebugImageMap, "DebugImageMap",
nsDisplayItem::TYPE_DEBUG_IMAGE_MAP));
}
#endif
}
}
if (ShouldDisplaySelection()) {
DisplaySelectionOverlay(aBuilder, aLists.Content(),
nsISelectionDisplay::DISPLAY_IMAGES);
}
}
bool
nsImageFrame::ShouldDisplaySelection()
{
// XXX what on EARTH is this code for?
nsresult result;
nsPresContext* presContext = PresContext();
int16_t displaySelection = presContext->PresShell()->GetSelectionFlags();
if (!(displaySelection & nsISelectionDisplay::DISPLAY_IMAGES))
return false;//no need to check the blue border, we cannot be drawn selected
//insert hook here for image selection drawing
#if IMAGE_EDITOR_CHECK
//check to see if this frame is in an editor context
//isEditor check. this needs to be changed to have better way to check
if (displaySelection == nsISelectionDisplay::DISPLAY_ALL)
{
nsCOMPtr<nsISelectionController> selCon;
result = GetSelectionController(presContext, getter_AddRefs(selCon));
if (NS_SUCCEEDED(result) && selCon)
{
nsCOMPtr<nsISelection> selection;
result = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
if (NS_SUCCEEDED(result) && selection)
{
int32_t rangeCount;
selection->GetRangeCount(&rangeCount);
if (rangeCount == 1) //if not one then let code drop to nsFrame::Paint
{
nsCOMPtr<nsIContent> parentContent = mContent->GetParent();
if (parentContent)
{
int32_t thisOffset = parentContent->IndexOf(mContent);
nsCOMPtr<nsIDOMNode> parentNode = do_QueryInterface(parentContent);
nsCOMPtr<nsIDOMNode> rangeNode;
int32_t rangeOffset;
nsCOMPtr<nsIDOMRange> range;
selection->GetRangeAt(0,getter_AddRefs(range));
if (range)
{
range->GetStartContainer(getter_AddRefs(rangeNode));
range->GetStartOffset(&rangeOffset);
if (parentNode && rangeNode && (rangeNode == parentNode) && rangeOffset == thisOffset)
{
range->GetEndContainer(getter_AddRefs(rangeNode));
range->GetEndOffset(&rangeOffset);
if ((rangeNode == parentNode) && (rangeOffset == (thisOffset +1))) //+1 since that would mean this whole content is selected only
return false; //do not allow nsFrame do draw any further selection
}
}
}
}
}
}
}
#endif
return true;
}
nsImageMap*
nsImageFrame::GetImageMap()
{
if (!mImageMap) {
nsIContent* map = GetMapElement();
if (map) {
mImageMap = new nsImageMap();
mImageMap->Init(this, map);
}
}
return mImageMap;
}
bool
nsImageFrame::IsServerImageMap()
{
return mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::ismap);
}
// Translate an point that is relative to our frame
// into a localized pixel coordinate that is relative to the
// content area of this frame (inside the border+padding).
void
nsImageFrame::TranslateEventCoords(const nsPoint& aPoint,
nsIntPoint& aResult)
{
nscoord x = aPoint.x;
nscoord y = aPoint.y;
// Subtract out border and padding here so that the coordinates are
// now relative to the content area of this frame.
nsRect inner = GetInnerArea();
x -= inner.x;
y -= inner.y;
aResult.x = nsPresContext::AppUnitsToIntCSSPixels(x);
aResult.y = nsPresContext::AppUnitsToIntCSSPixels(y);
}
bool
nsImageFrame::GetAnchorHREFTargetAndNode(nsIURI** aHref, nsString& aTarget,
nsIContent** aNode)
{
bool status = false;
aTarget.Truncate();
*aHref = nullptr;
*aNode = nullptr;
// Walk up the content tree, looking for an nsIDOMAnchorElement
for (nsIContent* content = mContent->GetParent();
content; content = content->GetParent()) {
nsCOMPtr<dom::Link> link(do_QueryInterface(content));
if (link) {
nsCOMPtr<nsIURI> href = content->GetHrefURI();
if (href) {
href->Clone(aHref);
}
status = (*aHref != nullptr);
nsCOMPtr<nsIDOMHTMLAnchorElement> anchor(do_QueryInterface(content));
if (anchor) {
anchor->GetTarget(aTarget);
}
NS_ADDREF(*aNode = content);
break;
}
}
return status;
}
nsresult
nsImageFrame::GetContentForEvent(WidgetEvent* aEvent,
nsIContent** aContent)
{
NS_ENSURE_ARG_POINTER(aContent);
nsIFrame* f = nsLayoutUtils::GetNonGeneratedAncestor(this);
if (f != this) {
return f->GetContentForEvent(aEvent, aContent);
}
// XXX We need to make this special check for area element's capturing the
// mouse due to bug 135040. Remove it once that's fixed.
nsIContent* capturingContent =
aEvent->HasMouseEventMessage() ? nsIPresShell::GetCapturingContent() :
nullptr;
if (capturingContent && capturingContent->GetPrimaryFrame() == this) {
*aContent = capturingContent;
NS_IF_ADDREF(*aContent);
return NS_OK;
}
nsImageMap* map = GetImageMap();
if (nullptr != map) {
nsIntPoint p;
TranslateEventCoords(
nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this), p);
nsCOMPtr<nsIContent> area = map->GetArea(p.x, p.y);
if (area) {
area.forget(aContent);
return NS_OK;
}
}
*aContent = GetContent();
NS_IF_ADDREF(*aContent);
return NS_OK;
}
// XXX what should clicks on transparent pixels do?
nsresult
nsImageFrame::HandleEvent(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus)
{
NS_ENSURE_ARG_POINTER(aEventStatus);
if ((aEvent->mMessage == eMouseUp &&
aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) ||
aEvent->mMessage == eMouseMove) {
nsImageMap* map = GetImageMap();
bool isServerMap = IsServerImageMap();
if ((nullptr != map) || isServerMap) {
nsIntPoint p;
TranslateEventCoords(
nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this), p);
bool inside = false;
// Even though client-side image map triggering happens
// through content, we need to make sure we're not inside
// (in case we deal with a case of both client-side and
// sever-side on the same image - it happens!)
if (nullptr != map) {
inside = !!map->GetArea(p.x, p.y);
}
if (!inside && isServerMap) {
// Server side image maps use the href in a containing anchor
// element to provide the basis for the destination url.
nsCOMPtr<nsIURI> uri;
nsAutoString target;
nsCOMPtr<nsIContent> anchorNode;
if (GetAnchorHREFTargetAndNode(getter_AddRefs(uri), target,
getter_AddRefs(anchorNode))) {
// XXX if the mouse is over/clicked in the border/padding area
// we should probably just pretend nothing happened. Nav4
// keeps the x,y coordinates positive as we do; IE doesn't
// bother. Both of them send the click through even when the
// mouse is over the border.
if (p.x < 0) p.x = 0;
if (p.y < 0) p.y = 0;
nsAutoCString spec;
uri->GetSpec(spec);
spec += nsPrintfCString("?%d,%d", p.x, p.y);
uri->SetSpec(spec);
bool clicked = false;
if (aEvent->mMessage == eMouseUp) {
*aEventStatus = nsEventStatus_eConsumeDoDefault;
clicked = true;
}
nsContentUtils::TriggerLink(anchorNode, aPresContext, uri, target,
clicked, true, true);
}
}
}
}
return nsSplittableFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
}
nsresult
nsImageFrame::GetCursor(const nsPoint& aPoint,
nsIFrame::Cursor& aCursor)
{
nsImageMap* map = GetImageMap();
if (nullptr != map) {
nsIntPoint p;
TranslateEventCoords(aPoint, p);
nsCOMPtr<nsIContent> area = map->GetArea(p.x, p.y);
if (area) {
// Use the cursor from the style of the *area* element.
// XXX Using the image as the parent style context isn't
// technically correct, but it's probably the right thing to do
// here, since it means that areas on which the cursor isn't
// specified will inherit the style from the image.
nsRefPtr<nsStyleContext> areaStyle =
PresContext()->PresShell()->StyleSet()->
ResolveStyleFor(area->AsElement(), StyleContext());
FillCursorInformationFromStyle(areaStyle->StyleUserInterface(),
aCursor);
if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) {
aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT;
}
return NS_OK;
}
}
return nsFrame::GetCursor(aPoint, aCursor);
}
nsresult
nsImageFrame::AttributeChanged(int32_t aNameSpaceID,
nsIAtom* aAttribute,
int32_t aModType)
{
nsresult rv = nsSplittableFrame::AttributeChanged(aNameSpaceID,
aAttribute, aModType);
if (NS_FAILED(rv)) {
return rv;
}
if (nsGkAtoms::alt == aAttribute)
{
PresContext()->PresShell()->FrameNeedsReflow(this,
nsIPresShell::eStyleChange,
NS_FRAME_IS_DIRTY);
}
return NS_OK;
}
nsIAtom*
nsImageFrame::GetType() const
{
return nsGkAtoms::imageFrame;
}
#ifdef DEBUG_FRAME_DUMP
nsresult
nsImageFrame::GetFrameName(nsAString& aResult) const
{
return MakeFrameName(NS_LITERAL_STRING("ImageFrame"), aResult);
}
void
nsImageFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const
{
nsCString str;
ListGeneric(str, aPrefix, aFlags);
// output the img src url
nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
if (imageLoader) {
nsCOMPtr<imgIRequest> currentRequest;
imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
getter_AddRefs(currentRequest));
if (currentRequest) {
nsCOMPtr<nsIURI> uri;
currentRequest->GetURI(getter_AddRefs(uri));
nsAutoCString uristr;
uri->GetAsciiSpec(uristr);
str += nsPrintfCString(" [src=%s]", uristr.get());
}
}
fprintf_stderr(out, "%s\n", str.get());
}
#endif
nsIFrame::LogicalSides
nsImageFrame::GetLogicalSkipSides(const nsHTMLReflowState* aReflowState) const
{
if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
NS_STYLE_BOX_DECORATION_BREAK_CLONE)) {
return LogicalSides();
}
LogicalSides skip;
if (nullptr != GetPrevInFlow()) {
skip |= eLogicalSideBitsBStart;
}
if (nullptr != GetNextInFlow()) {
skip |= eLogicalSideBitsBEnd;
}
return skip;
}
nsresult
nsImageFrame::GetIntrinsicImageSize(nsSize& aSize)
{
if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord &&
mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord) {
aSize.SizeTo(mIntrinsicSize.width.GetCoordValue(),
mIntrinsicSize.height.GetCoordValue());
return NS_OK;
}
return NS_ERROR_FAILURE;
}
nsresult
nsImageFrame::LoadIcon(const nsAString& aSpec,
nsPresContext *aPresContext,
imgRequestProxy** aRequest)
{
nsresult rv = NS_OK;
NS_PRECONDITION(!aSpec.IsEmpty(), "What happened??");
if (!sIOService) {
rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService);
NS_ENSURE_SUCCESS(rv, rv);
}
nsCOMPtr<nsIURI> realURI;
SpecToURI(aSpec, sIOService, getter_AddRefs(realURI));
nsRefPtr<imgLoader> il =
nsContentUtils::GetImgLoaderForDocument(aPresContext->Document());
nsCOMPtr<nsILoadGroup> loadGroup;
GetLoadGroup(aPresContext, getter_AddRefs(loadGroup));
// For icon loads, we don't need to merge with the loadgroup flags
nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
nsContentPolicyType contentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE;
return il->LoadImage(realURI, /* icon URI */
nullptr, /* initial document URI; this is only
relevant for cookies, so does not
apply to icons. */
nullptr, /* referrer (not relevant for icons) */
mozilla::net::RP_Default,
nullptr, /* principal (not relevant for icons) */
loadGroup,
gIconLoad,
nullptr, /* Not associated with any particular document */
loadFlags,
nullptr,
contentPolicyType,
EmptyString(),
aRequest);
}
void
nsImageFrame::GetDocumentCharacterSet(nsACString& aCharset) const
{
if (mContent) {
NS_ASSERTION(mContent->GetComposedDoc(),
"Frame still alive after content removed from document!");
aCharset = mContent->GetComposedDoc()->GetDocumentCharacterSet();
}
}
void
nsImageFrame::SpecToURI(const nsAString& aSpec, nsIIOService *aIOService,
nsIURI **aURI)
{
nsCOMPtr<nsIURI> baseURI;
if (mContent) {
baseURI = mContent->GetBaseURI();
}
nsAutoCString charset;
GetDocumentCharacterSet(charset);
NS_NewURI(aURI, aSpec,
charset.IsEmpty() ? nullptr : charset.get(),
baseURI, aIOService);
}
void
nsImageFrame::GetLoadGroup(nsPresContext *aPresContext, nsILoadGroup **aLoadGroup)
{
if (!aPresContext)
return;
NS_PRECONDITION(nullptr != aLoadGroup, "null OUT parameter pointer");
nsIPresShell *shell = aPresContext->GetPresShell();
if (!shell)
return;
nsIDocument *doc = shell->GetDocument();
if (!doc)
return;
*aLoadGroup = doc->GetDocumentLoadGroup().take();
}
nsresult nsImageFrame::LoadIcons(nsPresContext *aPresContext)
{
NS_ASSERTION(!gIconLoad, "called LoadIcons twice");
NS_NAMED_LITERAL_STRING(loadingSrc,"resource://gre-resources/loading-image.png");
NS_NAMED_LITERAL_STRING(brokenSrc,"resource://gre-resources/broken-image.png");
gIconLoad = new IconLoad();
NS_ADDREF(gIconLoad);
nsresult rv;
// create a loader and load the images
rv = LoadIcon(loadingSrc,
aPresContext,
getter_AddRefs(gIconLoad->mLoadingImage));
if (NS_FAILED(rv)) {
return rv;
}
gIconLoad->mLoadingImage->RequestDecode();
rv = LoadIcon(brokenSrc,
aPresContext,
getter_AddRefs(gIconLoad->mBrokenImage));
if (NS_FAILED(rv)) {
return rv;
}
gIconLoad->mBrokenImage->RequestDecode();
return rv;
}
NS_IMPL_ISUPPORTS(nsImageFrame::IconLoad, nsIObserver,
imgINotificationObserver)
static const char* kIconLoadPrefs[] = {
"browser.display.force_inline_alttext",
"browser.display.show_image_placeholders",
"browser.display.show_loading_image_placeholder",
nullptr
};
nsImageFrame::IconLoad::IconLoad()
{
// register observers
Preferences::AddStrongObservers(this, kIconLoadPrefs);
GetPrefs();
}
void
nsImageFrame::IconLoad::Shutdown()
{
Preferences::RemoveObservers(this, kIconLoadPrefs);
// in case the pref service releases us later
if (mLoadingImage) {
mLoadingImage->CancelAndForgetObserver(NS_ERROR_FAILURE);
mLoadingImage = nullptr;
}
if (mBrokenImage) {
mBrokenImage->CancelAndForgetObserver(NS_ERROR_FAILURE);
mBrokenImage = nullptr;
}
}
NS_IMETHODIMP
nsImageFrame::IconLoad::Observe(nsISupports *aSubject, const char* aTopic,
const char16_t* aData)
{
NS_ASSERTION(!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID),
"wrong topic");
#ifdef DEBUG
// assert |aData| is one of our prefs.
uint32_t i = 0;
for (; i < ArrayLength(kIconLoadPrefs); ++i) {
if (NS_ConvertASCIItoUTF16(kIconLoadPrefs[i]) == nsDependentString(aData))
break;
}
MOZ_ASSERT(i < ArrayLength(kIconLoadPrefs));
#endif
GetPrefs();
return NS_OK;
}
void nsImageFrame::IconLoad::GetPrefs()
{
mPrefForceInlineAltText =
Preferences::GetBool("browser.display.force_inline_alttext");
mPrefShowPlaceholders =
Preferences::GetBool("browser.display.show_image_placeholders", true);
mPrefShowLoadingPlaceholder =
Preferences::GetBool("browser.display.show_loading_image_placeholder", true);
}
NS_IMETHODIMP
nsImageFrame::IconLoad::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
{
if (aType != imgINotificationObserver::LOAD_COMPLETE &&
aType != imgINotificationObserver::FRAME_UPDATE) {
return NS_OK;
}
nsTObserverArray<nsImageFrame*>::ForwardIterator iter(mIconObservers);
nsImageFrame *frame;
while (iter.HasMore()) {
frame = iter.GetNext();
frame->InvalidateFrame();
}
return NS_OK;
}
NS_IMPL_ISUPPORTS(nsImageListener, imgINotificationObserver)
nsImageListener::nsImageListener(nsImageFrame *aFrame) :
mFrame(aFrame)
{
}
nsImageListener::~nsImageListener()
{
}
NS_IMETHODIMP
nsImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
{
if (!mFrame)
return NS_ERROR_FAILURE;
return mFrame->Notify(aRequest, aType, aData);
}
static bool
IsInAutoWidthTableCellForQuirk(nsIFrame *aFrame)
{
if (eCompatibility_NavQuirks != aFrame->PresContext()->CompatibilityMode())
return false;
// Check if the parent of the closest nsBlockFrame has auto width.
nsBlockFrame *ancestor = nsLayoutUtils::FindNearestBlockAncestor(aFrame);
if (ancestor->StyleContext()->GetPseudo() == nsCSSAnonBoxes::cellContent) {
// Assume direct parent is a table cell frame.
nsFrame *grandAncestor = static_cast<nsFrame*>(ancestor->GetParent());
return grandAncestor &&
grandAncestor->StylePosition()->mWidth.GetUnit() == eStyleUnit_Auto;
}
return false;
}
/* virtual */ void
nsImageFrame::AddInlineMinISize(nsRenderingContext *aRenderingContext,
nsIFrame::InlineMinISizeData *aData)
{
NS_ASSERTION(GetParent(), "Must have a parent if we get here!");
nsIFrame* parent = GetParent();
bool canBreak =
!CanContinueTextRun() &&
parent->StyleText()->WhiteSpaceCanWrap(parent) &&
!IsInAutoWidthTableCellForQuirk(this);
if (canBreak)
aData->OptionallyBreak(aRenderingContext);
aData->trailingWhitespace = 0;
aData->skipWhitespace = false;
aData->trailingTextFrame = nullptr;
aData->currentLine += nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
this, nsLayoutUtils::MIN_ISIZE);
aData->atStartOfLine = false;
if (canBreak)
aData->OptionallyBreak(aRenderingContext);
}