layout/generic/nsImageFrame.cpp
author Emilio Cobos Álvarez <emilio@crisal.io>
Wed, 16 May 2018 19:39:16 +0200
changeset 796237 b90be48dcc97066e106fa1777c3d3acc1e46cd1d
parent 795647 920d49a96e6ec78991466cb5e1850c81d39937f2
child 796729 81868c3cee21a54522fdbac79c857390402a87b2
permissions -rw-r--r--
Bug 1149357: Make mIntrinsicSize account for density. r?dholbert Only doing it in ComputeSize (via GetNaturalSize) is unsound, and the rest of the users of mIntrinsicSize definitely do need scaling accounted for. Move the adjustment to nsImageFrame for two reasons: * Prevents adding more dependencies from nsIImageLoadingContent, which otherwise would need to go away anyway in bug 215083. * Avoids having to duplicate the image orientation logic, since mImage is already an OrientedImage if needed. MozReview-Commit-ID: EA0n0TctZhN

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "gfxContext.h"
#include "gfxUtils.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Encoding.h"
#include "mozilla/EventStates.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Helpers.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/dom/HTMLImageElement.h"
#include "mozilla/dom/ResponsiveImageSelector.h"
#include "mozilla/layers/WebRenderLayerManager.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 "nsIPresShell.h"
#include "nsGkAtoms.h"
#include "nsIDocument.h"
#include "nsContentUtils.h"
#include "nsCSSAnonBoxes.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 "nsNameSpaceManager.h"
#include <algorithm>
#ifdef ACCESSIBILITY
#include "nsAccessibilityService.h"
#endif
#include "nsLayoutUtils.h"
#include "nsDisplayList.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "FrameLayerBuilder.h"
#include "mozilla/dom/Selection.h"
#include "nsIURIMutator.h"

#include "imgIContainer.h"
#include "imgLoader.h"
#include "imgRequestProxy.h"

#include "nsCSSFrameConstructor.h"
#include "nsRange.h"

#include "nsError.h"
#include "nsBidiUtils.h"
#include "nsBidiPresUtils.h"

#include "gfxRect.h"
#include "ImageLayers.h"
#include "ImageContainer.h"
#include "mozilla/ServoStyleSet.h"
#include "nsBlockFrame.h"
#include "nsStyleStructInlines.h"

#include "mozilla/Preferences.h"

#include "mozilla/dom/Link.h"
#include "SVGImageContext.h"
#include "mozilla/dom/HTMLAnchorElement.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)

// Default alignment value (so we can tell an unset value from a set value)
#define ALIGN_UNSET uint8_t(-1)

// static icon information
StaticRefPtr<nsImageFrame::IconLoad> nsImageFrame::gIconLoad;

// 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, ComputedStyle* aStyle)
{
  return new (aPresShell) nsImageFrame(aStyle);
}

NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame)

nsImageFrame::nsImageFrame(ComputedStyle* aStyle, ClassID aID)
  : nsAtomicContainerFrame(aStyle, aID)
  , 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) {
    return;
  }

  mImageMap->Destroy();
  mImageMap = nullptr;

#ifdef ACCESSIBILITY
  if (nsAccessibilityService* accService = GetAccService()) {
    accService->RecreateAccessible(PresShell(), mContent);
  }
#endif
}

void
nsImageFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
{
  if (mReflowCallbackPosted) {
    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->RemoveNativeObserver(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, aPostDestroyData);
}

void
nsImageFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle)
{
  nsAtomicContainerFrame::DidSetComputedStyle(aOldComputedStyle);

  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 ComputedStyle before
  // because this is the first time it's been set, or if the image-orientation
  // property changed from its previous value.
  bool shouldUpdateOrientation =
    !aOldComputedStyle ||
    aOldComputedStyle->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) {
    MOZ_CRASH("Why do we have an nsImageFrame here at all?");
  }

  imageLoader->AddNativeObserver(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));

  if (currentRequest) {
    uint32_t categoryToBoostPriority = imgIRequest::CATEGORY_FRAME_INIT;

    // Increase load priority further if intrinsic size might be important for layout.
    if (!HaveSpecifiedSize(StylePosition())) {
      categoryToBoostPriority |= imgIRequest::CATEGORY_SIZE_QUERY;
    }

    currentRequest->BoostPriority(categoryToBoostPriority);
  }
}

