/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is 
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or 
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the NPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
#include "nsHTMLParts.h"
#include "nsCOMPtr.h"
#include "nsImageFrame.h"
#include "nsString.h"
#include "nsIPresContext.h"
#include "nsIRenderingContext.h"
#include "nsIPresShell.h"
#include "nsHTMLIIDs.h"
#include "nsIImage.h"
#include "nsIWidget.h"
#include "nsHTMLAtoms.h"
#include "nsIHTMLContent.h"
#include "nsIDocument.h"
#include "nsIStyleContext.h"
#include "nsStyleConsts.h"
#include "nsImageMap.h"
#include "nsILinkHandler.h"
#include "nsIURL.h"
#include "nsIIOService.h"
#include "nsIURL.h"
#include "nsILoadGroup.h"
#include "nsIServiceManager.h"
#include "nsNetUtil.h"
#include "nsIView.h"
#include "nsIViewManager.h"
#include "nsHTMLContainerFrame.h"
#include "prprf.h"
#include "nsIFontMetrics.h"
#include "nsCSSRendering.h"
#include "nsIDOMHTMLAnchorElement.h"
#include "nsIDOMHTMLImageElement.h"
#include "nsIDeviceContext.h"
#include "nsINameSpaceManager.h"
#include "nsTextFragment.h"
#include "nsIDOMHTMLMapElement.h"
#include "nsIStyleSet.h"
#include "nsLayoutAtoms.h"
#include "nsISizeOfHandler.h"
#include "nsImageMapUtils.h"
#include "nsIFrameManager.h"
#include "nsIScriptSecurityManager.h"
#ifdef ACCESSIBILITY
#include "nsIAccessibilityService.h"
#endif
#include "nsIServiceManager.h"
#include "nsIDOMNode.h"
#include "nsGUIEvent.h"

#include "imgIContainer.h"
#include "imgILoader.h"

#include "nsIEventQueueService.h"
#include "plevent.h"

#include "nsContentPolicyUtils.h"
#include "nsIScriptGlobalObject.h"
#include "nsIDOMWindow.h"
#include "nsIDOMDocument.h"
#include "nsCSSFrameConstructor.h"
#include "nsIPref.h"

#ifdef DEBUG
#undef NOISY_IMAGE_LOADING
#undef NOISY_ICON_LOADING
#else
#undef NOISY_IMAGE_LOADING
#undef NOISY_ICON_LOADING
#endif


// Value's for mSuppress
#define SUPPRESS_UNSET   0
#define DONT_SUPPRESS    1
#define SUPPRESS         2
#define DEFAULT_SUPPRESS 3

// values for image loading icons
#define NS_ICON_LOADING_IMAGE (0)
#define NS_ICON_BROKEN_IMAGE  (1)

// 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 PRUint8(-1)

// static icon information: initialized in Init and de-initialized in Destroy
// - created if null, deleted if refcount goes to 0
nsImageFrame::IconLoad* nsImageFrame::mIconLoad = nsnull;

// test if the width and height are fixed, looking at the style data
static PRBool HaveFixedSize(const struct 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
  nsStyleUnit widthUnit = aStylePosition.mWidth.GetUnit();
  nsStyleUnit heightUnit = aStylePosition.mHeight.GetUnit();

  return ((widthUnit  == eStyleUnit_Coord ||
           widthUnit  == eStyleUnit_Percent) &&
          (heightUnit == eStyleUnit_Coord ||
           heightUnit == eStyleUnit_Percent));
}
// use the data in the reflow state to decide if the image has a constrained size
// (i.e. width and height that are based on the containing block size and not the image size) 
// so we can avoid animated GIF related reflows
inline PRBool HaveFixedSize(const nsHTMLReflowState& aReflowState)
{ 
  NS_ASSERTION(aReflowState.mStylePosition, "crappy reflowState - null stylePosition");
  return HaveFixedSize(*(aReflowState.mStylePosition)); 
}

nsresult
NS_NewImageFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame)
{
  NS_PRECONDITION(aNewFrame, "null OUT ptr");
  if (nsnull == aNewFrame) {
    return NS_ERROR_NULL_POINTER;
  }
  nsImageFrame* it = new (aPresShell) nsImageFrame;
  if (nsnull == it) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  *aNewFrame = it;
  return NS_OK;
}


int
nsImageFrame::GetImageLoad(imgIRequest *aRequest)
{
  if (aRequest == mLoads[0].mRequest)
    return 0;
  else if (aRequest == mLoads[1].mRequest)
    return 1;

  NS_ASSERTION((aRequest == mLoads[0].mRequest &&
               aRequest == mLoads[1].mRequest), "Failure to figure out which imgIRequest this is!");

  return -1;
}

nsImageFrame::nsImageFrame() :
  mIntrinsicSize(0, 0),
  mGotInitialReflow(PR_FALSE),
  mImageBlocked(PR_FALSE),
  mFailureReplace(PR_TRUE)
{
  // Size is constrained if we have a width and height. 
  // - Set in reflow in case the attributes are changed
  mSizeConstrained = PR_FALSE;
}

nsImageFrame::~nsImageFrame()
{
}

NS_IMETHODIMP
nsImageFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr)
{
  NS_ENSURE_ARG_POINTER(aInstancePtr);
  *aInstancePtr = nsnull;

#ifdef DEBUG
  if (aIID.Equals(NS_GET_IID(nsIFrameDebug))) {
    *aInstancePtr = NS_STATIC_CAST(nsIFrameDebug*,this);
    return NS_OK;
  }
#endif

  if (aIID.Equals(NS_GET_IID(nsIImageFrame))) {
    *aInstancePtr = NS_STATIC_CAST(nsIImageFrame*,this);
    return NS_OK;
  } else if (aIID.Equals(NS_GET_IID(nsIFrame))) {
    *aInstancePtr = NS_STATIC_CAST(nsIFrame*,this);
    return NS_OK;
  } else if (aIID.Equals(NS_GET_IID(nsISupports))) {
    *aInstancePtr = NS_STATIC_CAST(nsIImageFrame*,this);
    return NS_OK;
  }

  return NS_NOINTERFACE;
}

#ifdef ACCESSIBILITY
NS_IMETHODIMP nsImageFrame::GetAccessible(nsIAccessible** aAccessible)
{
  nsCOMPtr<nsIAccessibilityService> accService = do_GetService("@mozilla.org/accessibilityService;1");

  if (accService) {
    return accService->CreateHTMLImageAccessible(NS_STATIC_CAST(nsIFrame*, this), aAccessible);
  }

  return NS_ERROR_FAILURE;
}
#endif

NS_IMETHODIMP_(nsrefcnt) nsImageFrame::AddRef(void)
{
  NS_WARNING("not supported for frames");
  return 1;
}

NS_IMETHODIMP_(nsrefcnt) nsImageFrame::Release(void)
{
  NS_WARNING("not supported for frames");
  return 1;
}

NS_IMETHODIMP
nsImageFrame::Destroy(nsIPresContext* aPresContext)
{
  // Tell our image map, if there is one, to clean up
  // This causes the nsImageMap to unregister itself as
  // a DOM listener.
  if(mImageMap) {
    mImageMap->Destroy();
    NS_RELEASE(mImageMap);
  }

  for (int i=0; i != 2; ++i) {
    if (mLoads[i].mRequest) {
      mLoads[i].mRequest->Cancel(NS_ERROR_FAILURE);
      mLoads[i].mRequest = nsnull;
    }
  }

  // set the frame to null so we don't send messages to a dead object.
  if (mListener)
    NS_REINTERPRET_CAST(nsImageListener*, mListener.get())->SetFrame(nsnull);

  mListener = nsnull;

   // check / cleanup the IconLoads singleton
   if (mIconLoad) {
#ifdef NOISY_ICON_LOADING
     printf( "Releasing IconLoad (%p)\n", this);
#endif
     if (mIconLoad->Release()) {
#ifdef NOISY_ICON_LOADING
       printf( "Deleting IconLoad (%p)\n", this);
#endif
       delete mIconLoad;
       mIconLoad = nsnull;
     }
   }
 

  return nsSplittableFrame::Destroy(aPresContext);
}



NS_IMETHODIMP
nsImageFrame::Init(nsIPresContext*  aPresContext,
                   nsIContent*      aContent,
                   nsIFrame*        aParent,
                   nsIStyleContext* aContext,
                   nsIFrame*        aPrevInFlow)
{
  nsresult  rv = nsSplittableFrame::Init(aPresContext, aContent, aParent,
                                         aContext, aPrevInFlow);
  // See if we have a SRC attribute
  nsAutoString src;
  nsresult ca;
  ca = mContent->GetAttr(kNameSpaceID_HTML, nsHTMLAtoms::src, src);
  if ((NS_CONTENT_ATTR_HAS_VALUE != ca) || (src.Length() == 0))
  {
    // Let's see if this is an object tag and we have a DATA attribute
    nsIAtom*  tag;
    mContent->GetTag(tag);

    if(tag == nsHTMLAtoms::object)
      mContent->GetAttr(kNameSpaceID_HTML, nsHTMLAtoms::data, src);
  
    NS_IF_RELEASE(tag);
  }

  mInitialLoadCompleted = PR_FALSE;
  mCanSendLoadEvent = PR_TRUE;

  LoadIcons(aPresContext);

  mLoads[0].mRequest = do_CreateInstance("@mozilla.org/image/request;1");
  rv = LoadImage(src, aPresContext, mLoads[0].mRequest); // if the image was found in the cache,
                                                         // it is possible that LoadImage will result in
                                                         // a call to OnStartContainer()

  return rv;
}


NS_IMETHODIMP nsImageFrame::OnStartDecode(imgIRequest *aRequest, nsIPresContext *aPresContext)
{
  return NS_OK;
}

