Bug 508473 part III: Pass destruction root to frame destruction methods r=bz sr=roc
[gecko.git] / layout / xul / base / src / nsImageBoxFrame.cpp
blob445d0f50b9ca86a3b43b2ef78d74d88acf69630d
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla Communicator client code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
24 * Alternatively, the contents of this file may be used under the terms of
25 * either of the GNU General Public License Version 2 or later (the "GPL"),
26 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
39 // Eric Vaughan
40 // Netscape Communications
42 // See documentation in associated header file
45 #include "nsImageBoxFrame.h"
46 #include "nsIDeviceContext.h"
47 #include "nsIFontMetrics.h"
48 #include "nsGkAtoms.h"
49 #include "nsStyleContext.h"
50 #include "nsStyleConsts.h"
51 #include "nsCOMPtr.h"
52 #include "nsPresContext.h"
53 #include "nsBoxLayoutState.h"
55 #include "nsHTMLParts.h"
56 #include "nsString.h"
57 #include "nsLeafFrame.h"
58 #include "nsPresContext.h"
59 #include "nsIRenderingContext.h"
60 #include "nsIPresShell.h"
61 #include "nsIDocument.h"
62 #include "nsIHTMLDocument.h"
63 #include "nsStyleConsts.h"
64 #include "nsImageMap.h"
65 #include "nsILinkHandler.h"
66 #include "nsIURL.h"
67 #include "nsILoadGroup.h"
68 #include "nsHTMLContainerFrame.h"
69 #include "prprf.h"
70 #include "nsIFontMetrics.h"
71 #include "nsCSSRendering.h"
72 #include "nsIDOMHTMLImageElement.h"
73 #include "nsIDeviceContext.h"
74 #include "nsINameSpaceManager.h"
75 #include "nsTextFragment.h"
76 #include "nsIDOMHTMLMapElement.h"
77 #include "nsBoxLayoutState.h"
78 #include "nsIDOMDocument.h"
79 #include "nsTransform2D.h"
80 #include "nsITheme.h"
82 #include "nsIServiceManager.h"
83 #include "nsIURI.h"
84 #include "nsNetUtil.h"
85 #include "nsThreadUtils.h"
86 #include "nsGUIEvent.h"
87 #include "nsEventDispatcher.h"
88 #include "nsDisplayList.h"
90 #include "nsContentUtils.h"
92 #define ONLOAD_CALLED_TOO_EARLY 1
94 class nsImageBoxFrameEvent : public nsRunnable
96 public:
97 nsImageBoxFrameEvent(nsIContent *content, PRUint32 message)
98 : mContent(content), mMessage(message) {}
100 NS_IMETHOD Run();
102 private:
103 nsCOMPtr<nsIContent> mContent;
104 PRUint32 mMessage;
107 NS_IMETHODIMP
108 nsImageBoxFrameEvent::Run()
110 nsIDocument* doc = mContent->GetOwnerDoc();
111 if (!doc) {
112 return NS_OK;
115 nsIPresShell *pres_shell = doc->GetPrimaryShell();
116 if (!pres_shell) {
117 return NS_OK;
120 nsCOMPtr<nsPresContext> pres_context = pres_shell->GetPresContext();
121 if (!pres_context) {
122 return NS_OK;
125 nsEventStatus status = nsEventStatus_eIgnore;
126 nsEvent event(PR_TRUE, mMessage);
128 event.flags |= NS_EVENT_FLAG_CANT_BUBBLE;
129 nsEventDispatcher::Dispatch(mContent, pres_context, &event, nsnull, &status);
130 return NS_OK;
133 // Fire off an event that'll asynchronously call the image elements
134 // onload handler once handled. This is needed since the image library
135 // can't decide if it wants to call it's observer methods
136 // synchronously or asynchronously. If an image is loaded from the
137 // cache the notifications come back synchronously, but if the image
138 // is loaded from the netswork the notifications come back
139 // asynchronously.
141 void
142 FireImageDOMEvent(nsIContent* aContent, PRUint32 aMessage)
144 NS_ASSERTION(aMessage == NS_LOAD || aMessage == NS_LOAD_ERROR,
145 "invalid message");
147 nsCOMPtr<nsIRunnable> event = new nsImageBoxFrameEvent(aContent, aMessage);
148 if (NS_FAILED(NS_DispatchToCurrentThread(event)))
149 NS_WARNING("failed to dispatch image event");
153 // NS_NewImageBoxFrame
155 // Creates a new image frame and returns it
157 nsIFrame*
158 NS_NewImageBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
160 return new (aPresShell) nsImageBoxFrame (aPresShell, aContext);
163 NS_IMPL_FRAMEARENA_HELPERS(nsImageBoxFrame)
165 NS_IMETHODIMP
166 nsImageBoxFrame::AttributeChanged(PRInt32 aNameSpaceID,
167 nsIAtom* aAttribute,
168 PRInt32 aModType)
170 nsresult rv = nsLeafBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
171 aModType);
173 if (aAttribute == nsGkAtoms::src) {
174 UpdateImage();
175 PresContext()->PresShell()->
176 FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
178 else if (aAttribute == nsGkAtoms::validate)
179 UpdateLoadFlags();
181 return rv;
184 nsImageBoxFrame::nsImageBoxFrame(nsIPresShell* aShell, nsStyleContext* aContext):
185 nsLeafBoxFrame(aShell, aContext),
186 mIntrinsicSize(0,0),
187 mLoadFlags(nsIRequest::LOAD_NORMAL),
188 mUseSrcAttr(PR_FALSE),
189 mSuppressStyleCheck(PR_FALSE)
191 MarkIntrinsicWidthsDirty();
194 nsImageBoxFrame::~nsImageBoxFrame()
199 /* virtual */ void
200 nsImageBoxFrame::MarkIntrinsicWidthsDirty()
202 SizeNeedsRecalc(mImageSize);
203 nsLeafBoxFrame::MarkIntrinsicWidthsDirty();
206 void
207 nsImageBoxFrame::DestroyFrom(nsIFrame* aDestructRoot)
209 // Release image loader first so that it's refcnt can go to zero
210 if (mImageRequest)
211 mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
213 if (mListener)
214 reinterpret_cast<nsImageBoxListener*>(mListener.get())->SetFrame(nsnull); // set the frame to null so we don't send messages to a dead object.
216 nsLeafBoxFrame::DestroyFrom(aDestructRoot);
220 NS_IMETHODIMP
221 nsImageBoxFrame::Init(nsIContent* aContent,
222 nsIFrame* aParent,
223 nsIFrame* aPrevInFlow)
225 if (!mListener) {
226 nsImageBoxListener *listener;
227 NS_NEWXPCOM(listener, nsImageBoxListener);
228 NS_ADDREF(listener);
229 listener->SetFrame(this);
230 listener->QueryInterface(NS_GET_IID(imgIDecoderObserver), getter_AddRefs(mListener));
231 NS_RELEASE(listener);
234 mSuppressStyleCheck = PR_TRUE;
235 nsresult rv = nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow);
236 mSuppressStyleCheck = PR_FALSE;
238 UpdateLoadFlags();
239 UpdateImage();
241 return rv;
244 void
245 nsImageBoxFrame::UpdateImage()
247 if (mImageRequest) {
248 mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
249 mImageRequest = nsnull;
252 // get the new image src
253 nsAutoString src;
254 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src);
255 mUseSrcAttr = !src.IsEmpty();
256 if (mUseSrcAttr) {
257 nsIDocument* doc = mContent->GetDocument();
258 if (!doc) {
259 // No need to do anything here...
260 return;
262 nsCOMPtr<nsIURI> baseURI = mContent->GetBaseURI();
263 nsCOMPtr<nsIURI> uri;
264 nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri),
265 src,
266 doc,
267 baseURI);
269 if (uri && nsContentUtils::CanLoadImage(uri, mContent, doc,
270 mContent->NodePrincipal())) {
271 nsContentUtils::LoadImage(uri, doc, mContent->NodePrincipal(),
272 doc->GetDocumentURI(), mListener, mLoadFlags,
273 getter_AddRefs(mImageRequest));
275 } else {
276 // Only get the list-style-image if we aren't being drawn
277 // by a native theme.
278 PRUint8 appearance = GetStyleDisplay()->mAppearance;
279 if (!(appearance && nsBox::gTheme &&
280 nsBox::gTheme->ThemeSupportsWidget(nsnull, this, appearance))) {
281 // get the list-style-image
282 imgIRequest *styleRequest = GetStyleList()->GetListStyleImage();
283 if (styleRequest) {
284 styleRequest->Clone(mListener, getter_AddRefs(mImageRequest));
289 if (!mImageRequest) {
290 // We have no image, so size to 0
291 mIntrinsicSize.SizeTo(0, 0);
295 void
296 nsImageBoxFrame::UpdateLoadFlags()
298 static nsIContent::AttrValuesArray strings[] =
299 {&nsGkAtoms::always, &nsGkAtoms::never, nsnull};
300 switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::validate,
301 strings, eCaseMatters)) {
302 case 0:
303 mLoadFlags = nsIRequest::VALIDATE_ALWAYS;
304 break;
305 case 1:
306 mLoadFlags = nsIRequest::VALIDATE_NEVER|nsIRequest::LOAD_FROM_CACHE;
307 break;
308 default:
309 mLoadFlags = nsIRequest::LOAD_NORMAL;
310 break;
314 class nsDisplayXULImage : public nsDisplayItem {
315 public:
316 nsDisplayXULImage(nsImageBoxFrame* aFrame) : nsDisplayItem(aFrame) {
317 MOZ_COUNT_CTOR(nsDisplayXULImage);
319 #ifdef NS_BUILD_REFCNT_LOGGING
320 virtual ~nsDisplayXULImage() {
321 MOZ_COUNT_DTOR(nsDisplayXULImage);
323 #endif
325 // Doesn't handle HitTest because nsLeafBoxFrame already creates an
326 // event receiver for us
327 virtual void Paint(nsDisplayListBuilder* aBuilder,
328 nsIRenderingContext* aCtx);
329 NS_DISPLAY_DECL_NAME("XULImage")
332 void nsDisplayXULImage::Paint(nsDisplayListBuilder* aBuilder,
333 nsIRenderingContext* aCtx)
335 static_cast<nsImageBoxFrame*>(mFrame)->
336 PaintImage(*aCtx, mVisibleRect, aBuilder->ToReferenceFrame(mFrame),
337 aBuilder->ShouldSyncDecodeImages()
338 ? (PRUint32) imgIContainer::FLAG_SYNC_DECODE
339 : (PRUint32) imgIContainer::FLAG_NONE);
342 NS_IMETHODIMP
343 nsImageBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
344 const nsRect& aDirtyRect,
345 const nsDisplayListSet& aLists)
347 nsresult rv = nsLeafBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
348 NS_ENSURE_SUCCESS(rv, rv);
350 if ((0 == mRect.width) || (0 == mRect.height)) {
351 // Do not render when given a zero area. This avoids some useless
352 // scaling work while we wait for our image dimensions to arrive
353 // asynchronously.
354 return NS_OK;
357 if (!IsVisibleForPainting(aBuilder))
358 return NS_OK;
360 return aLists.Content()->AppendNewToTop(new (aBuilder) nsDisplayXULImage(this));
363 void
364 nsImageBoxFrame::PaintImage(nsIRenderingContext& aRenderingContext,
365 const nsRect& aDirtyRect, nsPoint aPt,
366 PRUint32 aFlags)
368 nsRect rect;
369 GetClientRect(rect);
371 rect += aPt;
373 if (!mImageRequest)
374 return;
376 // don't draw if the image is not dirty
377 nsRect dirty;
378 if (!dirty.IntersectRect(aDirtyRect, rect))
379 return;
381 nsCOMPtr<imgIContainer> imgCon;
382 mImageRequest->GetImage(getter_AddRefs(imgCon));
384 if (imgCon) {
385 PRBool hasSubRect = !mUseSrcAttr && (mSubRect.width > 0 || mSubRect.height > 0);
386 nsLayoutUtils::DrawSingleImage(&aRenderingContext, imgCon,
387 nsLayoutUtils::GetGraphicsFilterForFrame(this),
388 rect, dirty, aFlags, hasSubRect ? &mSubRect : nsnull);
394 // DidSetStyleContext
396 // When the style context changes, make sure that all of our image is up to date.
398 /* virtual */ void
399 nsImageBoxFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
401 nsLeafBoxFrame::DidSetStyleContext(aOldStyleContext);
403 // Fetch our subrect.
404 const nsStyleList* myList = GetStyleList();
405 mSubRect = myList->mImageRegion; // before |mSuppressStyleCheck| test!
407 if (mUseSrcAttr || mSuppressStyleCheck)
408 return; // No more work required, since the image isn't specified by style.
410 // If we're using a native theme implementation, we shouldn't draw anything.
411 const nsStyleDisplay* disp = GetStyleDisplay();
412 if (disp->mAppearance && nsBox::gTheme &&
413 nsBox::gTheme->ThemeSupportsWidget(nsnull, this, disp->mAppearance))
414 return;
416 // If list-style-image changes, we have a new image.
417 nsCOMPtr<nsIURI> oldURI, newURI;
418 if (mImageRequest)
419 mImageRequest->GetURI(getter_AddRefs(oldURI));
420 if (myList->GetListStyleImage())
421 myList->GetListStyleImage()->GetURI(getter_AddRefs(newURI));
422 PRBool equal;
423 if (newURI == oldURI || // handles null==null
424 (newURI && oldURI &&
425 NS_SUCCEEDED(newURI->Equals(oldURI, &equal)) && equal))
426 return;
428 UpdateImage();
429 } // DidSetStyleContext
431 void
432 nsImageBoxFrame::GetImageSize()
434 if (mIntrinsicSize.width > 0 && mIntrinsicSize.height > 0) {
435 mImageSize.width = mIntrinsicSize.width;
436 mImageSize.height = mIntrinsicSize.height;
437 } else {
438 mImageSize.width = 0;
439 mImageSize.height = 0;
445 * Ok return our dimensions
447 nsSize
448 nsImageBoxFrame::GetPrefSize(nsBoxLayoutState& aState)
450 nsSize size(0,0);
451 DISPLAY_PREF_SIZE(this, size);
452 if (DoesNeedRecalc(mImageSize))
453 GetImageSize();
455 if (!mUseSrcAttr && (mSubRect.width > 0 || mSubRect.height > 0))
456 size = nsSize(mSubRect.width, mSubRect.height);
457 else
458 size = mImageSize;
459 AddBorderAndPadding(size);
460 nsIBox::AddCSSPrefSize(aState, this, size);
462 nsSize minSize = GetMinSize(aState);
463 nsSize maxSize = GetMaxSize(aState);
465 return BoundsCheck(minSize, size, maxSize);
468 nsSize
469 nsImageBoxFrame::GetMinSize(nsBoxLayoutState& aState)
471 // An image can always scale down to (0,0).
472 nsSize size(0,0);
473 DISPLAY_MIN_SIZE(this, size);
474 AddBorderAndPadding(size);
475 nsIBox::AddCSSMinSize(aState, this, size);
476 return size;
479 nscoord
480 nsImageBoxFrame::GetBoxAscent(nsBoxLayoutState& aState)
482 return GetPrefSize(aState).height;
485 nsIAtom*
486 nsImageBoxFrame::GetType() const
488 return nsGkAtoms::imageBoxFrame;
491 #ifdef DEBUG
492 NS_IMETHODIMP
493 nsImageBoxFrame::GetFrameName(nsAString& aResult) const
495 return MakeFrameName(NS_LITERAL_STRING("ImageBox"), aResult);
497 #endif
500 NS_IMETHODIMP nsImageBoxFrame::OnStartContainer(imgIRequest *request,
501 imgIContainer *image)
503 NS_ENSURE_ARG_POINTER(image);
505 // Ensure the animation (if any) is started
506 image->StartAnimation();
508 nscoord w, h;
509 image->GetWidth(&w);
510 image->GetHeight(&h);
512 mIntrinsicSize.SizeTo(nsPresContext::CSSPixelsToAppUnits(w),
513 nsPresContext::CSSPixelsToAppUnits(h));
515 if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
516 PresContext()->PresShell()->
517 FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
520 return NS_OK;
523 NS_IMETHODIMP nsImageBoxFrame::OnStopContainer(imgIRequest *request,
524 imgIContainer *image)
526 nsBoxLayoutState state(PresContext());
527 this->Redraw(state);
529 return NS_OK;
532 NS_IMETHODIMP nsImageBoxFrame::OnStopDecode(imgIRequest *request,
533 nsresult aStatus,
534 const PRUnichar *statusArg)
536 if (NS_SUCCEEDED(aStatus))
537 // Fire an onload DOM event.
538 FireImageDOMEvent(mContent, NS_LOAD);
539 else {
540 // Fire an onerror DOM event.
541 mIntrinsicSize.SizeTo(0, 0);
542 PresContext()->PresShell()->
543 FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
544 FireImageDOMEvent(mContent, NS_LOAD_ERROR);
547 return NS_OK;
550 NS_IMETHODIMP nsImageBoxFrame::FrameChanged(imgIContainer *container,
551 nsIntRect *dirtyRect)
553 nsBoxLayoutState state(PresContext());
554 this->Redraw(state);
556 return NS_OK;
559 NS_IMPL_ISUPPORTS2(nsImageBoxListener, imgIDecoderObserver, imgIContainerObserver)
561 nsImageBoxListener::nsImageBoxListener()
565 nsImageBoxListener::~nsImageBoxListener()
569 NS_IMETHODIMP nsImageBoxListener::OnStartContainer(imgIRequest *request,
570 imgIContainer *image)
572 if (!mFrame)
573 return NS_ERROR_FAILURE;
575 return mFrame->OnStartContainer(request, image);
578 NS_IMETHODIMP nsImageBoxListener::OnStopContainer(imgIRequest *request,
579 imgIContainer *image)
581 if (!mFrame)
582 return NS_ERROR_FAILURE;
584 return mFrame->OnStopContainer(request, image);
587 NS_IMETHODIMP nsImageBoxListener::OnStopDecode(imgIRequest *request,
588 nsresult status,
589 const PRUnichar *statusArg)
591 if (!mFrame)
592 return NS_ERROR_FAILURE;
594 return mFrame->OnStopDecode(request, status, statusArg);
597 NS_IMETHODIMP nsImageBoxListener::FrameChanged(imgIContainer *container,
598 nsIntRect *dirtyRect)
600 if (!mFrame)
601 return NS_ERROR_FAILURE;
603 return mFrame->FrameChanged(container, dirtyRect);