static void
AdjustIntrinsicSizeIfNeeded(nsIContent& aContent, nsSize& aSize)
{
  auto* image = HTMLImageElement::FromNode(aContent);
  if (!image) {
    return;
  }

  ResponsiveImageSelector* selector = image->GetResponsiveImageSelector();
  if (!selector) {
    return;
  }

  double density = selector->GetSelectedImageDensity();
  MOZ_ASSERT(density > 0.0);
  if (aSize.width != -1) {
    aSize.width = NSToIntRound(double(aSize.width) / density);
  }
  if (aSize.height != -1) {
    aSize.height = NSToIntRound(double(aSize.height) / density);
  }
}

bool
nsImageFrame::UpdateIntrinsicSize(imgIContainer* aImage)
{
  MOZ_ASSERT(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))) {
    AdjustIntrinsicSizeIfNeeded(*mContent, 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)
{
  MOZ_ASSERT(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,
                                        ComputedStyle* aComputedStyle)
{
  EventStates state = aElement->State();
  if (IMAGE_OK(state,
               HaveSpecifiedSize(aComputedStyle->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 (aComputedStyle->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 (aElement->OwnerDoc()->GetCompatibilityMode() !=
           eCompatibility_NavQuirks) {
    useSizedBox = false;
  }
  else {
    // check whether we have specified size
    useSizedBox = HaveSpecifiedSize(aComputedStyle->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;
  }

  MarkNeedsDisplayItemRebuild();

  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)
{
  // XXX: Do we really want to check whether we have a
  // WebRenderUserDataProperty?
  if (HasProperty(WebRenderUserDataProperty::Key())) {
    RefPtr<WebRenderFallbackData> data = GetWebRenderUserData<WebRenderFallbackData>(this, static_cast<uint32_t>(DisplayItemType::TYPE_IMAGE));
    if (data) {
      data->SetInvalid(true);
    }
    SchedulePaint();
    return;
  }

  InvalidateLayer(DisplayItemType::TYPE_IMAGE,
                  aLayerInvalidRect,
                  aFrameInvalidRect);

  if (!mFirstFrameComplete) {
    InvalidateLayer(DisplayItemType::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 imageInvalid = 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));
          if (currentRequest) {
            uint32_t imageStatus;
            imageInvalid =
              NS_SUCCEEDED(currentRequest->GetImageStatus(&imageStatus)) &&
              (imageStatus & imgIRequest::STATUS_ERROR);
          } else {
            // check if images are user-disabled (or blocked for other
            // reasons)
            int16_t imageBlockingStatus;
            imageLoader->GetImageBlockingStatus(&imageBlockingStatus);
            imageInvalid = imageBlockingStatus != nsIContentPolicy::ACCEPT;
          }
        }
        // invalid image specified. make the image big enough for the "broken" icon
        if (imageInvalid) {
          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(gfxContext *aRenderingContext,
                          WritingMode aWM,
                          const LogicalSize& aCBSize,
                          nscoord aAvailableISize,
                          const LogicalSize& aMargin,
                          const LogicalSize& aBorder,
                          const LogicalSize& aPadding,
                          ComputeSizeFlags aFlags)
{
  EnsureIntrinsicSizeAndRatio();
  return ComputeSizeWithIntrinsicDimensions(aRenderingContext, aWM,
                                            mIntrinsicSize, 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->AsElement()->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(gfxContext *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(gfxContext *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);
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
  NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
                  ("enter nsImageFrame::Reflow: availSize=%d,%d",
                  aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));

  MOZ_ASSERT(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow");

  // see if we have a frozen size (i.e. a fixed width and height)
  if (HaveFixedSize(aReflowInput)) {
    AddStateBits(IMAGE_SIZECONSTRAINED);
  } else {
    RemoveStateBits(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) {
    AddStateBits(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.SetIncomplete();
  }

  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, aReflowInput.mStyleDisplay);

  if ((GetStateBits() & NS_FRAME_FIRST_REFLOW) && !mReflowCallbackPosted) {
    nsIPresShell* shell = 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,
                            gfxContext& 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,
                             gfxContext&          aRenderingContext,
                             const nsString&      aAltText,
                             const nsRect&        aRect)
{
  // Set font and color
  aRenderingContext.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) const 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) const override
  {
    *aSnap = false;
    return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
  }

  virtual void Paint(nsDisplayListBuilder* aBuilder,
                     gfxContext* 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);
    ImgDrawResult result =
      f->DisplayAltFeedback(*aCtx,
                            GetPaintRect(),
                            ToReferenceFrame(),
                            flags);

    nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
  }

  NS_DISPLAY_DECL_NAME("AltFeedback", TYPE_ALT_FEEDBACK)
};

ImgDrawResult
nsImageFrame::DisplayAltFeedback(gfxContext& 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 ImgDrawResult::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 ImgDrawResult that PaintBorderWithStyleBorder returns.
    MOZ_ASSERT(recessedBorder.mBorderImageSource.GetType() == eStyleImageType_Null);

    Unused <<
      nsCSSRendering::PaintBorderWithStyleBorder(PresContext(), aRenderingContext,
                                                 this, inner, inner,
                                                 recessedBorder, mComputedStyle,
                                                 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 ImgDrawResult::SUCCESS;
  }

  DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();

  // Clip so we don't render outside the inner rect
  aRenderingContext.Save();
  aRenderingContext.Clip(
    NSRectToSnappedRect(inner, PresContext()->AppUnitsPerDevPixel(), *drawTarget));

  ImgDrawResult result = ImgDrawResult::NOT_READY;

  // Check if we should display image placeholders
  if (!gIconLoad->mPrefShowPlaceholders ||
      (isLoading && !gIconLoad->mPrefShowLoadingPlaceholder)) {
    result = ImgDrawResult::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 &&
        !(imageStatus & imgIRequest::STATUS_ERROR)) {
      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(aRenderingContext, PresContext(), imgCon,
        nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect,
        /* no SVGImageContext */ Nothing(), aFlags);
    }

    // If we could not draw the icon, just draw some graffiti in the mean time.
    if (result == ImgDrawResult::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) {
      nsAutoString altText;
      nsCSSFrameConstructor::GetAlternateTextFor(content->AsElement(),
                                                 content->NodeInfo()->NameAtom(),
                                                 altText);
      DisplayAltText(PresContext(), aRenderingContext, altText, inner);
    }
  }

  aRenderingContext.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,
                      gfxContext* aCtx)
{
  uint32_t flags = imgIContainer::FLAG_NONE;
  if (aBuilder->ShouldSyncDecodeImages()) {
    flags |= imgIContainer::FLAG_SYNC_DECODE;
  }
  if (aBuilder->IsPaintingToWindow()) {
    flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
  }

  ImgDrawResult result = static_cast<nsImageFrame*>(mFrame)->
    PaintImage(*aCtx, ToReferenceFrame(), GetPaintRect(), mImage, flags);

  if (result == ImgDrawResult::NOT_READY ||
      result == ImgDrawResult::INCOMPLETE ||
      result == ImgDrawResult::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(), GetPaintRect(), 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) const
{
  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() const
{
  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)
{
  if (!nsDisplayItem::ForceActiveLayers()) {
    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;
      }
    }
  }


  if (!CanOptimizeToImageLayer(aManager, aBuilder)) {
    return LAYER_NONE;
  }

  // Image layer doesn't support draw focus ring for image map.
  nsImageFrame* f = static_cast<nsImageFrame*>(mFrame);
  if (f->HasImageMap()) {
    return LAYER_NONE;
  }

  return LAYER_ACTIVE;
}


/* virtual */ nsRegion
nsDisplayImage::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                bool* aSnap) const
{
  *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 = imgIContainer::FLAG_ASYNC_NOTIFY;
  if (aBuilder->ShouldSyncDecodeImages()) {
    flags |= imgIContainer::FLAG_SYNC_DECODE;
  }

  RefPtr<ImageContainer> container =
    mImage->GetImageContainer(aManager, flags);
  if (!container || !container->HasCurrentImage()) {
    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();
}

bool
nsDisplayImage::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                        mozilla::wr::IpcResourceUpdateQueue& aResources,
                                        const StackingContextHelper& aSc,
                                        WebRenderLayerManager* aManager,
                                        nsDisplayListBuilder* aDisplayListBuilder)
{
  if (!mImage) {
    return false;
  }

  if (mFrame->IsImageFrame()) {
    // Image layer doesn't support draw focus ring for image map.
    nsImageFrame* f = static_cast<nsImageFrame*>(mFrame);
    if (f->HasImageMap()) {
      return false;
    }
  }

  uint32_t flags = imgIContainer::FLAG_ASYNC_NOTIFY;
  if (aDisplayListBuilder->IsPaintingToWindow()) {
    flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
  }
  if (aDisplayListBuilder->ShouldSyncDecodeImages()) {
    flags |= imgIContainer::FLAG_SYNC_DECODE;
  }

  const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
  const LayoutDeviceRect destRect(
    LayoutDeviceRect::FromAppUnits(GetDestRect(), factor));
  Maybe<SVGImageContext> svgContext;
  IntSize decodeSize =
    nsLayoutUtils::ComputeImageContainerDrawingParameters(mImage, mFrame, destRect,
                                                          aSc, flags, svgContext);
  RefPtr<ImageContainer> container =
    mImage->GetImageContainerAtSize(aManager, decodeSize, svgContext, flags);
  if (!container) {
    return false;
  }

  // If the image container is empty, we don't want to fallback. Any other
  // failure will be due to resource constraints and fallback is unlikely to
  // help us. Hence we can ignore the return value from PushImage.
  aManager->CommandBuilder().PushImage(this, container, aBuilder, aResources, aSc, destRect);
  return true;
}

ImgDrawResult
nsImageFrame::PaintImage(gfxContext& 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;
  }

  Maybe<SVGImageContext> svgContext;
  SVGImageContext::MaybeStoreContextPaint(svgContext, this, aImage);

  ImgDrawResult result =
    nsLayoutUtils::DrawSingleImage(aRenderingContext,
      PresContext(), aImage,
      nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect,
      svgContext, flags, &anchorPoint);

  if (nsImageMap* map = GetImageMap()) {
    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 == ImgDrawResult::SUCCESS) {
    mPrevImage = aImage;
  } else if (result == ImgDrawResult::BAD_IMAGE) {
    mPrevImage = nullptr;
  }

  return result;
}

void
nsImageFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                               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()->AppendToTop(
        MakeDisplayItem<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();
        }
        // Increase loading priority if the image is ready to be displayed.
        if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)){
          currentRequest->BoostPriority(imgIRequest::CATEGORY_DISPLAY);
        }
      }
    } else {
      aLists.Content()->AppendToTop(
        MakeDisplayItem<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()->AppendToTop(
          MakeDisplayItem<nsDisplayGeneric>(aBuilder, this, PaintDebugImageMap, "DebugImageMap",
                                            DisplayItemType::TYPE_DEBUG_IMAGE_MAP));
      }
#endif
    }
  }

  if (ShouldDisplaySelection()) {
    DisplaySelectionOverlay(aBuilder, aLists.Content(),
                            nsISelectionDisplay::DISPLAY_IMAGES);
  }
}