NS_IMETHODIMP nsImageFrame::OnStartContainer(imgIRequest *aRequest, nsIPresContext *aPresContext, imgIContainer *aImage)
{
  if (!aImage) return NS_ERROR_INVALID_ARG;

  mInitialLoadCompleted = PR_TRUE;

  // handle iconLoads first...
  if (HandleIconLoads(aRequest, PR_FALSE)) {
    return NS_OK;
  }

  int whichLoad = GetImageLoad(aRequest);
  if (whichLoad == -1) return NS_ERROR_FAILURE;
  struct ImageLoad *load= &mLoads[whichLoad];

  if (aImage)
  {
    /* Get requested animation policy from the pres context:
     *   normal = 0
     *   one frame = 1
     *   one loop = 2
     */
    PRUint16 animateMode = imgIContainer::kNormalAnimMode; //default value
    nsresult rv = aPresContext->GetImageAnimationMode(&animateMode);
    if (NS_SUCCEEDED(rv))
      aImage->SetAnimationMode(animateMode);
  }

  nscoord w, h;
  aImage->GetWidth(&w);
  aImage->GetHeight(&h);

  float p2t;
  aPresContext->GetPixelsToTwips(&p2t);

  nsSize newsize(NSIntPixelsToTwips(w, p2t), NSIntPixelsToTwips(h, p2t));

  if (load->mIntrinsicSize != newsize) {
    load->mIntrinsicSize = newsize;

    if (load->mIntrinsicSize.width != 0 && load->mIntrinsicSize.height != 0)
      load->mTransform.SetToScale((float(mComputedSize.width) / float(load->mIntrinsicSize.width)),
                                  (float(mComputedSize.height) / float(load->mIntrinsicSize.height)));

    if (!mSizeConstrained) {
      nsCOMPtr<nsIPresShell> presShell;
      aPresContext->GetShell(getter_AddRefs(presShell));
      NS_ASSERTION(mParent, "No parent to pass the reflow request up to.");
      NS_ASSERTION(presShell, "No PresShell.");
      if (mParent && presShell && mGotInitialReflow && whichLoad == 0) { // don't reflow if we havn't gotten the inital reflow yet
        mState |= NS_FRAME_IS_DIRTY;
        mParent->ReflowDirtyChild(presShell, NS_STATIC_CAST(nsIFrame*, this));
      }
    }
  }

  return NS_OK;
}

NS_IMETHODIMP nsImageFrame::OnStartFrame(imgIRequest *aRequest, nsIPresContext *aPresContext, gfxIImageFrame *aFrame)
{
  return NS_OK;
}

NS_IMETHODIMP nsImageFrame::OnDataAvailable(imgIRequest *aRequest, nsIPresContext *aPresContext, gfxIImageFrame *aFrame, const nsRect *aRect)
{
  // XXX do we need to make sure that the reflow from the OnStartContainer has been
  // processed before we start calling invalidate

  if (!aRect)
    return NS_ERROR_NULL_POINTER;

  // handle iconLoads first...
  if (HandleIconLoads(aRequest, PR_FALSE)) {
    if (!aRect->IsEmpty()) {
      Invalidate(aPresContext, *aRect, PR_FALSE);
    }
    return NS_OK;
  }

  int whichLoad = GetImageLoad(aRequest);
  if (whichLoad == -1) return NS_ERROR_FAILURE;
  if (whichLoad != 0) return NS_OK;
  struct ImageLoad *load= &mLoads[whichLoad];


  nsRect r(aRect->x, aRect->y, aRect->width, aRect->height);

  float p2t;
  aPresContext->GetPixelsToTwips(&p2t);
  r.x = NSIntPixelsToTwips(r.x, p2t);
  r.y = NSIntPixelsToTwips(r.y, p2t);
  r.width = NSIntPixelsToTwips(r.width, p2t);
  r.height = NSIntPixelsToTwips(r.height, p2t);

  load->mTransform.TransformCoord(&r.x, &r.y, &r.width, &r.height);

  r.x += mBorderPadding.left;
  r.y += mBorderPadding.top;

  if (whichLoad == 0 && !r.IsEmpty())
    Invalidate(aPresContext, r, PR_FALSE);

  return NS_OK;
}

NS_IMETHODIMP nsImageFrame::OnStopFrame(imgIRequest *aRequest, nsIPresContext *aPresContext, gfxIImageFrame *aFrame)
{
  return NS_OK;
}

NS_IMETHODIMP nsImageFrame::OnStopContainer(imgIRequest *aRequest, nsIPresContext *aPresContext, imgIContainer *aImage)
{
  return NS_OK;
}

static void PR_CALLBACK
HandleImagePLEvent(nsIContent *aContent, PRUint32 aMessage, PRUint32 aFlags)
{
  nsCOMPtr<nsIDOMNode> node(do_QueryInterface(aContent));

  if (!node) {
    NS_ERROR("null or non-DOM node passed to HandleImagePLEvent!");

    return;
  }

  nsCOMPtr<nsIDOMDocument> dom_doc;
  node->GetOwnerDocument(getter_AddRefs(dom_doc));

  nsCOMPtr<nsIDocument> doc(do_QueryInterface(dom_doc));

  if (!doc) {
    return;
  }

  nsCOMPtr<nsIPresShell> pres_shell;
  doc->GetShellAt(0, getter_AddRefs(pres_shell));

  if (!pres_shell) {
    return;
  }

  nsCOMPtr<nsIPresContext> pres_context;
  pres_shell->GetPresContext(getter_AddRefs(pres_context));

  if (!pres_context) {
    return;
  }

  nsEventStatus status = nsEventStatus_eIgnore;
  nsEvent event;
  event.eventStructType = NS_EVENT;
  event.message = aMessage;

  aContent->HandleDOMEvent(pres_context, &event, nsnull, aFlags, &status);
}

static void PR_CALLBACK
HandleImageOnloadPLEvent(PLEvent *aEvent)
{
  nsIContent *content = (nsIContent *)PL_GetEventOwner(aEvent);

  HandleImagePLEvent(content, NS_IMAGE_LOAD,
                     NS_EVENT_FLAG_INIT | NS_EVENT_FLAG_CANT_BUBBLE);

  NS_RELEASE(content);
}

static void PR_CALLBACK
HandleImageOnerrorPLEvent(PLEvent *aEvent)
{
  nsIContent *content = (nsIContent *)PL_GetEventOwner(aEvent);

  HandleImagePLEvent(content, NS_IMAGE_ERROR, NS_EVENT_FLAG_INIT);

  NS_RELEASE(content);
}

static void PR_CALLBACK
DestroyImagePLEvent(PLEvent* aEvent)
{
  delete aEvent;
}

// Fire off a PLEvent that'll asynchronously call the image elements
// onload handler once handled. This is needed since the image library
// can't decide if it wants to call it's observer methods
// synchronously or asynchronously. If an image is loaded from the
// cache the notifications come back synchronously, but if the image
// is loaded from the netswork the notifications come back
// asynchronously.

void
nsImageFrame::FireDOMEvent(PRUint32 aMessage)
{
  static NS_DEFINE_CID(kEventQueueServiceCID,   NS_EVENTQUEUESERVICE_CID);

  nsCOMPtr<nsIEventQueueService> event_service =
    do_GetService(kEventQueueServiceCID);

  if (!event_service) {
    NS_WARNING("Failed to get event queue service");

    return;
  }

  nsCOMPtr<nsIEventQueue> event_queue;

  event_service->GetThreadEventQueue(NS_CURRENT_THREAD,
                                     getter_AddRefs(event_queue));

  if (!event_queue) {
    NS_WARNING("Failed to get event queue from service");

    return;
  }

  PLEvent *event = new PLEvent;

  if (!event) {
    // Out of memory, but none of our callers care, so just warn and
    // don't fire the event

    NS_WARNING("Out of memory?");

    return;
  }

  PLHandleEventProc f;

  switch (aMessage) {
  case NS_IMAGE_LOAD :
    f = (PLHandleEventProc)::HandleImageOnloadPLEvent;

    break;
  case NS_IMAGE_ERROR :
    f = (PLHandleEventProc)::HandleImageOnerrorPLEvent;

    break;
  default:
    NS_WARNING("Huh, I don't know how to fire this type of event?!");

    return;
  }

  PL_InitEvent(event, mContent, f, (PLDestroyEventProc)::DestroyImagePLEvent);

  // The event owns the content pointer now.
  NS_ADDREF(mContent);

  event_queue->PostEvent(event);
}


NS_IMETHODIMP nsImageFrame::OnStopDecode(imgIRequest *aRequest, nsIPresContext *aPresContext, nsresult aStatus, const PRUnichar *aStatusArg)
{
  nsCOMPtr<nsIPresShell> presShell;
  aPresContext->GetShell(getter_AddRefs(presShell));

  // check to see if an image error occurred
  PRBool imageFailedToLoad = PR_FALSE;

  // handle iconLoads first...
  if (HandleIconLoads(aRequest, NS_SUCCEEDED(aStatus))) {
    return NS_OK;
  }

  int whichLoad = GetImageLoad(aRequest);

  if (whichLoad == -1) return NS_ERROR_FAILURE;

  if (whichLoad == 1) {
    if (NS_SUCCEEDED(aStatus)) {
      if (mLoads[0].mRequest) {
        mLoads[0].mRequest->Cancel(NS_ERROR_FAILURE);
      }

      mLoads[0].mRequest = mLoads[1].mRequest;
      mLoads[0].mIntrinsicSize = mLoads[1].mIntrinsicSize;
      // XXX i don't think we always want to set this.
      mLoads[0].mTransform = mLoads[1].mTransform;

      mLoads[1].mRequest = nsnull;

      if (!mSizeConstrained && (mLoads[0].mIntrinsicSize != mIntrinsicSize)) {
        nsCOMPtr<nsIPresShell> presShell;
        aPresContext->GetShell(getter_AddRefs(presShell));
        NS_ASSERTION(mParent, "No parent to pass the reflow request up to.");
        NS_ASSERTION(presShell, "No PresShell.");
        if (mParent && presShell && mGotInitialReflow) { // don't reflow if we havn't gotten the inital reflow yet
          mState |= NS_FRAME_IS_DIRTY;
          mParent->ReflowDirtyChild(presShell, NS_STATIC_CAST(nsIFrame*, this));
        }
      } else {
        nsSize s;
        GetSize(s);
        nsRect r(0,0, s.width, s.height);
        if (!r.IsEmpty()) {
          Invalidate(aPresContext, r, PR_FALSE);
        }
      }

    } else {
      mLoads[1].mRequest = nsnull;
    }

  } else if (NS_FAILED(aStatus)) {

    if (whichLoad == 0 || !mLoads[0].mRequest) {
      imageFailedToLoad = PR_TRUE;
    }

  }

  // if src failed to load, determine how to handle it: 
  //  - either render the ALT text in this frame, or let the presShell handle it
  if (imageFailedToLoad && presShell) {
    if (mFailureReplace) {
      // first check for image map
      nsAutoString usemap;
      mContent->GetAttr(kNameSpaceID_HTML, nsHTMLAtoms::usemap, usemap);    
      if (usemap.IsEmpty()) {
        // check if we want to honor the ALT text in the IMG frame, or let the preShell make it into inline text
        //  - if QuirksMode, and the IMG has a size, then render the ALT text in the IMG frame
        //    UNLESS there is a pref set to force inline alt text
        PRBool useSizedBox = PR_FALSE;
        PRBool prefForceInlineAltText = mIconLoad ? mIconLoad->mPrefForceInlineAltText : PR_FALSE;

        // check if we have fixed size
        const nsStylePosition* stylePosition =
          (const nsStylePosition*)mStyleContext->GetStyleData(eStyleStruct_Position);
        NS_ASSERTION(stylePosition, "null style position: frame is corrupted");
        
        // check for quirks mode
        nsCompatibility mode;
        aPresContext->GetCompatibilityMode(&mode);
        
        // check for being in Composer: if we are, we always show the size-box
        PRBool forceIcon = PR_FALSE;

        // check for style property that indicates the icon should always be shown
        const nsStyleUIReset* styleData;
        GetStyleData(eStyleStruct_UIReset, (const nsStyleStruct*&) styleData);
        if (styleData->mForceBrokenImageIcon) {
          forceIcon = PR_TRUE;
        }

        // wrap it all up: use the size box if
        // - in editor
        // - not forcing inline alt text, have fixed size, in quirks mode, image not blocked
        useSizedBox = forceIcon ||
                      (!prefForceInlineAltText &&
                       HaveFixedSize(*stylePosition) && 
                       mode == eCompatibility_NavQuirks &&
                       !mImageBlocked);

        if (!useSizedBox) {
          // let the presShell handle converting this into the inline alt text frame
          presShell->CantRenderReplacedElement(aPresContext, this);
        } else {
          // we are handling it
          // invalidate the icon area (it may change states)   
          InvalidateIcon(aPresContext);
        }
      }
    }
    mFailureReplace = PR_TRUE;
  }

  // After these DOM events are fired its possible that this frame may be deleted.  As a result
  // the code should not attempt to access any of the frames internal data after this point.
  if (presShell) {
    if (imageFailedToLoad) {
      // Send error event
      FireDOMEvent(NS_IMAGE_ERROR);
    } else if (mCanSendLoadEvent) {
      // Send load event
      mCanSendLoadEvent = PR_FALSE;

      FireDOMEvent(NS_IMAGE_LOAD);
    }
  }

  return NS_OK;
}

