summaryrefslogtreecommitdiffstats
path: root/layout/generic/nsImageFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/generic/nsImageFrame.cpp')
-rw-r--r--layout/generic/nsImageFrame.cpp2443
1 files changed, 2443 insertions, 0 deletions
diff --git a/layout/generic/nsImageFrame.cpp b/layout/generic/nsImageFrame.cpp
new file mode 100644
index 000000000..c64520f2e
--- /dev/null
+++ b/layout/generic/nsImageFrame.cpp
@@ -0,0 +1,2443 @@
+/* -*- 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 "mozilla/Unused.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 "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.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 ReflowInput& aReflowInput)
+{
+ NS_ASSERTION(aReflowInput.mStylePosition, "crappy reflowInput - 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 aReflowInput.mStylePosition->mHeight.ConvertsToLength() &&
+ aReflowInput.mStylePosition->mWidth.ConvertsToLength();
+}
+
+nsIFrame*
+NS_NewImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsImageFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame)
+
+
+nsImageFrame::nsImageFrame(nsStyleContext* aContext) :
+ nsAtomicContainerFrame(aContext),
+ mComputedSize(0, 0),
+ mIntrinsicRatio(0, 0),
+ mDisplayingIcon(false),
+ mFirstFrameComplete(false),
+ mReflowCallbackPosted(false),
+ mForceSyncDecoding(false)
+{
+ EnableVisibilityTracking();
+
+ // 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(nsAtomicContainerFrame)
+
+#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);
+
+ nsAtomicContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+void
+nsImageFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
+{
+ nsAtomicContainerFrame::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)
+{
+ nsAtomicContainerFrame::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 = mPrevImage = 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();
+ }
+
+ mPrevImage = nullptr;
+ }
+
+ 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 = mPrevImage = 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 (intrinsicSizeChanged) {
+ if (!(mState & IMAGE_SIZECONSTRAINED)) {
+ 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();
+ }
+
+ mPrevImage = nullptr;
+ }
+ // 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.
+ }
+
+ if (GetVisibility() != Visibility::APPROXIMATELY_VISIBLE) {
+ 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;
+ SamplingFilter samplingFilter =
+ nsLayoutUtils::GetSamplingFilterForFrame(this);
+ gfxSize gfxPredictedScreenSize = gfxSize(predictedScreenIntSize.width,
+ predictedScreenIntSize.height);
+ nsIntSize predictedImageSize =
+ mImage->OptimalImageSizeForDest(gfxPredictedScreenSize,
+ imgIContainer::FRAME_CURRENT,
+ samplingFilter, flags);
+
+ // Request a decode.
+ mImage->RequestDecodeForSize(predictedImageSize, flags);
+}
+
+nsRect
+nsImageFrame::PredictedDestRect(const nsRect& aFrameContentBox)
+{
+ // 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();
+
+ return nsLayoutUtils::ComputeObjectDestRect(constraintRect,
+ mIntrinsicSize,
+ mIntrinsicRatio,
+ StylePosition());
+}
+
+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
+ if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
+ bool imageBroken = false;
+ // check for broken images. valid null images (eg. img src="") are
+ // not considered broken because they have no image requests
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+ if (imageLoader) {
+ nsCOMPtr<imgIRequest> currentRequest;
+ imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(currentRequest));
+ uint32_t imageStatus;
+ imageBroken =
+ currentRequest &&
+ NS_SUCCEEDED(currentRequest->GetImageStatus(&imageStatus)) &&
+ (imageStatus & imgIRequest::STATUS_ERROR);
+ }
+ // invalid image specified. make the image big enough for the "broken" icon
+ if (imageBroken) {
+ 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 ComputeSizeWithIntrinsicDimensions(aRenderingContext, aWM,
+ intrinsicSize, mIntrinsicRatio,
+ aCBSize, aMargin, aBorder, aPadding,
+ aFlags);
+}
+
+// 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,
+ ReflowOutput& aMetrics,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsImageFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
+ NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
+ ("enter nsImageFrame::Reflow: availSize=%d,%d",
+ aReflowInput.AvailableWidth(), aReflowInput.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(aReflowInput)) {
+ 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(aReflowInput.ComputedWidth(), aReflowInput.ComputedHeight());
+
+ aMetrics.Width() = mComputedSize.width;
+ aMetrics.Height() = mComputedSize.height;
+
+ // add borders and padding
+ aMetrics.Width() += aReflowInput.ComputedPhysicalBorderPadding().LeftRight();
+ aMetrics.Height() += aReflowInput.ComputedPhysicalBorderPadding().TopBottom();
+
+ if (GetPrevInFlow()) {
+ aMetrics.Width() = GetPrevInFlow()->GetSize().width;
+ nscoord y = GetContinuationOffset();
+ aMetrics.Height() -= y + aReflowInput.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 != aReflowInput.AvailableHeight() &&
+ aMetrics.Height() > aReflowInput.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), aReflowInput.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, aReflowInput, aMetrics);
+}
+
+bool
+nsImageFrame::ReflowFinished()
+{
+ mReflowCallbackPosted = false;
+
+ // XXX(seth): We don't need this. The purpose of updating visibility
+ // synchronously is to ensure that animated images start animating
+ // immediately. In the short term, however,
+ // nsImageLoadingContent::OnUnlockedDraw() is enough to ensure that
+ // animations start as soon as the image is painted for the first time, and in
+ // the long term we want to update visibility information from the display
+ // list whenever we paint, so we don't actually need to do this. However, to
+ // avoid behavior changes during the transition from the old image visibility
+ // code, we'll leave it in for now.
+ UpdateVisibilitySynchronously();
+
+ 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));
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(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.GetDrawTarget(),
+ *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) {
+ mBorderColor[side] = StyleComplexColor::FromColor(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());
+
+ // Assert that we're not drawing a border-image here; if we were, we
+ // couldn't ignore the DrawResult that PaintBorderWithStyleBorder returns.
+ MOZ_ASSERT(recessedBorder.mBorderImageSource.GetType() == eStyleImageType_Null);
+
+ Unused <<
+ nsCSSRendering::PaintBorderWithStyleBorder(PresContext(), aRenderingContext,
+ this, inner, inner,
+ recessedBorder, mStyleContext,
+ PaintBorderFlags::SYNC_DECODE_IMAGES);
+ }
+
+ // 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::GetSamplingFilterForFrame(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, DrawTarget* aDrawTarget,
+ 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());
+ AutoRestoreTransform autoRestoreTransform(aDrawTarget);
+ aDrawTarget->SetTransform(
+ aDrawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
+ f->GetImageMap()->Draw(aFrame, *aDrawTarget,
+ 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);
+
+ if (result == DrawResult::NOT_READY ||
+ result == DrawResult::INCOMPLETE ||
+ result == DrawResult::TEMPORARY_ERROR) {
+ // If the current image failed to paint because it's still loading or
+ // decoding, try painting the previous image.
+ if (mPrevImage) {
+ result = static_cast<nsImageFrame*>(mFrame)->
+ PaintImage(*aCtx, ToReferenceFrame(), mVisibleRect, mPrevImage, 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);
+}
+
+already_AddRefed<imgIContainer>
+nsDisplayImage::GetImage()
+{
+ nsCOMPtr<imgIContainer> image = mImage;
+ return image.forget();
+}
+
+nsRect
+nsDisplayImage::GetDestRect()
+{
+ bool snap = true;
+ const nsRect frameContentBox = GetBounds(&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)
+{
+ *aSnap = false;
+ if (mImage && mImage->WillDrawOpaqueNow()) {
+ const nsRect frameContentBox = GetBounds(aSnap);
+ return GetDestRect().Intersect(frameContentBox);
+ }
+ 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;
+
+ RefPtr<ImageContainer> container =
+ mImage->GetImageContainer(aManager, flags);
+ if (!container) {
+ return nullptr;
+ }
+
+ RefPtr<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();
+}
+
+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::GetSamplingFilterForFrame(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);
+ }
+
+ if (result == DrawResult::SUCCESS) {
+ mPrevImage = aImage;
+ } else if (result == DrawResult::BAD_IMAGE) {
+ mPrevImage = nullptr;
+ }
+
+ 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) {
+ uint32_t status = 0;
+ currentRequest->GetImageStatus(&status);
+ if (!(status & imgIRequest::STATUS_DECODE_COMPLETE)) {
+ MaybeDecodeForPredictedSize();
+ }
+ }
+ } else {
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayImage(aBuilder, this, mImage, mPrevImage));
+
+ // 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 == eMouseClick &&
+ 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;
+ nsresult rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ spec += nsPrintfCString("?%d,%d", p.x, p.y);
+ rv = uri->SetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool clicked = false;
+ if (aEvent->mMessage == eMouseClick && !aEvent->DefaultPrevented()) {
+ *aEventStatus = nsEventStatus_eConsumeDoDefault;
+ clicked = true;
+ }
+ nsContentUtils::TriggerLink(anchorNode, aPresContext, uri, target,
+ clicked, true, true);
+ }
+ }
+ }
+ }
+
+ return nsAtomicContainerFrame::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.
+ RefPtr<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 = nsAtomicContainerFrame::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;
+}
+
+void
+nsImageFrame::OnVisibilityChange(Visibility aNewVisibility,
+ Maybe<OnNonvisible> aNonvisibleAction)
+{
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+ if (!imageLoader) {
+ MOZ_ASSERT_UNREACHABLE("Should have an nsIImageLoadingContent");
+ nsAtomicContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+ return;
+ }
+
+ imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+
+ if (aNewVisibility == Visibility::APPROXIMATELY_VISIBLE) {
+ MaybeDecodeForPredictedSize();
+ }
+
+ nsAtomicContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+}
+
+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 ReflowInput* aReflowInput) const
+{
+ if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::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));
+
+ RefPtr<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, /* No context */
+ 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;
+ }
+
+ rv = LoadIcon(brokenSrc,
+ aPresContext,
+ getter_AddRefs(gIconLoad->mBrokenImage));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ 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)
+{
+ MOZ_ASSERT(aRequest);
+
+ if (aType != imgINotificationObserver::LOAD_COMPLETE &&
+ aType != imgINotificationObserver::FRAME_UPDATE) {
+ return NS_OK;
+ }
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ if (!image) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Retrieve the image's intrinsic size.
+ int32_t width = 0;
+ int32_t height = 0;
+ image->GetWidth(&width);
+ image->GetHeight(&height);
+
+ // Request a decode at that size.
+ image->RequestDecodeForSize(IntSize(width, height),
+ imgIContainer::DECODE_FLAGS_DEFAULT);
+ }
+
+ 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)
+{
+ nscoord isize = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+ this, nsLayoutUtils::MIN_ISIZE);
+ bool canBreak = !IsInAutoWidthTableCellForQuirk(this);
+ aData->DefaultAddInlineMinISize(this, isize, canBreak);
+}