bool
nsImageFrame::ShouldDisplaySelection()
{
  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

  // If the image is the only selected node, don't draw the selection overlay.
  // This can happen when selecting an image in contenteditable context.
  if (displaySelection == nsISelectionDisplay::DISPLAY_ALL) {
    if (const nsFrameSelection* frameSelection = GetConstFrameSelection()) {
      const Selection* selection = frameSelection->GetSelection(SelectionType::eNormal);
      if (selection && selection->RangeCount() == 1) {
        nsINode* parent = mContent->GetParent();
        int32_t thisOffset = parent->ComputeIndexOf(mContent);
        nsRange* range = selection->GetRangeAt(0);
        if (range->GetStartContainer() == parent &&
            range->GetEndContainer() == parent &&
            static_cast<int32_t>(range->StartOffset()) == thisOffset &&
            static_cast<int32_t>(range->EndOffset()) == thisOffset + 1) {
          return false;
        }
      }
    }
  }
  return true;
}

nsImageMap*
nsImageFrame::GetImageMap()
{
  if (!mImageMap) {
    if (nsIContent* map = GetMapElement()) {
      mImageMap = new nsImageMap();
      mImageMap->Init(this, map);
    }
  }

  return mImageMap;
}

bool
nsImageFrame::IsServerImageMap()
{
  return mContent->AsElement()->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);

      RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::FromNode(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;
  }

  if (nsImageMap* map = GetImageMap()) {
    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 (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 = NS_MutateURI(uri)
                 .SetSpec(spec)
                 .Finalize(uri);
          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, /* isTrusted */ true);
        }
      }
    }
  }

  return nsAtomicContainerFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
}