NS_IMETHODIMP nsImageFrame::FrameChanged(imgIContainer *aContainer, nsIPresContext *aPresContext, gfxIImageFrame *aNewFrame, nsRect *aDirtyRect)
{
  if (!mLoads[0].mRequest)
    return NS_OK; // if mLoads[0].mRequest is null, this isn't for the first one, so we don't care about it.

  nsCOMPtr<imgIContainer> con;
  mLoads[0].mRequest->GetImage(getter_AddRefs(con));
  if (aContainer == con.get()) {
    nsRect r(*aDirtyRect);

    float p2t;
    aPresContext->GetPixelsToTwips(&p2t);
    r.x = NSIntPixelsToTwips(r.x, p2t);
    r.y = NSIntPixelsToTwips(r.y, p2t);
    r.width = NSIntPixelsToTwips(r.width, p2t);
    r.height = NSIntPixelsToTwips(r.height, p2t);

    mLoads[0].mTransform.TransformCoord(&r.x, &r.y, &r.width, &r.height);
    if (!r.IsEmpty()) {
      Invalidate(aPresContext, r, PR_FALSE);
    }
  }
  return NS_OK;
}


#define MINMAX(_value,_min,_max) \
    ((_value) < (_min)           \
     ? (_min)                    \
     : ((_value) > (_max)        \
        ? (_max)                 \
        : (_value)))

void
nsImageFrame::GetDesiredSize(nsIPresContext* aPresContext,
                             const nsHTMLReflowState& aReflowState,
                             nsHTMLReflowMetrics& aDesiredSize)
{
  // if mLoads[0].mIntrinsicSize.width and height are 0, then we should
  // check to see if the size is already known by the image container.
  if (mLoads[0].mIntrinsicSize.width == 0 &&
      mLoads[0].mIntrinsicSize.height == 0) {
    PRBool needMinIconSizing = PR_FALSE;
    float p2t;
    aPresContext->GetPixelsToTwips(&p2t);
    if (mLoads[0].mRequest) {
      nsCOMPtr<imgIContainer> con;
      mLoads[0].mRequest->GetImage(getter_AddRefs(con));
      if (con) {
        nscoord width, height;

        con->GetWidth(&width);
        con->GetHeight(&height);
        mLoads[0].mIntrinsicSize.width = NSIntPixelsToTwips(width, p2t);
        mLoads[0].mIntrinsicSize.height = NSIntPixelsToTwips(height, p2t);
      } else {
        // no image container, so we may need to synthesize a minimum size for the image icons
        needMinIconSizing = PR_TRUE;
      }
    } else {
      // no request, so we may need to synthesize a minimum size for the image icons
      needMinIconSizing = PR_TRUE;
    }

    if (needMinIconSizing) {
      // XXX:  we need this in composer, but it is also good for general quirks mode to always
      // XXX:  have room for the icon
      // check for quirks mode
      nsCompatibility mode;
      aPresContext->GetCompatibilityMode(&mode);
      if (mode == eCompatibility_NavQuirks) {
        // image request is null or image size not known, probably an invalid image specified
        // - make the image big enough for the icon (it may not be used if inline alt expansion is used instead)
        mLoads[0].mIntrinsicSize.width = NSIntPixelsToTwips(ICON_SIZE+(2*(ICON_PADDING+ALT_BORDER_WIDTH)), p2t);
        mLoads[0].mIntrinsicSize.height = NSIntPixelsToTwips(ICON_SIZE+(2*(ICON_PADDING+ALT_BORDER_WIDTH)), p2t);
      }
    }
  }
  mIntrinsicSize = mLoads[0].mIntrinsicSize;

  float t2p, sp2t;
  aPresContext->GetTwipsToPixels(&t2p);
  aPresContext->GetScaledPixelsToTwips(&sp2t);

  // convert from normal twips to scaled twips (printing...)
  float t2st = t2p * sp2t; // twips to scaled twips
  nscoord intrinsicWidth =
      NSToCoordRound(float(mIntrinsicSize.width) * t2st);
  nscoord intrinsicHeight =
      NSToCoordRound(float(mIntrinsicSize.height) * t2st);

  // Determine whether the image has fixed content width
  nscoord widthConstraint = aReflowState.mComputedWidth;
  nscoord minWidth = aReflowState.mComputedMinWidth;
  nscoord maxWidth = aReflowState.mComputedMaxWidth;

  // Determine whether the image has fixed content height
  nscoord heightConstraint = aReflowState.mComputedHeight;
  nscoord minHeight = aReflowState.mComputedMinHeight;
  nscoord maxHeight = aReflowState.mComputedMaxHeight;

  PRBool isAutoWidth = widthConstraint == NS_INTRINSICSIZE;
  if (isAutoWidth)
    widthConstraint = intrinsicWidth;
  PRBool isAutoHeight = heightConstraint == NS_UNCONSTRAINEDSIZE;
  if (isAutoHeight)
    heightConstraint = intrinsicHeight;

  nscoord newWidth = MINMAX(widthConstraint, minWidth, maxWidth);
  nscoord newHeight = MINMAX(heightConstraint, minHeight, maxHeight);

  if (isAutoWidth && newWidth != intrinsicWidth)
    isAutoWidth = PR_FALSE;
  if (isAutoHeight && newHeight != intrinsicHeight)
    isAutoHeight = PR_FALSE;

  if (isAutoWidth) {
    if (!isAutoHeight && intrinsicHeight != 0) {
       newWidth = (intrinsicWidth * newHeight) / intrinsicHeight;
    }
  } else {
    if (isAutoHeight && intrinsicWidth != 0) {
       newHeight = (intrinsicHeight * newWidth) / intrinsicWidth;
    }
  }

  mComputedSize.width = newWidth;
  mComputedSize.height = newHeight;

  nsTransform2D *transform = &mLoads[0].mTransform;

  if (mComputedSize == mIntrinsicSize) {
    transform->SetToIdentity();
  } else {
    if (mIntrinsicSize.width != 0 && mIntrinsicSize.height != 0) {
      transform->SetToScale(float(mComputedSize.width) / float(mIntrinsicSize.width),
                            float(mComputedSize.height) / float(mIntrinsicSize.height));
    }
  }

  aDesiredSize.width = mComputedSize.width;
  aDesiredSize.height = mComputedSize.height;
}

void
nsImageFrame::GetInnerArea(nsIPresContext* aPresContext,
                           nsRect& aInnerArea) const
{
  aInnerArea.x = mBorderPadding.left;
  aInnerArea.y = mBorderPadding.top;
  aInnerArea.width = mRect.width -
    (mBorderPadding.left + mBorderPadding.right);
  aInnerArea.height = (!mPrevInFlow && !mNextInFlow) 
    ? mRect.height - mBorderPadding.top - mBorderPadding.bottom : mComputedSize.height;
}

NS_IMETHODIMP
nsImageFrame::ContentChanged(nsIPresContext* aPresContext,
                             nsIContent*     aChild,
                             nsISupports*    aSubContent)
{
  nsCOMPtr<nsIPresShell> shell;
  aPresContext->GetShell(getter_AddRefs(shell));
  mState |= NS_FRAME_IS_DIRTY;
  return mParent->ReflowDirtyChild(shell, this);
}

// get the offset into the content area of the image where aImg starts if it is a continuation.
nscoord 
nsImageFrame::GetContinuationOffset(nscoord* aWidth) const
{
  nscoord offset = 0;
  if (aWidth) {
    *aWidth = 0;
  }

  if (mPrevInFlow) {
    for (nsIFrame* prevInFlow = mPrevInFlow ; prevInFlow; prevInFlow->GetPrevInFlow(&prevInFlow)) {
      nsRect rect;
      prevInFlow->GetRect(rect);
      if (aWidth) {
        *aWidth = rect.width;
      }
      offset += rect.height;
    }
    offset -= mBorderPadding.top;
    offset = PR_MAX(0, offset);
  }
  return offset;
}

NS_IMETHODIMP
nsImageFrame::Reflow(nsIPresContext*          aPresContext,
                     nsHTMLReflowMetrics&     aMetrics,
                     const nsHTMLReflowState& aReflowState,
                     nsReflowStatus&          aStatus)
{
  DO_GLOBAL_REFLOW_COUNT("nsImageFrame", aReflowState.reason);
  DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus);
  NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
                  ("enter nsImageFrame::Reflow: availSize=%d,%d",
                  aReflowState.availableWidth, aReflowState.availableHeight));

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

  aStatus = NS_FRAME_COMPLETE;

  // see if we have a frozen size (i.e. a fixed width and height)
  mSizeConstrained = HaveFixedSize(aReflowState);

  if (aReflowState.reason == eReflowReason_Initial)
    mGotInitialReflow = PR_TRUE;

  // get the desired size of the complete image
  GetDesiredSize(aPresContext, aReflowState, aMetrics);

  // add borders and padding
  mBorderPadding   = aReflowState.mComputedBorderPadding;
  aMetrics.width  += mBorderPadding.left + mBorderPadding.right;
  aMetrics.height += mBorderPadding.top + mBorderPadding.bottom;
  
  if (mPrevInFlow) {
    nscoord y = GetContinuationOffset(&aMetrics.width);
    aMetrics.height -= y + mBorderPadding.top;
    aMetrics.height = PR_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
  PRBool isPaginated;
  aPresContext->IsPaginated(&isPaginated);
  if ((isPaginated) && 
      (NS_UNCONSTRAINEDSIZE != aReflowState.availableHeight) && 
      (aMetrics.height > aReflowState.availableHeight)) { 
    nsCOMPtr<nsIAtom> fType;
    GetFrameType(getter_AddRefs(fType));
    // split an image frame but not an image control frame
    if (nsLayoutAtoms::imageFrame == fType.get()) {
      float p2t;
      aPresContext->GetScaledPixelsToTwips(&p2t);
      // our desired height was greater than 0, so to avoid infinite splitting, use 1 pixel as the min
      aMetrics.height = PR_MAX(NSToCoordRound(p2t), aReflowState.availableHeight);
      aStatus = NS_FRAME_NOT_COMPLETE;
    }
  }
  aMetrics.ascent  = aMetrics.height;
  aMetrics.descent = 0;

  if (nsnull != aMetrics.maxElementSize) {
    // If we have a percentage based width, then our MES width is 0
    if (eStyleUnit_Percent == aReflowState.mStylePosition->mWidth.GetUnit()) {
      aMetrics.maxElementSize->width = 0;
    } else {
      aMetrics.maxElementSize->width = aMetrics.width;
    }
    aMetrics.maxElementSize->height = aMetrics.height;
  }
  if (aMetrics.mFlags & NS_REFLOW_CALC_MAX_WIDTH) {
    aMetrics.mMaximumWidth = aMetrics.width;
  }

  NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
                  ("exit nsImageFrame::Reflow: size=%d,%d",
                  aMetrics.width, aMetrics.height));
  return NS_OK;
}

// 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
void
nsImageFrame::MeasureString(const PRUnichar*     aString,
                            PRInt32              aLength,
                            nscoord              aMaxWidth,
                            PRUint32&            aMaxFit,
                            nsIRenderingContext& aContext)
{
  nscoord totalWidth = 0;
  nscoord spaceWidth;
  aContext.GetWidth(' ', spaceWidth);

  aMaxFit = 0;
  while (aLength > 0) {
    // Find the next place we can line break
    PRUint32  len = aLength;
    PRBool    trailingSpace = PR_FALSE;
    for (PRInt32 i = 0; i < aLength; i++) {
      if (XP_IS_SPACE(aString[i]) && (i > 0)) {
        len = i;  // don't include the space when measuring
        trailingSpace = PR_TRUE;
        break;
      }
    }
  
    // Measure this chunk of text, and see if it fits
    nscoord width;
    aContext.GetWidth(aString, len, width);
    PRBool  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 = PR_FALSE;
        }

        len++;
      }

      aMaxFit += len;
      aString += len;
      aLength -= len;
    }

    if (!fits) {
      break;
    }
  }
}

// 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(nsIPresContext*      aPresContext,
                             nsIRenderingContext& aRenderingContext,
                             const nsString&      aAltText,
                             const nsRect&        aRect)
{
  const nsStyleColor* color =
    (const nsStyleColor*)mStyleContext->GetStyleData(eStyleStruct_Color);
  const nsStyleFont* font =
    (const nsStyleFont*)mStyleContext->GetStyleData(eStyleStruct_Font);

  // Set font and color
  aRenderingContext.SetColor(color->mColor);
  aRenderingContext.SetFont(font->mFont);

  // Format the text to display within the formatting rect
  nsIFontMetrics* fm;
  aRenderingContext.GetFontMetrics(fm);

  nscoord maxAscent, maxDescent, height;
  fm->GetMaxAscent(maxAscent);
  fm->GetMaxDescent(maxDescent);
  fm->GetHeight(height);

  // 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 PRUnichar* str = aAltText.get();
  PRInt32          strLen = aAltText.Length();
  nscoord          y = aRect.y;
  while ((strLen > 0) && ((y + maxDescent) < aRect.YMost())) {
    // Determine how much of the text to display on this line
    PRUint32  maxFit;  // number of characters that fit
    MeasureString(str, strLen, aRect.width, maxFit, aRenderingContext);
    
    // Display the text
    aRenderingContext.DrawString(str, maxFit, aRect.x, y + maxAscent);

    // Move to the next line
    str += maxFit;
    strLen -= maxFit;
    y += height;
  }

  NS_RELEASE(fm);
}

struct nsRecessedBorder : public nsStyleBorder {
  nsRecessedBorder(nscoord aBorderWidth)
    : nsStyleBorder()
  {
    nsStyleCoord  styleCoord(aBorderWidth);

    mBorder.SetLeft(styleCoord);
    mBorder.SetTop(styleCoord);
    mBorder.SetRight(styleCoord);
    mBorder.SetBottom(styleCoord);

    mBorderStyle[0] = NS_STYLE_BORDER_STYLE_INSET;  
    mBorderStyle[1] = NS_STYLE_BORDER_STYLE_INSET;  
    mBorderStyle[2] = NS_STYLE_BORDER_STYLE_INSET;  
    mBorderStyle[3] = NS_STYLE_BORDER_STYLE_INSET;  

    mBorderColor[0] = 0;  
    mBorderColor[1] = 0;  
    mBorderColor[2] = 0;  
    mBorderColor[3] = 0;  

    mHasCachedBorder = PR_FALSE;
  }
};

void
nsImageFrame::DisplayAltFeedback(nsIPresContext*      aPresContext,
                                 nsIRenderingContext& aRenderingContext,
                                 PRInt32              aIconId)
{
  // Calculate the inner area
  nsRect  inner;
  GetInnerArea(aPresContext, inner);

  // Display a recessed one pixel border
  float   p2t;
  nscoord borderEdgeWidth;
  aPresContext->GetScaledPixelsToTwips(&p2t);
  borderEdgeWidth = NSIntPixelsToTwips(ALT_BORDER_WIDTH, p2t);

  // if inner area is empty, then make it big enough for at least the icon
  if (inner.IsEmpty()){
    inner.SizeBy(2*(NSIntPixelsToTwips(ICON_SIZE+ICON_PADDING+ALT_BORDER_WIDTH,p2t)),
                 2*(NSIntPixelsToTwips(ICON_SIZE+ICON_PADDING+ALT_BORDER_WIDTH,p2t)));
  }

  // 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;
  }

  // Paint the border
  nsRecessedBorder recessedBorder(borderEdgeWidth);
  nsCSSRendering::PaintBorder(aPresContext, aRenderingContext, this, inner,
                              inner, recessedBorder, mStyleContext, 0);

  // Adjust the inner rect to account for the one pixel recessed border,
  // and a six pixel padding on each edge
  inner.Deflate(NSIntPixelsToTwips(ICON_PADDING+ALT_BORDER_WIDTH, p2t), 
                NSIntPixelsToTwips(ICON_PADDING+ALT_BORDER_WIDTH, p2t));
  if (inner.IsEmpty()) {
    return;
  }

  // check if the size of the (deflated) rect is big enough to show the icon
  // - if not, then just bail, leaving the border rect by itself
  // NOTE: setting the clip (below) should be enough, but there is a bug in the Linux
  //       rendering context that images are not clipped when a clip rect is set (bugzilla bug 78497)
  //       and also this will be slightly more efficient since we will not try to render any icons
  //       or ALT text into the rect.
  if (inner.width < NSIntPixelsToTwips(ICON_SIZE, p2t) || 
      inner.height < NSIntPixelsToTwips(ICON_SIZE, p2t)) {
    return;
  }

  // Clip so we don't render outside the inner rect
  PRBool clipState;
  aRenderingContext.PushState();
  aRenderingContext.SetClipRect(inner, nsClipCombine_kIntersect, clipState);

  PRBool dispIcon = mIconLoad ? mIconLoad->mPrefShowPlaceholders : PR_TRUE;

  // Check if we should display image placeholders
  if (dispIcon) {
    PRInt32 size = NSIntPixelsToTwips(ICON_SIZE, p2t);

    PRBool iconUsed = PR_FALSE;

    // see if the icon images are present...
    if (mIconLoad && mIconLoad->mIconsLoaded) {
      // pick the correct image
      NS_ASSERTION( aIconId == NS_ICON_LOADING_IMAGE ||
                    aIconId == NS_ICON_BROKEN_IMAGE, "Invalid Icon ID in DisplayAltFeedback");

      nsCOMPtr<imgIContainer> imgCon;
      if (mIconLoad->mIconLoads[aIconId].mRequest) {
        mIconLoad->mIconLoads[aIconId].mRequest->GetImage(getter_AddRefs(imgCon));
      }
      if (imgCon) { 
        // draw it
        nsPoint p(inner.x, inner.y);
        nsRect r(0,0,size,size);
        aRenderingContext.DrawImage(imgCon, &r, &p);
        iconUsed = PR_TRUE;
      }
    }

    // if we could not draw the image, then just draw some grafitti
    if (!iconUsed) {
      nscolor oldColor;
      aRenderingContext.DrawRect(0,0,size,size);
      aRenderingContext.GetColor(oldColor);
      aRenderingContext.SetColor( aIconId == NS_ICON_BROKEN_IMAGE ? NS_RGB(0xFF,0,0) : NS_RGB(0,0xFF,0));
      aRenderingContext.FillEllipse(NS_STATIC_CAST(int,size/2),NS_STATIC_CAST(int,size/2),
                                    NS_STATIC_CAST(int,(size/2)-(2*p2t)),NS_STATIC_CAST(int,(size/2)-(2*p2t)));
      aRenderingContext.SetColor(oldColor);
    }  

    // Reduce the inner rect by the width of the icon, and leave an
    // additional ICON_PADDING pixels for padding
    PRInt32 iconWidth = NSIntPixelsToTwips(ICON_SIZE + ICON_PADDING, p2t);
    inner.x += iconWidth;
    inner.width -= iconWidth;
  }

  // If there's still room, display the alt-text
  if (!inner.IsEmpty()) {
    nsCOMPtr<nsIContent> content;
    nsCOMPtr<nsIAtom>    tag;
    nsAutoString         altText;

    GetContent(getter_AddRefs(content));
    if (content) {
      content->GetTag(*getter_AddRefs(tag));
      if (tag) {
        nsCSSFrameConstructor::GetAlternateTextFor(content, tag, altText);
        DisplayAltText(aPresContext, aRenderingContext, altText, inner);
      }
    }
  }

  aRenderingContext.PopState(clipState);
}