nsresult
nsImageFrame::GetCursor(const nsPoint& aPoint,
                        nsIFrame::Cursor& aCursor)
{
  if (nsImageMap* map = GetImageMap()) {
    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 ComputedStyle 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<ComputedStyle> areaStyle =
        PresShell()->StyleSet()->
          ResolveStyleFor(area->AsElement(), Style(),
                          LazyComputeBehavior::Allow);
      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,
                               nsAtom* aAttribute,
                               int32_t aModType)
{
  nsresult rv = nsAtomicContainerFrame::AttributeChanged(aNameSpaceID,
                                                         aAttribute, aModType);
  if (NS_FAILED(rv)) {
    return rv;
  }
  if (nsGkAtoms::alt == aAttribute)
  {
    PresShell()->FrameNeedsReflow(this, nsIPresShell::eStyleChange,
                                  NS_FRAME_IS_DIRTY);
  }

  return NS_OK;
}

void
nsImageFrame::OnVisibilityChange(Visibility aNewVisibility,
                                 const 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);
}

#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;
  MOZ_ASSERT(!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_Unset,
                       nullptr,      /* principal (not relevant for icons) */
                       0,
                       loadGroup,
                       gIconLoad,
                       nullptr,      /* No context */
                       nullptr,      /* Not associated with any particular document */
                       loadFlags,
                       nullptr,
                       contentPolicyType,
                       EmptyString(),
                       false,        /* aUseUrgentStartForChannel */
                       aRequest);
}

void
nsImageFrame::GetDocumentCharacterSet(nsACString& aCharset) const
{
  if (mContent) {
    NS_ASSERTION(mContent->GetComposedDoc(),
                 "Frame still alive after content removed from document!");
    mContent->GetComposedDoc()->GetDocumentCharacterSet()->Name(aCharset);
  }
}

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;

  MOZ_ASSERT(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();

  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->Style()->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(gfxContext* aRenderingContext,
                                nsIFrame::InlineMinISizeData* aData)
{
  nscoord isize = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
                    this, nsLayoutUtils::MIN_ISIZE);
  bool canBreak = !IsInAutoWidthTableCellForQuirk(this);
  aData->DefaultAddInlineMinISize(this, isize, canBreak);
}