NS_METHOD
nsImageFrame::Paint(nsIPresContext*      aPresContext,
                    nsIRenderingContext& aRenderingContext,
                    const nsRect&        aDirtyRect,
                    nsFramePaintLayer    aWhichLayer,
                    PRUint32             aFlags)
{
  PRBool isVisible;
  if (NS_SUCCEEDED(IsVisibleForPainting(aPresContext, aRenderingContext, PR_TRUE, &isVisible)) && 
      isVisible && mRect.width && mRect.height) {
    // If painting is suppressed, we need to stop image painting.  We
    // have to cover <img> here because of input image controls.
    PRBool paintingSuppressed = PR_FALSE;
    nsCOMPtr<nsIPresShell> shell;
    aPresContext->GetShell(getter_AddRefs(shell));
    shell->IsPaintingSuppressed(&paintingSuppressed);
    if (paintingSuppressed)
      return NS_OK;


    // First paint background and borders, which should be in the
    // FOREGROUND or BACKGROUND paint layer if the element is
    // inline-level or block-level, respectively.  (See CSS2 9.5, which
    // is the rationale for paint layers.)
    const nsStyleDisplay *display;
    ::GetStyleData(NS_STATIC_CAST(nsIStyleContext*, mStyleContext), &display);
    nsFramePaintLayer backgroundLayer = display->IsBlockLevel()
                                            ? NS_FRAME_PAINT_LAYER_BACKGROUND
                                            : NS_FRAME_PAINT_LAYER_FOREGROUND;
    if (aWhichLayer == backgroundLayer) {
      const nsStyleVisibility* vis = 
        (const nsStyleVisibility*)mStyleContext->GetStyleData(eStyleStruct_Visibility);
      if (vis->IsVisibleOrCollapsed()) {
        const nsStyleBorder* myBorder = (const nsStyleBorder*)
          mStyleContext->GetStyleData(eStyleStruct_Border);
        nsRect rect(0, 0, mRect.width, mRect.height);
        nsCSSRendering::PaintBackground(aPresContext, aRenderingContext, this,
                                        aDirtyRect, rect, *myBorder, 0, 0);
        nsCSSRendering::PaintBorder(aPresContext, aRenderingContext, this,
                                    aDirtyRect, rect, *myBorder,
                                    mStyleContext, 0);
      }
    }

    nsCOMPtr<imgIContainer> imgCon;

    PRUint32 loadStatus = imgIRequest::STATUS_ERROR;

    if (mLoads[0].mRequest) {
      mLoads[0].mRequest->GetImage(getter_AddRefs(imgCon));
      mLoads[0].mRequest->GetImageStatus(&loadStatus);
    }

    if (loadStatus & imgIRequest::STATUS_ERROR || !imgCon) {
      // No image yet, or image load failed. Draw the alt-text and an icon
      // indicating the status (unless image is blocked, in which case we show nothing)

#ifndef SUPPRESS_LOADING_ICON
      if (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer &&
          (!mImageBlocked || mIconLoad->mPrefAllImagesBlocked)) {
#else
      if (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer &&
          mInitialLoadCompleted) {
#endif
        DisplayAltFeedback(aPresContext, aRenderingContext, 
                           (loadStatus & imgIRequest::STATUS_ERROR)
                           ? NS_ICON_BROKEN_IMAGE
                           : NS_ICON_LOADING_IMAGE);
      }
    }
    else {
      PRBool paintOutline   = PR_FALSE;
      mInitialLoadCompleted = PR_TRUE;
      if (NS_FRAME_PAINT_LAYER_FOREGROUND == aWhichLayer) {
        // Now render the image into our content area (the area inside the
        // borders and padding)
        nsRect inner;
        GetInnerArea(aPresContext, inner);

        if (loadStatus & imgIRequest::STATUS_ERROR) {
          if (imgCon) {
            inner.SizeTo(mComputedSize);
          }
        }

        if (imgCon) {
          nscoord offsetY = 0; 
          nsSize  size(inner.width, inner.height);
          // if the image is split account for y-offset, border, padding
          if (mPrevInFlow || mNextInFlow) {
            if (mPrevInFlow) {
              offsetY = GetContinuationOffset();
            }
            size.height = mRect.height;
            if (!mPrevInFlow) {
              size.height -= mBorderPadding.top;
            }
            if (!mNextInFlow) {
              size.height -= mBorderPadding.bottom;
            }
          }

          if (mLoads[0].mIntrinsicSize == mComputedSize) {
            inner.IntersectRect(inner, aDirtyRect);
            nsPoint p(inner.x, inner.y);
            nsRect r(inner.x, inner.y, inner.width, inner.height);
            r.x -= mBorderPadding.left;
            r.y -= mBorderPadding.top;
            if (mPrevInFlow) {
              r.y = offsetY;
              p.y = 0; 
            }

            aRenderingContext.DrawImage(imgCon, &r, &p);
          } else {
            nsTransform2D trans;
            trans.SetToScale((float(mLoads[0].mIntrinsicSize.width) / float(inner.width)),
                             (float(mLoads[0].mIntrinsicSize.height) / float(inner.height)));

            nsRect r(0, offsetY, size.width, size.height);

            trans.TransformCoord(&r.x, &r.y, &r.width, &r.height);

            nsRect d(inner.x, inner.y, mComputedSize.width, mComputedSize.height);
            // if the image is split account for y-offset, border, padding
            if (mPrevInFlow || mNextInFlow) {
              if (mPrevInFlow) {
                d.y -= mBorderPadding.top;
              }
              d.height = size.height;
            }

            aRenderingContext.DrawScaledImage(imgCon, &r, &d);
          }
          paintOutline = PR_TRUE;
        }
      }

      nsImageMap* map = GetImageMap(aPresContext);
      if (nsnull != map) {
        nsRect inner;
        GetInnerArea(aPresContext, inner);
        PRBool clipState;
        aRenderingContext.SetColor(NS_RGB(0, 0, 0));
        aRenderingContext.SetLineStyle(nsLineStyle_kDotted);
        aRenderingContext.PushState();
        aRenderingContext.Translate(inner.x, inner.y);
        map->Draw(aPresContext, aRenderingContext);
        aRenderingContext.PopState(clipState);
        paintOutline = PR_TRUE;
      }

      // paint the outline in the overlay layer (or if there is an image map) until the 
      // general problem of painting it outside the border box is solved.
      if (paintOutline) {
        const nsStyleBorder* myBorder = (const nsStyleBorder*)
          mStyleContext->GetStyleData(eStyleStruct_Border);
        const nsStyleOutline* myOutline = (const nsStyleOutline*)
          mStyleContext->GetStyleData(eStyleStruct_Outline);
        nsRect rect(0, 0, mRect.width, mRect.height);
        nsCSSRendering::PaintOutline(aPresContext, aRenderingContext, this,
                                     aDirtyRect, rect, *myBorder,
                                     *myOutline, mStyleContext, 0);
      }

#ifdef DEBUG
      if ((NS_FRAME_PAINT_LAYER_DEBUG == aWhichLayer) &&
          GetShowFrameBorders()) {
        nsImageMap* map = GetImageMap(aPresContext);
        if (nsnull != map) {
          nsRect inner;
          GetInnerArea(aPresContext, inner);
          PRBool clipState;
          aRenderingContext.SetColor(NS_RGB(0, 0, 0));
          aRenderingContext.PushState();
          aRenderingContext.Translate(inner.x, inner.y);
          map->Draw(aPresContext, aRenderingContext);
          aRenderingContext.PopState(clipState);
        }
      }
#endif
    }

  }
  PRInt16 displaySelection = 0;

  nsresult result; 
  nsCOMPtr<nsIPresShell> shell;
  result = aPresContext->GetShell(getter_AddRefs(shell));
  if (NS_FAILED(result))
    return result;
  result = shell->GetSelectionFlags(&displaySelection);
  if (NS_FAILED(result))
    return result;
  if (!(displaySelection & nsISelectionDisplay::DISPLAY_IMAGES))
    return NS_OK;//no need to check the blue border, we cannot be drawn selected
  
  return nsFrame::Paint(aPresContext, aRenderingContext, aDirtyRect, aWhichLayer, nsISelectionDisplay::DISPLAY_IMAGES);
}

nsImageMap*
nsImageFrame::GetImageMap(nsIPresContext* aPresContext)
{
  if (!mImageMap) {
    nsCOMPtr<nsIDocument> doc;
    mContent->GetDocument(*getter_AddRefs(doc));
    if (!doc) {
      return nsnull;
    }

    nsAutoString usemap;
    mContent->GetAttr(kNameSpaceID_HTML, nsHTMLAtoms::usemap, usemap);

    nsCOMPtr<nsIDOMHTMLMapElement> map;
    if (NS_SUCCEEDED(nsImageMapUtils::FindImageMap(doc,usemap,getter_AddRefs(map))) && map) {
      nsCOMPtr<nsIPresShell> presShell;
      aPresContext->GetShell(getter_AddRefs(presShell));

      mImageMap = new nsImageMap();
      if (mImageMap) {
        NS_ADDREF(mImageMap);
        mImageMap->Init(presShell, this, map);
      }
    }
  }

  return mImageMap;
}

void
nsImageFrame::TriggerLink(nsIPresContext* aPresContext,
                          const nsString& aURLSpec,
                          const nsString& aTargetSpec,
                          PRBool aClick)
{
  // We get here with server side image map
  nsILinkHandler* handler = nsnull;
  aPresContext->GetLinkHandler(&handler);
  if (nsnull != handler) {
    if (aClick) {
      nsresult proceed = NS_OK;
      // Check that this page is allowed to load this URI.
      // Almost a copy of the similarly named method in nsGenericElement
      nsresult rv;
      nsCOMPtr<nsIScriptSecurityManager> securityManager = 
               do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);

      nsCOMPtr<nsIPresShell> ps;
      if (NS_SUCCEEDED(rv)) 
        rv = aPresContext->GetShell(getter_AddRefs(ps));
      nsCOMPtr<nsIDocument> doc;
      if (NS_SUCCEEDED(rv) && ps) 
        rv = ps->GetDocument(getter_AddRefs(doc));
      
      nsCOMPtr<nsIURI> baseURI;
      if (NS_SUCCEEDED(rv) && doc) 
        doc->GetDocumentURL(getter_AddRefs(baseURI));
      nsCOMPtr<nsIURI> absURI;
      if (NS_SUCCEEDED(rv)) 
        rv = NS_NewURI(getter_AddRefs(absURI), aURLSpec, nsnull, baseURI);
      
      if (NS_SUCCEEDED(rv)) 
        proceed = securityManager->CheckLoadURI(baseURI, absURI, nsIScriptSecurityManager::STANDARD);

      // Only pass off the click event if the script security manager
      // says it's ok.
      if (NS_SUCCEEDED(proceed))
        handler->OnLinkClick(mContent, eLinkVerb_Replace, aURLSpec.get(), aTargetSpec.get());
    }
    else {
      handler->OnOverLink(mContent, aURLSpec.get(), aTargetSpec.get());
    }
    NS_RELEASE(handler);
  }
}

PRBool
nsImageFrame::IsServerImageMap()
{
  nsAutoString ismap;
  return NS_CONTENT_ATTR_HAS_VALUE ==
    mContent->GetAttr(kNameSpaceID_HTML, nsHTMLAtoms::ismap, ismap);
}

PRIntn
nsImageFrame::GetSuppress()
{
  nsAutoString s;
  if (NS_CONTENT_ATTR_HAS_VALUE ==
      mContent->GetAttr(kNameSpaceID_HTML, nsHTMLAtoms::suppress, s)) {
    if (s.EqualsIgnoreCase("true")) {
      return SUPPRESS;
    } else if (s.EqualsIgnoreCase("false")) {
      return DONT_SUPPRESS;
    }
  }
  return DEFAULT_SUPPRESS;
}

//XXX the event come's in in view relative coords, but really should
//be in frame relative coords by the time it hits our frame.

// Translate an point that is relative to our view (or a containing
// view) into a localized pixel coordinate that is relative to the
// content area of this frame (inside the border+padding).
void
nsImageFrame::TranslateEventCoords(nsIPresContext* aPresContext,
                                   const nsPoint& aPoint,
                                   nsPoint& aResult)
{
  nscoord x = aPoint.x;
  nscoord y = aPoint.y;

  // If we have a view then the event coordinates are already relative
  // to this frame; otherwise we have to adjust the coordinates
  // appropriately.
  nsIView* view;
  GetView(aPresContext, &view);
  if (nsnull == view) {
    nsPoint offset;
    GetOffsetFromView(aPresContext, offset, &view);
    if (nsnull != view) {
      x -= offset.x;
      y -= offset.y;
    }
  }

  // Subtract out border and padding here so that the coordinates are
  // now relative to the content area of this frame.
  nsRect inner;
  GetInnerArea(aPresContext, inner);
  x -= inner.x;
  y -= inner.y;

  // Translate the coordinates from twips to pixels
  float t2p;
  aPresContext->GetTwipsToPixels(&t2p);
  aResult.x = NSTwipsToIntPixels(x, t2p);
  aResult.y = NSTwipsToIntPixels(y, t2p);
}

PRBool
nsImageFrame::GetAnchorHREFAndTarget(nsString& aHref, nsString& aTarget)
{
  PRBool status = PR_FALSE;
  aHref.Truncate();
  aTarget.Truncate();

  // Walk up the content tree, looking for an nsIDOMAnchorElement
  nsCOMPtr<nsIContent> content;
  mContent->GetParent(*getter_AddRefs(content));
  while (content) {
    nsCOMPtr<nsIDOMHTMLAnchorElement> anchor(do_QueryInterface(content));
    if (anchor) {
      anchor->GetHref(aHref);
      if (aHref.Length() > 0) {
        status = PR_TRUE;
      }
      anchor->GetTarget(aTarget);
      break;
    }
    nsCOMPtr<nsIContent> parent;
    content->GetParent(*getter_AddRefs(parent));
    content = parent;
  }
  return status;
}

NS_IMETHODIMP
nsImageFrame::CanContinueTextRun(PRBool& aContinueTextRun) const
{
  // images really CAN continue text runs, but the textFrame needs to be 
  // educated before we can indicate that it can. For now, we handle the fixing up 
  // of max element widths in nsLineLayout::VerticalAlignFrames, but hopefully
  // this can be eliminated and the textFrame can be convinced to handle inlines
  // that take up space in text runs.

  aContinueTextRun = PR_FALSE;
  return NS_OK;
}


NS_IMETHODIMP  
nsImageFrame::GetContentForEvent(nsIPresContext* aPresContext,
                                 nsEvent* aEvent,
                                 nsIContent** aContent)
{
  NS_ENSURE_ARG_POINTER(aContent);
  nsImageMap* map;
  map = GetImageMap(aPresContext);

  if (nsnull != map) {
    nsPoint p;
    TranslateEventCoords(aPresContext, aEvent->point, p);
    nsAutoString absURL, target, altText;
    PRBool suppress;
    PRBool inside = PR_FALSE;
    nsCOMPtr<nsIContent> area;
    inside = map->IsInside(p.x, p.y, getter_AddRefs(area),
                           absURL, target, altText,
                           &suppress);
    if (inside && area) {
      *aContent = area;
      NS_ADDREF(*aContent);
      return NS_OK;
    }
  }

  return GetContent(aContent);
}

// XXX what should clicks on transparent pixels do?
NS_METHOD
nsImageFrame::HandleEvent(nsIPresContext* aPresContext,
                          nsGUIEvent* aEvent,
                          nsEventStatus* aEventStatus)
{
  NS_ENSURE_ARG_POINTER(aEventStatus);
  nsImageMap* map;

  switch (aEvent->message) {
  case NS_MOUSE_LEFT_BUTTON_UP:
  case NS_MOUSE_MOVE:
    {
      map = GetImageMap(aPresContext);
      PRBool isServerMap = IsServerImageMap();
      if ((nsnull != map) || isServerMap) {
        nsPoint p;
        TranslateEventCoords(aPresContext, aEvent->point, p);
        nsAutoString absURL, target, altText;
        PRBool suppress;
        PRBool inside = PR_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 (nsnull != map) {
          nsCOMPtr<nsIContent> area;
          inside = map->IsInside(p.x, p.y, getter_AddRefs(area),
                                 absURL, target, altText,
                                 &suppress);
        }

        if (!inside && isServerMap) {
          suppress = GetSuppress();
          nsCOMPtr<nsIURI> baseURL;
          GetBaseURI(getter_AddRefs(baseURL));
          
          if (baseURL) {
            // Server side image maps use the href in a containing anchor
            // element to provide the basis for the destination url.
            nsAutoString src;
            if (GetAnchorHREFAndTarget(src, target)) {
              NS_MakeAbsoluteURI(absURL, src, baseURL);
            
              // 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;
              char cbuf[50];
              PR_snprintf(cbuf, sizeof(cbuf), "?%d,%d", p.x, p.y);
              absURL.AppendWithConversion(cbuf);
              PRBool clicked = PR_FALSE;
              if (aEvent->message == NS_MOUSE_LEFT_BUTTON_UP) {
                *aEventStatus = nsEventStatus_eConsumeDoDefault; 
                clicked = PR_TRUE;
              }
              TriggerLink(aPresContext, absURL, target, clicked);
            }
          } 
          else 
          {
            NS_WARNING("baseURL is null for imageFrame - ignoring mouse event");
          }
        }
      }
      break;
    }
    default:
      break;
  }

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

//XXX This will need to be rewritten once we have content for areas
NS_METHOD
nsImageFrame::GetCursor(nsIPresContext* aPresContext,
                        nsPoint& aPoint,
                        PRInt32& aCursor)
{
  nsImageMap* map = GetImageMap(aPresContext);
  if (nsnull != map) {
    nsPoint p;
    TranslateEventCoords(aPresContext, aPoint, p);
    aCursor = NS_STYLE_CURSOR_DEFAULT;
    if (map->IsInside(p.x, p.y)) {
      // Use style defined cursor if one is provided, otherwise when
      // the cursor style is "auto" we use the pointer cursor.
      const nsStyleUserInterface* styleUserInterface;
      GetStyleData(eStyleStruct_UserInterface, (const nsStyleStruct*&)styleUserInterface);
      aCursor = styleUserInterface->mCursor;
      if (NS_STYLE_CURSOR_AUTO == aCursor) {
        aCursor = NS_STYLE_CURSOR_POINTER;
      }
    }
    return NS_OK;
  }
  return nsFrame::GetCursor(aPresContext, aPoint, aCursor);
}

NS_IMETHODIMP
nsImageFrame::AttributeChanged(nsIPresContext* aPresContext,
                               nsIContent* aChild,
                               PRInt32 aNameSpaceID,
                               nsIAtom* aAttribute,
                               PRInt32 aModType, 
                               PRInt32 aHint)
{
  nsresult rv = nsSplittableFrame::AttributeChanged(aPresContext, aChild,
                                                    aNameSpaceID, aAttribute, aModType, aHint);
  if (NS_OK != rv) {
    return rv;
  }
  if (nsHTMLAtoms::src == aAttribute) {
    nsAutoString newSRC;
    aChild->GetAttr(kNameSpaceID_None, nsHTMLAtoms::src, newSRC);


    if (mLoads[0].mRequest) {
      PRUint32 loadStatus = imgIRequest::STATUS_ERROR;
      mLoads[0].mRequest->GetImageStatus(&loadStatus);

      if (!(loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE)) {
        mFailureReplace = PR_FALSE; // don't cause a CantRenderReplacedElement call
        mLoads[0].mRequest->Cancel(NS_ERROR_FAILURE);
        mLoads[0].mRequest = nsnull;
      }
    }

    mCanSendLoadEvent = PR_TRUE;

    if (mLoads[1].mRequest) {
      mLoads[1].mRequest->Cancel(NS_ERROR_FAILURE);
      mLoads[1].mRequest = nsnull;
    }

    nsCOMPtr<imgIRequest> req(do_CreateInstance("@mozilla.org/image/request;1"));
    if (!mLoads[0].mRequest) {
      mLoads[0].mRequest = req;
    } else {
      mLoads[1].mRequest = req;
    }

    LoadImage(newSRC, aPresContext, req);
  }
  else if (nsHTMLAtoms::width == aAttribute || nsHTMLAtoms::height == aAttribute)
  { // XXX: could check for new width == old width, and make that a no-op
    nsCOMPtr<nsIPresShell> presShell;
    aPresContext->GetShell(getter_AddRefs(presShell));
    mState |= NS_FRAME_IS_DIRTY;
	  mParent->ReflowDirtyChild(presShell, (nsIFrame*) this);
    // NOTE: mSizeFixed will be updated in Reflow...
  }

  return NS_OK;
}

NS_IMETHODIMP
nsImageFrame::GetFrameType(nsIAtom** aType) const
{
  NS_PRECONDITION(nsnull != aType, "null OUT parameter pointer");
  *aType = nsLayoutAtoms::imageFrame;
  NS_ADDREF(*aType);
  return NS_OK;
}

#ifdef DEBUG
NS_IMETHODIMP
nsImageFrame::List(nsIPresContext* aPresContext, FILE* out, PRInt32 aIndent) const
{
  IndentBy(out, aIndent);
  ListTag(out);
#ifdef DEBUG_waterson
  fprintf(out, " [parent=%p]", mParent);
#endif
  nsIView*  view;
  GetView(aPresContext, &view);
  if (view) {
    fprintf(out, " [view=%p]", view);
  }
  fprintf(out, " {%d,%d,%d,%d}", mRect.x, mRect.y, mRect.width, 
mRect.height);
  if (0 != mState) {
    fprintf(out, " [state=%08x]", mState);
  }
  fprintf(out, " [content=%p]", mContent);

  // output the img src url
  if (mLoads[0].mRequest) {
    nsCOMPtr<nsIURI> uri;
    mLoads[0].mRequest->GetURI(getter_AddRefs(uri));
    nsCAutoString uristr;
    uri->GetAsciiSpec(uristr);
    fprintf(out, " [src=%s]", uristr.get());
  }
  fputs("\n", out);
  return NS_OK;
}
#endif

NS_IMETHODIMP 
nsImageFrame::GetIntrinsicImageSize(nsSize& aSize)
{
  aSize = mLoads[0].mIntrinsicSize;
  return NS_OK;
}

NS_IMETHODIMP
nsImageFrame::GetNaturalImageSize(PRUint32 *naturalWidth, 
                                  PRUint32 *naturalHeight)
{ 
  *naturalWidth = 0;
  *naturalHeight = 0;

  if (mLoads[0].mRequest) {
    nsCOMPtr<imgIContainer> container;
    mLoads[0].mRequest->GetImage(getter_AddRefs(container));
    if (container) {
      PRInt32 w, h;
      container->GetWidth(&w);
      container->GetHeight(&h);

      *naturalWidth = NS_STATIC_CAST(PRUint32, w);
      *naturalHeight = NS_STATIC_CAST(PRUint32, h);
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsImageFrame::GetImageRequest(imgIRequest **aRequest)
{
  *aRequest = mLoads[0].mRequest;
  NS_IF_ADDREF(*aRequest);
  return NS_OK;
}

NS_IMETHODIMP 
nsImageFrame::IsImageComplete(PRBool* aComplete)
{
  NS_ENSURE_ARG_POINTER(aComplete);
  if (!mLoads[0].mRequest) {
    *aComplete = PR_FALSE;
    return NS_OK;
  }

  PRUint32 status;
  mLoads[0].mRequest->GetImageStatus(&status);
  *aComplete = ((status & imgIRequest::STATUS_LOAD_COMPLETE) != 0);

  return NS_OK;
}

nsresult
nsImageFrame::LoadImage(const nsAString& aSpec, nsIPresContext *aPresContext, imgIRequest *aRequest, PRBool aCheckContentPolicy)
{
  nsresult rv = RealLoadImage(aSpec, aPresContext, aRequest, aCheckContentPolicy);

  if (NS_FAILED(rv)) {
    int whichLoad = GetImageLoad(aRequest);
    if (whichLoad == -1) return NS_ERROR_FAILURE;
    mLoads[whichLoad].mRequest = nsnull;
  }

  return rv;
}

nsresult
nsImageFrame::RealLoadImage(const nsAString& aSpec, nsIPresContext *aPresContext, imgIRequest *aRequest, PRBool aCheckContentPolicy)
{
  nsresult rv = NS_OK;

  /* set this to TRUE here in case we return early */
  mInitialLoadCompleted = PR_TRUE;

  /* don't load the image if aSpec is empty */
  if (aSpec.IsEmpty()) return NS_ERROR_FAILURE;

  nsCOMPtr<nsIURI> realURI;

  /* don't load the image if some security check fails... */
  GetRealURI(aSpec, getter_AddRefs(realURI));
  if (aCheckContentPolicy)
    if (!CanLoadImage(realURI)) return NS_ERROR_FAILURE;

  if (!mListener) {
    nsImageListener *listener = new nsImageListener(this);
    if (!listener) return NS_ERROR_OUT_OF_MEMORY;
    NS_ADDREF(listener);
    listener->QueryInterface(NS_GET_IID(imgIDecoderObserver), getter_AddRefs(mListener));
    NS_ASSERTION(mListener, "queryinterface for the listener failed");
    NS_RELEASE(listener);
  }

  nsCOMPtr<imgILoader> il(do_GetService("@mozilla.org/image/loader;1", &rv));
  if (NS_FAILED(rv)) return rv;

  nsCOMPtr<nsILoadGroup> loadGroup;
  GetLoadGroup(aPresContext, getter_AddRefs(loadGroup));

  nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
  if (aPresContext) {
    aPresContext->GetImageLoadFlags(loadFlags);
  }

  /* get the URI, convert internal-gopher-stuff if needed */
  nsCOMPtr<nsIURI> uri;
  GetURI(aSpec, getter_AddRefs(uri));
  if (!uri)
    uri = realURI;

  /* set this back to FALSE before we do the real load */
  mInitialLoadCompleted = PR_FALSE;

  nsCOMPtr<nsIURI> baseURI;
  rv = aPresContext->GetBaseURL(getter_AddRefs(baseURI));
  nsCOMPtr<imgIRequest> tempRequest;
  return il->LoadImage(uri, baseURI, loadGroup, mListener, aPresContext, loadFlags, nsnull, aRequest, getter_AddRefs(tempRequest));
}

#define INTERNAL_GOPHER_LENGTH 16 /* "internal-gopher-" length */

void
nsImageFrame::GetDocumentCharacterSet(nsAString& aCharset) const
{
  nsresult rv;
  nsCOMPtr<nsIHTMLContent> htmlContent(do_QueryInterface(mContent, &rv));
  if (NS_SUCCEEDED(rv)) {
    nsCOMPtr<nsIDocument> doc;
    rv = htmlContent->GetDocument(*getter_AddRefs(doc));
    if (NS_SUCCEEDED(rv))
      (void) doc->GetDocumentCharacterSet(aCharset);
  }
}

void
nsImageFrame::GetURI(const nsAString& aSpec, nsIURI **aURI)
{
  *aURI = nsnull;

  /* Note: navigator 4.* and earlier releases ignored the base tags
     effect on the builtin images. So we do too. Use aSpec instead
     of the absolute url...
   */

  /* The prefix for special "internal" images that are well known.
     Look and see if this is an internal-gopher- url.
   */
  if (NS_LITERAL_STRING("internal-gopher-").Equals(Substring(aSpec, 0, INTERNAL_GOPHER_LENGTH))) {
    nsAutoString newURI;
    newURI.Assign(NS_LITERAL_STRING("resource:/res/html/gopher-") +
                  Substring(aSpec, INTERNAL_GOPHER_LENGTH, aSpec.Length() - INTERNAL_GOPHER_LENGTH) +
                  NS_LITERAL_STRING(".gif"));
    GetRealURI(newURI, aURI);
  }
}

void
nsImageFrame::GetRealURI(const nsAString& aSpec, nsIURI **aURI)
{
  nsCOMPtr<nsIURI> baseURI;
  GetBaseURI(getter_AddRefs(baseURI));
  nsAutoString charset;
  GetDocumentCharacterSet(charset);
  NS_NewURI(aURI, aSpec, 
            charset.IsEmpty() ? nsnull : NS_ConvertUCS2toUTF8(charset).get(), 
            baseURI);
}

void
nsImageFrame::GetBaseURI(nsIURI **aURI)
{
  NS_PRECONDITION(nsnull != aURI, "null OUT parameter pointer");

  nsresult rv;
  nsCOMPtr<nsIURI> baseURI;
  nsCOMPtr<nsIHTMLContent> htmlContent(do_QueryInterface(mContent, &rv));
  if (NS_SUCCEEDED(rv)) {
    htmlContent->GetBaseURL(*getter_AddRefs(baseURI));
  }
  else {
    nsCOMPtr<nsIDocument> doc;
    if (mContent) {
      rv = mContent->GetDocument(*getter_AddRefs(doc));
      if (doc) {
        doc->GetBaseURL(*getter_AddRefs(baseURI));
      }
    }
  }
  *aURI = baseURI;
  NS_IF_ADDREF(*aURI);
}

void
nsImageFrame::GetLoadGroup(nsIPresContext *aPresContext, nsILoadGroup **aLoadGroup)
{
  if (!aPresContext)
    return;

  NS_PRECONDITION(nsnull != aLoadGroup, "null OUT parameter pointer");

  nsCOMPtr<nsIPresShell> shell;
  aPresContext->GetShell(getter_AddRefs(shell));

  if (!shell)
    return;

  nsCOMPtr<nsIDocument> doc;
  shell->GetDocument(getter_AddRefs(doc));
  if (!doc)
    return;

  doc->GetDocumentLoadGroup(aLoadGroup);
}


PRBool
nsImageFrame::CanLoadImage(nsIURI *aURI)
{

  PRBool shouldLoad = PR_TRUE; // default permit

  // Check with the content-policy things to make sure this load is permitted.
  nsresult rv;
  nsCOMPtr<nsIDOMElement> element(do_QueryInterface(mContent));

  if (!element) // this would seem bad(tm)
    return shouldLoad;

  nsCOMPtr<nsIDocument> document;
  if (mContent) {
    rv = mContent->GetDocument(*getter_AddRefs(document));
    if (! document) {
      NS_ASSERTION(0, "expecting a document");
      return shouldLoad;
    }

    nsCOMPtr<nsIScriptGlobalObject> globalScript;
    rv = document->GetScriptGlobalObject(getter_AddRefs(globalScript));
    if (NS_FAILED(rv)) return shouldLoad;

    nsCOMPtr<nsIDOMWindow> domWin(do_QueryInterface(globalScript));

    rv = NS_CheckContentLoadPolicy(nsIContentPolicy::IMAGE,
                                   aURI, element, domWin, &shouldLoad);
    if (NS_SUCCEEDED(rv) && !shouldLoad) {
      // this image has been blocked, so flag it
      mImageBlocked = PR_TRUE;
      return shouldLoad;
    }
  }
   
  /* ... additional checks ? */

  return shouldLoad;
}

nsresult nsImageFrame::LoadIcons(nsIPresContext *aPresContext)
{
  NS_NAMED_LITERAL_STRING(loadingSrc,"resource:/res/loading-image.gif"); 
  NS_NAMED_LITERAL_STRING(brokenSrc,"resource:/res/broken-image.gif");

  nsresult rv = NS_OK;
  PRBool doLoad = PR_FALSE;  // only load icons once...

  // see if the first instance and we need to create the icon loader
  if (!mIconLoad) {
#ifdef NOISY_ICON_LOADING
      printf( "Allocating IconLoad (%p)\n", this);
#endif
    mIconLoad = new IconLoad(aPresContext);
    if (!mIconLoad) 
      return NS_ERROR_OUT_OF_MEMORY;
    doLoad = PR_TRUE;
  }
  // always addref
  mIconLoad->AddRef();

#ifdef NOISY_ICON_LOADING
   printf( "IconLoad AddRef'd(%p)\n", this);
#endif

  // if already loaded, we are done
  if (mIconLoad->mIconsLoaded) return rv;

  if (doLoad) {
    // create a loader and load the images
    mIconLoad->mIconLoads[NS_ICON_LOADING_IMAGE].mRequest = do_CreateInstance("@mozilla.org/image/request;1");
    if (mIconLoad->mIconLoads[NS_ICON_LOADING_IMAGE].mRequest) {
#ifdef NOISY_ICON_LOADING
      printf( "Loading request %p\n", mIconLoad->mIconLoads[NS_ICON_LOADING_IMAGE].mRequest.get() );
#endif
      rv = LoadImage(loadingSrc, aPresContext, mIconLoad->mIconLoads[NS_ICON_LOADING_IMAGE].mRequest, PR_FALSE);
      if (NS_SUCCEEDED(rv)) {
        mIconLoad->mIconLoads[NS_ICON_BROKEN_IMAGE].mRequest = do_CreateInstance("@mozilla.org/image/request;1");
        if (mIconLoad->mIconLoads[NS_ICON_BROKEN_IMAGE].mRequest) {
#ifdef NOISY_ICON_LOADING
          printf( "Loading request %p\n", mIconLoad->mIconLoads[NS_ICON_BROKEN_IMAGE].mRequest.get() );
#endif
          rv = LoadImage(brokenSrc, aPresContext, mIconLoad->mIconLoads[NS_ICON_BROKEN_IMAGE].mRequest, PR_FALSE);
          // ImageLoader will callback into OnStartContainer, which will handle the mIconsLoaded flag
        }
      }
    }
  }
  return rv;
}

PRBool nsImageFrame::HandleIconLoads(imgIRequest* aRequest, PRBool aLoaded)
{
  PRBool result = PR_FALSE;

  if( mIconLoad ) {
    // check which image it is
    if (aRequest == mIconLoad->mIconLoads[NS_ICON_LOADING_IMAGE].mRequest.get()) {
      result = PR_TRUE;
    } else if (aRequest == mIconLoad->mIconLoads[NS_ICON_BROKEN_IMAGE].mRequest.get()) {
      result = PR_TRUE;
    }
    if (result) {
      mIconLoad->mIconsLoaded = aLoaded;
    }

#ifdef NOISY_ICON_LOADING
    if (mIconLoad->mIconsLoaded && result) {
      printf( "Icons Loaded: request for %s\n", aRequest == mIconLoad->mIconLoads[NS_ICON_LOADING_IMAGE].mRequest ?
                                                "NS_ICON_LOADING_IMAGE" : "NS_ICON_BROKEN_IMAGE" );
    }
#endif
  }
#ifdef NOISY_ICON_LOADING
  printf( "HandleIconLoads returned %s (%p)\n", result ? "TRUE" : "FALSE", this);
#endif

  return result;
}

void nsImageFrame::InvalidateIcon(nsIPresContext *aPresContext)
{
  // invalidate the inner area, where the icon lives

  NS_ASSERTION(aPresContext, "null presContext in InvalidateIcon");
  float   p2t;
  aPresContext->GetScaledPixelsToTwips(&p2t);
  nsRect inner;
  GetInnerArea(aPresContext, inner);

  nsRect rect(inner.x,
              inner.y,
              NSIntPixelsToTwips(ICON_SIZE+ICON_PADDING, p2t),
              NSIntPixelsToTwips(ICON_SIZE+ICON_PADDING, p2t));
  NS_ASSERTION(!rect.IsEmpty(), "icon rect cannot be empty!");
  Invalidate(aPresContext, rect, PR_FALSE);
}

void nsImageFrame::IconLoad::GetPrefs(nsIPresContext *aPresContext)
{
  NS_ASSERTION(aPresContext, "null presContext is not allowed in GetAltModePref");
  // NOTE: the presContext could be used to fetch a cached pref if needed, but is not for now

  nsCOMPtr<nsIPref> prefs = do_GetService(NS_PREF_CONTRACTID);
  if (prefs) {
    PRBool boolPref;
    PRInt32 intPref;
    if (NS_SUCCEEDED(prefs->GetBoolPref("browser.display.force_inline_alttext", &boolPref))) {
      mPrefForceInlineAltText = boolPref;
    } else {
      mPrefForceInlineAltText = PR_FALSE;
    }
    if (NS_SUCCEEDED(prefs->GetIntPref("network.image.imageBehavior", &intPref)) && intPref == 2) {
      mPrefAllImagesBlocked = PR_TRUE;
    } else {
      mPrefAllImagesBlocked = PR_FALSE;
    }
    if (NS_SUCCEEDED(prefs->GetBoolPref("browser.display.show_image_placeholders", &boolPref))) {
      mPrefShowPlaceholders = boolPref;
    } else {
      mPrefShowPlaceholders = PR_TRUE;
    }
  }
}

#ifdef DEBUG
NS_IMETHODIMP
nsImageFrame::SizeOf(nsISizeOfHandler* aHandler, PRUint32* aResult) const
{
#if 0
  if (!aResult) {
    return NS_ERROR_NULL_POINTER;
  }
  PRUint32 sum;
  mImageLoader.SizeOf(aHandler, &sum);
  sum += sizeof(*this) - sizeof(mImageLoader);
  if (mImageMap) {
    PRBool recorded;
    aHandler->RecordObject((void*) mImageMap, &recorded);
    if (!recorded) {
      PRUint32 mapSize;
      mImageMap->SizeOf(aHandler, &mapSize);
      aHandler->AddSize(nsLayoutAtoms::imageMap, mapSize);
    }
  }
  *aResult = sum;
#endif
  *aResult = 0;

  return NS_OK;
}
#endif


NS_IMPL_ISUPPORTS2(nsImageListener, imgIDecoderObserver, imgIContainerObserver)

nsImageListener::nsImageListener(nsImageFrame *aFrame) :
  mFrame(aFrame)
{
  NS_INIT_ISUPPORTS();
}

nsImageListener::~nsImageListener()
{
}

NS_IMETHODIMP nsImageListener::OnStartDecode(imgIRequest *aRequest, nsISupports *aContext)
{
  if (!mFrame)
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsIPresContext> pc(do_QueryInterface(aContext));

  NS_ASSERTION(pc, "not a pres context!");

  return mFrame->OnStartDecode(aRequest, pc);
}

NS_IMETHODIMP nsImageListener::OnStartContainer(imgIRequest *aRequest, nsISupports *aContext, imgIContainer *aImage)
{
  if (!mFrame)
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsIPresContext> pc(do_QueryInterface(aContext));

  NS_ASSERTION(pc, "not a pres context!");

  return mFrame->OnStartContainer(aRequest, pc, aImage);
}

NS_IMETHODIMP nsImageListener::OnStartFrame(imgIRequest *aRequest, nsISupports *aContext, gfxIImageFrame *aFrame)
{
  if (!mFrame)
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsIPresContext> pc(do_QueryInterface(aContext));

  NS_ASSERTION(pc, "not a pres context!");

  return mFrame->OnStartFrame(aRequest, pc, aFrame);
}

NS_IMETHODIMP nsImageListener::OnDataAvailable(imgIRequest *aRequest, nsISupports *aContext, gfxIImageFrame *aFrame, const nsRect *aRect)
{
  if (!mFrame)
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsIPresContext> pc(do_QueryInterface(aContext));

  NS_ASSERTION(pc, "not a pres context!");

  return mFrame->OnDataAvailable(aRequest, pc, aFrame, aRect);
}

NS_IMETHODIMP nsImageListener::OnStopFrame(imgIRequest *aRequest, nsISupports *aContext, gfxIImageFrame *aFrame)
{
  if (!mFrame)
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsIPresContext> pc(do_QueryInterface(aContext));

  NS_ASSERTION(pc, "not a pres context!");

  return mFrame->OnStopFrame(aRequest, pc, aFrame);
}

NS_IMETHODIMP nsImageListener::OnStopContainer(imgIRequest *aRequest, nsISupports *aContext, imgIContainer *aImage)
{
  if (!mFrame)
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsIPresContext> pc(do_QueryInterface(aContext));

  NS_ASSERTION(pc, "not a pres context!");

  return mFrame->OnStopContainer(aRequest, pc, aImage);
}

NS_IMETHODIMP nsImageListener::OnStopDecode(imgIRequest *aRequest, nsISupports *aContext, nsresult status, const PRUnichar *statusArg)
{
  if (!mFrame)
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsIPresContext> pc(do_QueryInterface(aContext));

  NS_ASSERTION(pc, "not a pres context!");

  return mFrame->OnStopDecode(aRequest, pc, status, statusArg);
}

NS_IMETHODIMP nsImageListener::FrameChanged(imgIContainer *aContainer, nsISupports *aContext, gfxIImageFrame *newframe, nsRect * dirtyRect)
{
  if (!mFrame)
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsIPresContext> pc(do_QueryInterface(aContext));

  NS_ASSERTION(pc, "not a pres context!");

  return mFrame->FrameChanged(aContainer, pc, newframe, dirtyRect);
}

