From 8abe613d4d8dd89c1ccdf5e7d8abefbe95e2cdbd Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Sat, 7 Apr 2012 08:58:41 -0700 Subject: [PATCH] Bug 697230: Part 1 - Centralize style image observers. r=bz --- content/base/public/nsIDocument.h | 13 +- content/base/src/nsDocument.cpp | 16 +- layout/base/Makefile.in | 1 - layout/base/nsCSSRendering.cpp | 24 +- layout/base/nsImageLoader.cpp | 284 ------------------- layout/base/nsImageLoader.h | 115 -------- layout/base/nsPresContext.cpp | 124 +-------- layout/base/nsPresContext.h | 50 ---- layout/base/nsPresShell.cpp | 12 +- layout/generic/nsFrame.cpp | 41 ++- layout/style/ImageLoader.cpp | 449 ++++++++++++++++++++++++++++++ layout/style/ImageLoader.h | 124 +++++++++ layout/style/Makefile.in | 2 + layout/style/nsCSSDataBlock.cpp | 14 +- layout/style/nsCSSValue.cpp | 43 ++- layout/style/nsCSSValue.h | 13 +- layout/style/nsRuleNode.cpp | 41 ++- 17 files changed, 748 insertions(+), 618 deletions(-) delete mode 100644 layout/base/nsImageLoader.cpp delete mode 100644 layout/base/nsImageLoader.h create mode 100644 layout/style/ImageLoader.cpp create mode 100644 layout/style/ImageLoader.h diff --git a/content/base/public/nsIDocument.h b/content/base/public/nsIDocument.h index 51dd7b893e2..4a636a50b11 100644 --- a/content/base/public/nsIDocument.h +++ b/content/base/public/nsIDocument.h @@ -116,6 +116,7 @@ class nsIObjectLoadingContent; namespace mozilla { namespace css { class Loader; +class ImageLoader; } // namespace css namespace dom { @@ -125,8 +126,8 @@ class Element; } // namespace mozilla #define NS_IDOCUMENT_IID \ -{ 0x8e51e6d9, 0x914d, 0x46ba, \ - { 0xb3, 0x11, 0x2f, 0x27, 0x3d, 0xe6, 0x0d, 0x19 } } +{ 0xdb888523, 0x541f, 0x49e3, \ + { 0xa9, 0x71, 0xb5, 0xea, 0xd1, 0xf0, 0xc3, 0xcf } } // Flag for AddStyleSheet(). @@ -643,6 +644,13 @@ public: return mCSSLoader; } + /** + * Get this document's StyleImageLoader. This is guaranteed to not return null. + */ + mozilla::css::ImageLoader* StyleImageLoader() const { + return mStyleImageLoader; + } + /** * Get the channel that was passed to StartDocumentLoad or Reset for this * document. Note that this may be null in some cases (eg if @@ -1739,6 +1747,7 @@ protected: // The cleanup is handled by the nsDocument destructor. nsNodeInfoManager* mNodeInfoManager; // [STRONG] mozilla::css::Loader* mCSSLoader; // [STRONG] + mozilla::css::ImageLoader* mStyleImageLoader; // [STRONG] nsHTMLStyleSheet* mAttrStyleSheet; // The set of all object, embed, applet, video and audio elements for diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index bee66e116b3..5ffb0b7d675 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -66,6 +66,7 @@ #include "nsIObserver.h" #include "nsIBaseWindow.h" #include "mozilla/css/Loader.h" +#include "mozilla/css/ImageLoader.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsIScriptRuntime.h" @@ -1649,6 +1650,11 @@ nsDocument::~nsDocument() NS_RELEASE(mCSSLoader); } + if (mStyleImageLoader) { + mStyleImageLoader->DropDocumentReference(); + NS_RELEASE(mStyleImageLoader); + } + // XXX Ideally we'd do this cleanup in the nsIDocument destructor. if (mNodeInfoManager) { mNodeInfoManager->DropDocumentReference(); @@ -1983,7 +1989,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END nsresult nsDocument::Init() { - if (mCSSLoader || mNodeInfoManager || mScriptLoader) { + if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) { return NS_ERROR_ALREADY_INITIALIZED; } @@ -2012,10 +2018,16 @@ nsDocument::Init() // Assume we're not quirky, until we know otherwise mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards); + mStyleImageLoader = new mozilla::css::ImageLoader(this); + NS_ADDREF(mStyleImageLoader); + + nsresult rv = mStyleImageLoader->Init(); + NS_ENSURE_SUCCESS(rv, rv); + mNodeInfoManager = new nsNodeInfoManager(); NS_ENSURE_TRUE(mNodeInfoManager, NS_ERROR_OUT_OF_MEMORY); - nsresult rv = mNodeInfoManager->Init(this); + rv = mNodeInfoManager->Init(this); NS_ENSURE_SUCCESS(rv, rv); // mNodeInfo keeps NodeInfoManager alive! diff --git a/layout/base/Makefile.in b/layout/base/Makefile.in index d2a618b0751..ed7d1fc68be 100644 --- a/layout/base/Makefile.in +++ b/layout/base/Makefile.in @@ -107,7 +107,6 @@ CPPSRCS = \ nsFrameManager.cpp \ nsFrameTraversal.cpp \ nsGenConList.cpp \ - nsImageLoader.cpp \ nsLayoutDebugger.cpp \ nsLayoutHistoryState.cpp \ nsLayoutUtils.cpp \ diff --git a/layout/base/nsCSSRendering.cpp b/layout/base/nsCSSRendering.cpp index 977a5de33a8..5efc9dc5517 100644 --- a/layout/base/nsCSSRendering.cpp +++ b/layout/base/nsCSSRendering.cpp @@ -84,8 +84,10 @@ #include "gfxDrawable.h" #include "nsCSSRenderingBorders.h" +#include "mozilla/css/ImageLoader.h" using namespace mozilla; +using namespace mozilla::css; /** * This is a small wrapper class to encapsulate image drawing that can draw an @@ -2419,10 +2421,20 @@ nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext, return; } - // Ensure we get invalidated for loads of the image. We need to do - // this here because this might be the only code that knows about the + // Ensure we get invalidated for loads of the image. If this is not the + // frame's normal style context this is the only code that knows about the // association of the style data with the frame. - aPresContext->SetupBackgroundImageLoaders(aForFrame, bg); + if (aBackgroundSC != aForFrame->GetStyleContext()) { + ImageLoader* loader = aPresContext->Document()->StyleImageLoader(); + + NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(i, bg) { + if (bg->mLayers[i].mImage.GetType() == eStyleImageType_Image) { + imgIRequest *image = bg->mLayers[i].mImage.GetImageData(); + + loader->AssociateRequestToFrame(image, aForFrame); + } + } + } // We can skip painting the background color if a background image is opaque. if (drawBackgroundColor && @@ -2732,9 +2744,11 @@ DrawBorderImage(nsPresContext* aPresContext, // XXX We shouldn't really... since if anybody is passing in a // different style, they'll potentially have the wrong size for the // border too. - aPresContext->SetupBorderImageLoaders(aForFrame, &aStyleBorder); - imgIRequest *req = aStyleBorder.GetBorderImage(); + ImageLoader* loader = aPresContext->Document()->StyleImageLoader(); + + // If this fails there's not much we can do ... + loader->AssociateRequestToFrame(req, aForFrame); // Get the actual image. diff --git a/layout/base/nsImageLoader.cpp b/layout/base/nsImageLoader.cpp deleted file mode 100644 index e90cc3e22c4..00000000000 --- a/layout/base/nsImageLoader.cpp +++ /dev/null @@ -1,284 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- - * - * ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla 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/MPL/ - * - * 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) 2001 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Stuart Parmenter - * - * Alternatively, the contents of this file may be used under the terms of - * either of 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 MPL, 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 MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -/* class to notify frames of background image loads */ - -#include "nsImageLoader.h" - -#include "imgILoader.h" - -#include "nsIURI.h" -#include "nsILoadGroup.h" -#include "nsNetUtil.h" - -#include "nsPresContext.h" -#include "nsIPresShell.h" -#include "nsIFrame.h" -#include "nsIContent.h" -#include "nsIDocument.h" - -#include "imgIContainer.h" - -#include "nsStyleContext.h" -#include "nsGkAtoms.h" -#include "nsLayoutUtils.h" - -// Paint forcing -#include "prenv.h" - -NS_IMPL_ISUPPORTS2(nsImageLoader, imgIDecoderObserver, imgIContainerObserver) - -nsImageLoader::nsImageLoader(nsIFrame *aFrame, PRUint32 aActions, - nsImageLoader *aNextLoader) - : mFrame(aFrame), - mActions(aActions), - mNextLoader(aNextLoader), - mRequestRegistered(false) -{ -} - -nsImageLoader::~nsImageLoader() -{ - Destroy(); -} - -/* static */ already_AddRefed -nsImageLoader::Create(nsIFrame *aFrame, imgIRequest *aRequest, - PRUint32 aActions, nsImageLoader *aNextLoader) -{ - nsRefPtr loader = - new nsImageLoader(aFrame, aActions, aNextLoader); - - loader->Load(aRequest); - - return loader.forget(); -} - -void -nsImageLoader::Destroy() -{ - // Destroy the chain with only one level of recursion. - nsRefPtr list = mNextLoader; - mNextLoader = nsnull; - while (list) { - nsRefPtr todestroy = list; - list = todestroy->mNextLoader; - todestroy->mNextLoader = nsnull; - todestroy->Destroy(); - } - - if (mRequest && mFrame) { - nsLayoutUtils::DeregisterImageRequest(mFrame->PresContext(), mRequest, - &mRequestRegistered); - } - - mFrame = nsnull; - - if (mRequest) { - mRequest->CancelAndForgetObserver(NS_ERROR_FAILURE); - } - - mRequest = nsnull; -} - -nsresult -nsImageLoader::Load(imgIRequest *aImage) -{ - NS_ASSERTION(!mRequest, "can't reuse image loaders"); - NS_ASSERTION(mFrame, "not initialized"); - NS_ASSERTION(aImage, "must have non-null image"); - - if (!mFrame) - return NS_ERROR_NOT_INITIALIZED; - - if (!aImage) - return NS_ERROR_FAILURE; - - // Deregister mRequest from the refresh driver, since it is no longer - // going to be managed by this nsImageLoader. - nsPresContext* presContext = mFrame->PresContext(); - - nsLayoutUtils::DeregisterImageRequest(presContext, mRequest, - &mRequestRegistered); - - // Make sure to clone into a temporary, then set mRequest, since - // cloning may notify and we don't want to trigger paints from this - // code. - nsCOMPtr newRequest; - nsresult rv = aImage->Clone(this, getter_AddRefs(newRequest)); - mRequest.swap(newRequest); - - if (mRequest) { - nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, mRequest, - &mRequestRegistered); - } - - return rv; -} - -NS_IMETHODIMP nsImageLoader::OnStartContainer(imgIRequest *aRequest, - imgIContainer *aImage) -{ - NS_ABORT_IF_FALSE(aImage, "Who's calling us then?"); - - /* Get requested animation policy from the pres context: - * normal = 0 - * one frame = 1 - * one loop = 2 - */ - aImage->SetAnimationMode(mFrame->PresContext()->ImageAnimationMode()); - - return NS_OK; -} - -NS_IMETHODIMP nsImageLoader::OnStopFrame(imgIRequest *aRequest, - PRUint32 aFrame) -{ - if (!mFrame) - return NS_ERROR_FAILURE; - - if (!mRequest) { - // We're in the middle of a paint anyway - return NS_OK; - } - - // Take requested actions - if (mActions & ACTION_REDRAW_ON_DECODE) { - DoRedraw(nsnull); - } - return NS_OK; -} - -NS_IMETHODIMP nsImageLoader::OnImageIsAnimated(imgIRequest *aRequest) -{ - // Register with the refresh driver now that we are aware that - // we are animated. - nsLayoutUtils::RegisterImageRequest(mFrame->PresContext(), - aRequest, &mRequestRegistered); - return NS_OK; -} - -NS_IMETHODIMP nsImageLoader::OnStopRequest(imgIRequest *aRequest, - bool aLastPart) -{ - if (!mFrame) - return NS_ERROR_FAILURE; - - if (!mRequest) { - // We're in the middle of a paint anyway - return NS_OK; - } - - // Take requested actions - if (mActions & ACTION_REDRAW_ON_LOAD) { - DoRedraw(nsnull); - } - return NS_OK; -} - -NS_IMETHODIMP nsImageLoader::FrameChanged(imgIRequest *aRequest, - imgIContainer *aContainer, - const nsIntRect *aDirtyRect) -{ - if (!mFrame) - return NS_ERROR_FAILURE; - - if (!mRequest) { - // We're in the middle of a paint anyway - return NS_OK; - } - - NS_ASSERTION(aRequest == mRequest, "This is a neat trick."); - - nsRect r = aDirtyRect->IsEqualInterior(nsIntRect::GetMaxSizedIntRect()) ? - nsRect(nsPoint(0, 0), mFrame->GetSize()) : - aDirtyRect->ToAppUnits(nsPresContext::AppUnitsPerCSSPixel()); - - DoRedraw(&r); - - return NS_OK; -} - -void -nsImageLoader::DoRedraw(const nsRect* aDamageRect) -{ - // NOTE: It is not sufficient to invalidate only the size of the image: - // the image may be tiled! - // The best option is to call into the frame, however lacking this - // we have to at least invalidate the frame's bounds, hence - // as long as we have a frame we'll use its size. - // - - // Invalidate the entire frame - // XXX We really only need to invalidate the client area of the frame... - - nsRect bounds(nsPoint(0, 0), mFrame->GetSize()); - - if (mFrame->GetType() == nsGkAtoms::canvasFrame) { - // The canvas's background covers the whole viewport. - bounds = mFrame->GetVisualOverflowRect(); - } - - // XXX this should be ok, but there is some crappy ass bug causing it not to work - // XXX seems related to the "body fixup rule" dealing with the canvas and body frames... -#if 0 - // Invalidate the entire frame only if the frame has a tiled background - // image, otherwise just invalidate the intersection of the frame's bounds - // with the damaged rect. - nsStyleContext* styleContext; - mFrame->GetStyleContext(&styleContext); - const nsStyleBackground* bg = styleContext->GetStyleBackground(); - - if ((bg->mBackgroundFlags & NS_STYLE_BG_IMAGE_NONE) || - (bg->mBackgroundRepeat == NS_STYLE_BG_REPEAT_OFF)) { - // The frame does not have a background image so we are free - // to invalidate only the intersection of the damage rect and - // the frame's bounds. - - if (aDamageRect) { - bounds.IntersectRect(*aDamageRect, bounds); - } - } - -#endif - - if (mFrame->GetStyleVisibility()->IsVisible()) { - mFrame->Invalidate(bounds); - } -} diff --git a/layout/base/nsImageLoader.h b/layout/base/nsImageLoader.h deleted file mode 100644 index 85e27b223b2..00000000000 --- a/layout/base/nsImageLoader.h +++ /dev/null @@ -1,115 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- - * - * ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla 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/MPL/ - * - * 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) 2001 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Stuart Parmenter - * - * Alternatively, the contents of this file may be used under the terms of - * either of 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 MPL, 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 MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -/* class to notify frames of background and border image loads */ - -#include "nsStubImageDecoderObserver.h" - -class nsIFrame; -class nsIURI; - -#include "imgIRequest.h" -#include "nsCOMPtr.h" -#include "nsAutoPtr.h" - -/** - * Image loaders pass notifications for background and border image - * loading and animation on to the frames. - * - * Each frame's image loaders form a linked list. - */ -class nsImageLoader : public nsStubImageDecoderObserver -{ -private: - nsImageLoader(nsIFrame *aFrame, PRUint32 aActions, - nsImageLoader *aNextLoader); - virtual ~nsImageLoader(); - -public: - /* - * Flags to specify actions that can be taken for the image at various - * times. Reflows always occur before redraws. "Decode" refers to one - * frame being available, whereas "load" refers to all the data being loaded - * from the network. - */ - enum { - ACTION_REDRAW_ON_DECODE = 0x01, - ACTION_REDRAW_ON_LOAD = 0x02, - }; - static already_AddRefed - Create(nsIFrame *aFrame, imgIRequest *aRequest, - PRUint32 aActions, nsImageLoader *aNextLoader); - - NS_DECL_ISUPPORTS - - // imgIDecoderObserver (override nsStubImageDecoderObserver) - NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage); - NS_IMETHOD OnStopFrame(imgIRequest *aRequest, PRUint32 aFrame); - NS_IMETHOD OnStopRequest(imgIRequest *aRequest, bool aLastPart); - NS_IMETHOD OnImageIsAnimated(imgIRequest *aRequest); - // Do not override OnDataAvailable since background images are not - // displayed incrementally; they are displayed after the entire image - // has been loaded. - // Note: Images referenced by the element are displayed - // incrementally in nsImageFrame.cpp. - - // imgIContainerObserver (override nsStubImageDecoderObserver) - NS_IMETHOD FrameChanged(imgIRequest *aRequest, - imgIContainer *aContainer, - const nsIntRect *aDirtyRect); - - void Destroy(); - - imgIRequest *GetRequest() { return mRequest; } - nsImageLoader *GetNextLoader() { return mNextLoader; } - -private: - nsresult Load(imgIRequest *aImage); - /* if aDamageRect is nsnull, the whole frame is redrawn. */ - void DoRedraw(const nsRect* aDamageRect); - - nsIFrame *mFrame; - nsCOMPtr mRequest; - PRUint32 mActions; - nsRefPtr mNextLoader; - - // This is a boolean flag indicating whether or not the current image request - // has been registered with the refresh driver. - bool mRequestRegistered; -}; diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index bf3be06df12..70c108a50f5 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -48,7 +48,6 @@ #include "nsIContentViewer.h" #include "nsPIDOMWindow.h" #include "nsStyleSet.h" -#include "nsImageLoader.h" #include "nsIContent.h" #include "nsIFrame.h" #include "nsIURL.h" @@ -97,6 +96,7 @@ #include "FrameLayerBuilder.h" #include "nsDOMMediaQueryList.h" #include "nsSMILAnimationController.h" +#include "mozilla/css/ImageLoader.h" #ifdef IBMBIDI #include "nsBidiPresUtils.h" @@ -194,14 +194,6 @@ IsVisualCharset(const nsCString& aCharset) } #endif // IBMBIDI - -static PLDHashOperator -destroy_loads(nsIFrame* aKey, nsRefPtr& aData, void* closure) -{ - aData->Destroy(); - return PL_DHASH_NEXT; -} - #include "nsContentCID.h" // NOTE! nsPresContext::operator new() zeroes out all members, so don't @@ -343,28 +335,12 @@ NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPresContext) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPresContext) -static PLDHashOperator -TraverseImageLoader(nsIFrame* aKey, nsRefPtr& aData, - void* aClosure) -{ - nsCycleCollectionTraversalCallback *cb = - static_cast(aClosure); - - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mImageLoaders[i] item"); - cb->NoteXPCOMChild(aData); - - return PL_DHASH_NEXT; -} - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsPresContext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDocument); // NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mDeviceContext); // not xpcom NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mEventManager, nsIObserver); // NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mLanguage); // an atom - for (PRUint32 i = 0; i < IMAGE_LOAD_TYPE_COUNT; ++i) - tmp->mImageLoaders[i].Enumerate(TraverseImageLoader, &cb); - // We own only the items in mDOMMediaQueryLists that have listeners; // this reference is managed by their AddListener and RemoveListener // methods. @@ -953,10 +929,6 @@ nsPresContext::Init(nsDeviceContext* aDeviceContext) mDeviceContext->FlushFontCache(); mCurAppUnitsPerDevPixel = AppUnitsPerDevPixel(); - for (PRUint32 i = 0; i < IMAGE_LOAD_TYPE_COUNT; ++i) - if (!mImageLoaders[i].Init()) - return NS_ERROR_OUT_OF_MEMORY; - mEventManager = new nsEventStateManager(); NS_ADDREF(mEventManager); @@ -1130,18 +1102,6 @@ nsPresContext::SetShell(nsIPresShell* aShell) } } -void -nsPresContext::DestroyImageLoaders() -{ - // Destroy image loaders. This is important to do when frames are being - // destroyed because imageloaders can have pointers to frames and we don't - // want those pointers to outlive the destruction of the frame arena. - for (PRUint32 i = 0; i < IMAGE_LOAD_TYPE_COUNT; ++i) { - mImageLoaders[i].Enumerate(destroy_loads, nsnull); - mImageLoaders[i].Clear(); - } -} - void nsPresContext::DoChangeCharSet(const nsCString& aCharSet) { @@ -1252,18 +1212,6 @@ static void SetImgAnimModeOnImgReq(imgIRequest* aImgReq, PRUint16 aMode) } } - // Enumeration call back for HashTable -static PLDHashOperator -set_animation_mode(nsIFrame* aKey, nsRefPtr& aData, void* closure) -{ - for (nsImageLoader *loader = aData; loader; - loader = loader->GetNextLoader()) { - imgIRequest* imgReq = loader->GetRequest(); - SetImgAnimModeOnImgReq(imgReq, (PRUint16)NS_PTR_TO_INT32(closure)); - } - return PL_DHASH_NEXT; -} - // IMPORTANT: Assumption is that all images for a Presentation // have the same Animation Mode (pavlov said this was OK) // @@ -1318,15 +1266,13 @@ nsPresContext::SetImageAnimationModeInternal(PRUint16 aMode) if (!IsDynamic()) return; - // Set the mode on the image loaders. - for (PRUint32 i = 0; i < IMAGE_LOAD_TYPE_COUNT; ++i) - mImageLoaders[i].Enumerate(set_animation_mode, NS_INT32_TO_PTR(aMode)); - // Now walk the content tree and set the animation mode // on all the images. if (mShell != nsnull) { nsIDocument *doc = mShell->GetDocument(); if (doc) { + doc->StyleImageLoader()->SetAnimationMode(aMode); + Element *rootElement = doc->GetRootElement(); if (rootElement) { SetImgAnimations(rootElement, aMode); @@ -1412,68 +1358,6 @@ nsPresContext::SetFullZoom(float aZoom) mCurAppUnitsPerDevPixel = AppUnitsPerDevPixel(); } -void -nsPresContext::SetImageLoaders(nsIFrame* aTargetFrame, - ImageLoadType aType, - nsImageLoader* aImageLoaders) -{ - NS_ASSERTION(mShell || !aImageLoaders, - "Shouldn't add new image loader after the shell is gone"); - - nsRefPtr oldLoaders; - mImageLoaders[aType].Get(aTargetFrame, getter_AddRefs(oldLoaders)); - - if (aImageLoaders) { - mImageLoaders[aType].Put(aTargetFrame, aImageLoaders); - } else if (oldLoaders) { - mImageLoaders[aType].Remove(aTargetFrame); - } - - if (oldLoaders) - oldLoaders->Destroy(); -} - -void -nsPresContext::SetupBackgroundImageLoaders(nsIFrame* aFrame, - const nsStyleBackground* aStyleBackground) -{ - nsRefPtr loaders; - NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(i, aStyleBackground) { - if (aStyleBackground->mLayers[i].mImage.GetType() == eStyleImageType_Image) { - PRUint32 actions = nsImageLoader::ACTION_REDRAW_ON_DECODE; - imgIRequest *image = aStyleBackground->mLayers[i].mImage.GetImageData(); - loaders = nsImageLoader::Create(aFrame, image, actions, loaders); - } - } - SetImageLoaders(aFrame, BACKGROUND_IMAGE, loaders); -} - -void -nsPresContext::SetupBorderImageLoaders(nsIFrame* aFrame, - const nsStyleBorder* aStyleBorder) -{ - // We get called the first time we try to draw a border-image, and - // also when the border image changes (including when it changes from - // non-null to null). - imgIRequest *borderImage = aStyleBorder->GetBorderImage(); - if (!borderImage) { - SetImageLoaders(aFrame, BORDER_IMAGE, nsnull); - return; - } - - PRUint32 actions = nsImageLoader::ACTION_REDRAW_ON_LOAD; - nsRefPtr loader = - nsImageLoader::Create(aFrame, borderImage, actions, nsnull); - SetImageLoaders(aFrame, BORDER_IMAGE, loader); -} - -void -nsPresContext::StopImagesFor(nsIFrame* aTargetFrame) -{ - for (PRUint32 i = 0; i < IMAGE_LOAD_TYPE_COUNT; ++i) - SetImageLoaders(aTargetFrame, ImageLoadType(i), nsnull); -} - void nsPresContext::SetContainer(nsISupports* aHandler) { @@ -2180,6 +2064,8 @@ NotifyDidPaintSubdocumentCallback(nsIDocument* aDocument, void* aData) void nsPresContext::NotifyDidPaintForSubtree() { + Document()->StyleImageLoader()->NotifyPaint(); + if (!mFireAfterPaintEvents) return; mFireAfterPaintEvents = false; diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h index 59108755842..51623d1608b 100644 --- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h @@ -74,7 +74,6 @@ #include "nsIContent.h" #include "prclist.h" -class nsImageLoader; #ifdef IBMBIDI class nsBidiPresUtils; #endif // IBMBIDI @@ -384,49 +383,6 @@ public: bool GetFocusRingOnAnything() const { return mFocusRingOnAnything; } PRUint8 GetFocusRingStyle() const { return mFocusRingStyle; } - /** - * The types of image load types that the pres context needs image - * loaders to track invalidation for. - */ - enum ImageLoadType { - BACKGROUND_IMAGE, - BORDER_IMAGE, - IMAGE_LOAD_TYPE_COUNT - }; - - /** - * Set the list of image loaders that track invalidation for a - * specific frame and type of image. This list will replace any - * previous list for that frame and image type (and null will remove - * any previous list). - */ - NS_HIDDEN_(void) SetImageLoaders(nsIFrame* aTargetFrame, - ImageLoadType aType, - nsImageLoader* aImageLoaders); - - /** - * Make an appropriate SetImageLoaders call (including potentially - * with null aImageLoaders) given that aFrame draws its background - * based on aStyleBackground. - */ - NS_HIDDEN_(void) SetupBackgroundImageLoaders(nsIFrame* aFrame, - const nsStyleBackground* - aStyleBackground); - - /** - * Make an appropriate SetImageLoaders call (including potentially - * with null aImageLoaders) given that aFrame draws its border - * based on aStyleBorder. - */ - NS_HIDDEN_(void) SetupBorderImageLoaders(nsIFrame* aFrame, - const nsStyleBorder* aStyleBorder); - - /** - * This method is called when a frame is being destroyed to - * ensure that the image loads get disassociated from the prescontext - */ - NS_HIDDEN_(void) StopImagesFor(nsIFrame* aTargetFrame); - NS_HIDDEN_(void) SetContainer(nsISupports* aContainer); virtual NS_HIDDEN_(already_AddRefed) GetContainerExternal() const; @@ -953,8 +909,6 @@ public: } inline void ForgetUpdatePluginGeometryFrame(nsIFrame* aFrame); - void DestroyImageLoaders(); - bool GetContainsUpdatePluginGeometryFrame() { return mContainsUpdatePluginGeometryFrame; @@ -1131,10 +1085,6 @@ public: nscoord mCurrentInflationContainerWidth; protected: - - nsRefPtrHashtable, nsImageLoader> - mImageLoaders[IMAGE_LOAD_TYPE_COUNT]; - nsWeakPtr mContainer; PRCList mDOMMediaQueryLists; diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 47462f0bc23..60be995c5d5 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -154,6 +154,7 @@ #include "nsHTMLMediaElement.h" #endif #include "nsSMILAnimationController.h" +#include "mozilla/css/ImageLoader.h" #include "nsRefreshDriver.h" @@ -2112,11 +2113,10 @@ PresShell::FireResizeEvent() void PresShell::SetIgnoreFrameDestruction(bool aIgnore) { - if (mPresContext) { - // We need to destroy the image loaders first, as they won't be - // notified when frames are destroyed once this setting takes effect. - // (See bug 673984) - mPresContext->DestroyImageLoaders(); + if (mDocument) { + // We need to tell the ImageLoader to drop all its references to frames + // because they're about to go away and it won't get notifications of that. + mDocument->StyleImageLoader()->ClearAll(); } mIgnoreFrameDestruction = aIgnore; } @@ -2129,7 +2129,7 @@ PresShell::NotifyDestroyingFrame(nsIFrame* aFrame) mPresContext->ForgetUpdatePluginGeometryFrame(aFrame); if (!mIgnoreFrameDestruction) { - mPresContext->StopImagesFor(aFrame); + mDocument->StyleImageLoader()->DropRequestsForFrame(aFrame); mFrameConstructor->NotifyDestroyingFrame(aFrame); diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index 9db8aea268f..a6ec9511603 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -129,10 +129,12 @@ #include "mozilla/Preferences.h" #include "mozilla/LookAndFeel.h" +#include "mozilla/css/ImageLoader.h" using namespace mozilla; using namespace mozilla::layers; using namespace mozilla::layout; +using namespace mozilla::css; // Struct containing cached metrics for box-wrapped frames. struct nsBoxLayoutMetrics @@ -707,6 +709,8 @@ EqualImages(imgIRequest *aOldImage, imgIRequest *aNewImage) /* virtual */ void nsFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) { + ImageLoader* imageLoader = PresContext()->Document()->StyleImageLoader(); + if (aOldStyleContext) { // If the old context had a background image image and new context // does not have the same image, clear the image load notifier @@ -719,13 +723,30 @@ nsFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) const nsStyleBackground *oldBG = aOldStyleContext->GetStyleBackground(); const nsStyleBackground *newBG = GetStyleBackground(); NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(i, oldBG) { + // If there is an image in oldBG that's not in newBG, drop it. if (i >= newBG->mImageCount || oldBG->mLayers[i].mImage != newBG->mLayers[i].mImage) { - // stop the image loading for the frame, the image has changed - PresContext()->SetImageLoaders(this, - nsPresContext::BACKGROUND_IMAGE, nsnull); - break; - } + const nsStyleImage& oldImage = oldBG->mLayers[i].mImage; + if (oldImage.GetType() != eStyleImageType_Image) { + continue; + } + + imageLoader->DisassociateRequestFromFrame(oldImage.GetImageData(), + this); + } + } + + NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(i, newBG) { + // If there is an image in newBG that's not in oldBG, add it. + if (i >= oldBG->mImageCount || + newBG->mLayers[i].mImage != oldBG->mLayers[i].mImage) { + const nsStyleImage& newImage = newBG->mLayers[i].mImage; + if (newImage.GetType() != eStyleImageType_Image) { + continue; + } + + imageLoader->AssociateRequestToFrame(newImage.GetImageData(), this); + } } // If we detect a change on margin, padding or border, we store the old @@ -766,6 +787,7 @@ nsFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) imgIRequest *oldBorderImage = aOldStyleContext ? aOldStyleContext->GetStyleBorder()->GetBorderImage() : nsnull; + imgIRequest *newBorderImage = GetStyleBorder()->GetBorderImage(); // For border-images, we can't be as conservative (we need to set the // new loaders if there has been any change) since the CalcDifference // call depended on the result of GetActualBorder() and that result @@ -779,9 +801,14 @@ nsFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) // is loaded) and paint. We also don't really care about any callers // who try to paint borders with a different style context, because // they won't have the correct size for the border either. - if (!EqualImages(oldBorderImage, GetStyleBorder()->GetBorderImage())) { + if (!EqualImages(oldBorderImage, newBorderImage)) { // stop and restart the image loading/notification - PresContext()->SetupBorderImageLoaders(this, GetStyleBorder()); + if (oldBorderImage) { + imageLoader->DisassociateRequestFromFrame(oldBorderImage, this); + } + if (newBorderImage) { + imageLoader->AssociateRequestToFrame(newBorderImage, this); + } } // If the page contains markup that overrides text direction, and diff --git a/layout/style/ImageLoader.cpp b/layout/style/ImageLoader.cpp new file mode 100644 index 00000000000..cf5d63c2a68 --- /dev/null +++ b/layout/style/ImageLoader.cpp @@ -0,0 +1,449 @@ +/* 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/. */ + +/* A class that handles style system image loads (other image loads are handled + * by the nodes in the content tree). + */ + +#include "mozilla/css/ImageLoader.h" +#include "nsContentUtils.h" +#include "nsLayoutUtils.h" +#include "nsNetError.h" + +namespace mozilla { +namespace css { + +/* static */ PLDHashOperator +ImageLoader::SetAnimationModeEnumerator(nsISupports* aKey, FrameSet* aValue, + void* aClosure) +{ + imgIRequest* request = static_cast(aKey); + + PRUint16* mode = static_cast(aClosure); + +#ifdef DEBUG + { + nsCOMPtr debugRequest = do_QueryInterface(aKey); + NS_ASSERTION(debugRequest == request, "This is bad"); + } +#endif + + nsCOMPtr container; + request->GetImage(getter_AddRefs(container)); + if (!container) { + return PL_DHASH_NEXT; + } + + // This can fail if the image is in error, and we don't care. + container->SetAnimationMode(*mode); + + return PL_DHASH_NEXT; +} + +nsresult +ImageLoader::Init() +{ + MOZ_ASSERT(mDocument); + + if (!mRequestToFrameMap.Init() || + !mFrameToRequestMap.Init() || + !mImages.Init()) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +void +ImageLoader::DropDocumentReference() +{ + ClearAll(); + mDocument = nsnull; +} + +void +ImageLoader::AssociateRequestToFrame(imgIRequest* aRequest, + nsIFrame* aFrame) +{ + MOZ_ASSERT(mRequestToFrameMap.IsInitialized() && + mFrameToRequestMap.IsInitialized() && + mImages.IsInitialized()); + + nsCOMPtr observer; + aRequest->GetDecoderObserver(getter_AddRefs(observer)); + if (!observer) { + // The request has already been canceled, so ignore it. This is ok because + // we're not going to get any more notifications from a canceled request. + return; + } + + MOZ_ASSERT(observer == this); + + FrameSet* frameSet = nsnull; + if (mRequestToFrameMap.Get(aRequest, &frameSet)) { + NS_ASSERTION(frameSet, "This should never be null!"); + } + + if (!frameSet) { + nsAutoPtr newFrameSet(new FrameSet()); + + bool result = mRequestToFrameMap.Put(aRequest, newFrameSet); + if (!result) { + return; + } + + frameSet = newFrameSet.forget(); + } + + RequestSet* requestSet = nsnull; + if (mFrameToRequestMap.Get(aFrame, &requestSet)) { + NS_ASSERTION(requestSet, "This should never be null"); + } + + if (!requestSet) { + nsAutoPtr newRequestSet(new RequestSet()); + + bool result = mFrameToRequestMap.Put(aFrame, newRequestSet); + if (!result) { + return; + } + + requestSet = newRequestSet.forget(); + } + + // Add these to the sets, but only if they're not already there. + PRUint32 i; + if (!frameSet->GreatestIndexLtEq(aFrame, i)) { + frameSet->InsertElementAt(i, aFrame); + } + if (!requestSet->GreatestIndexLtEq(aRequest, i)) { + requestSet->InsertElementAt(i, aRequest); + } +} + +void +ImageLoader::MaybeRegisterCSSImage(nsCSSValue::Image* aImage) +{ + NS_ASSERTION(aImage, "This should never be null!"); + + bool found = false; + aImage->mRequests.GetWeak(mDocument, &found); + if (found) { + // This document already has a request. + return; + } + + imgIRequest* canonicalRequest = aImage->mRequests.GetWeak(nsnull); + if (!canonicalRequest) { + // The image was blocked or something. + return; + } + + nsCOMPtr request; + + // Ignore errors here. If cloning fails for some reason we'll put a null + // entry in the hash and we won't keep trying to clone. + mInClone = true; + canonicalRequest->Clone(this, getter_AddRefs(request)); + mInClone = false; + + aImage->mRequests.Put(mDocument, request); + + AddImage(aImage); +} + +void +ImageLoader::DeregisterCSSImage(nsCSSValue::Image* aImage) +{ + RemoveImage(aImage); +} + +void +ImageLoader::DisassociateRequestFromFrame(imgIRequest* aRequest, + nsIFrame* aFrame) +{ + FrameSet* frameSet = nsnull; + RequestSet* requestSet = nsnull; + + MOZ_ASSERT(mRequestToFrameMap.IsInitialized() && + mFrameToRequestMap.IsInitialized() && + mImages.IsInitialized()); + +#ifdef DEBUG + { + nsCOMPtr observer; + aRequest->GetDecoderObserver(getter_AddRefs(observer)); + MOZ_ASSERT(!observer || observer == this); + } +#endif + + mRequestToFrameMap.Get(aRequest, &frameSet); + mFrameToRequestMap.Get(aFrame, &requestSet); + + if (frameSet) { + frameSet->RemoveElementSorted(aFrame); + } + if (requestSet) { + requestSet->RemoveElementSorted(aRequest); + } + + if (frameSet && !frameSet->Length()) { + mRequestToFrameMap.Remove(aRequest); + + nsPresContext* presContext = GetPresContext(); + if (presContext) { + nsLayoutUtils::DeregisterImageRequest(presContext, + aRequest, + nsnull); + } + } + + if (requestSet && !requestSet->Length()) { + mFrameToRequestMap.Remove(aFrame); + } +} + +void +ImageLoader::DropRequestsForFrame(nsIFrame* aFrame) +{ + RequestSet* requestSet = nsnull; + if (!mFrameToRequestMap.Get(aFrame, &requestSet)) { + return; + } + + NS_ASSERTION(requestSet, "This should never be null"); + + RequestSet frozenRequestSet(*requestSet); + for (RequestSet::size_type i = frozenRequestSet.Length(); i != 0; --i) { + imgIRequest* request = frozenRequestSet.ElementAt(i - 1); + + DisassociateRequestFromFrame(request, aFrame); + } +} + +void +ImageLoader::SetAnimationMode(PRUint16 aMode) +{ + NS_ASSERTION(aMode == imgIContainer::kNormalAnimMode || + aMode == imgIContainer::kDontAnimMode || + aMode == imgIContainer::kLoopOnceAnimMode, + "Wrong Animation Mode is being set!"); + + mRequestToFrameMap.EnumerateRead(SetAnimationModeEnumerator, &aMode); +} + +static PLDHashOperator +ClearImageHashSet(nsPtrHashKey* aKey, void* aClosure) +{ + nsIDocument* doc = static_cast(aClosure); + nsCSSValue::Image* image = aKey->GetKey(); + + imgIRequest* request = image->mRequests.GetWeak(doc); + if (request) { + request->CancelAndForgetObserver(NS_BINDING_ABORTED); + } + + image->mRequests.Remove(doc); + + return PL_DHASH_REMOVE; +} + +void +ImageLoader::ClearAll() +{ + mRequestToFrameMap.Clear(); + mFrameToRequestMap.Clear(); + mImages.EnumerateEntries(&ClearImageHashSet, mDocument); +} + +void +ImageLoader::LoadImage(nsIURI* aURI, nsIPrincipal* aOriginPrincipal, + nsIURI* aReferrer, nsCSSValue::Image* aImage) +{ + NS_ASSERTION(aImage->mRequests.Count() == 0, "Huh?"); + + aImage->mRequests.Put(nsnull, nsnull); + + if (!aURI) { + return; + } + + if (!nsContentUtils::CanLoadImage(aURI, mDocument, mDocument, + aOriginPrincipal)) { + return; + } + + nsCOMPtr request; + nsContentUtils::LoadImage(aURI, mDocument, aOriginPrincipal, aReferrer, + nsnull, nsIRequest::LOAD_NORMAL, + getter_AddRefs(request)); + + if (!request) { + return; + } + + nsCOMPtr clonedRequest; + mInClone = true; + nsresult rv = request->Clone(this, getter_AddRefs(clonedRequest)); + mInClone = false; + + if (NS_FAILED(rv)) { + return; + } + + aImage->mRequests.Put(nsnull, request); + aImage->mRequests.Put(mDocument, clonedRequest); + + AddImage(aImage); +} + +void +ImageLoader::AddImage(nsCSSValue::Image* aImage) +{ + NS_ASSERTION(!mImages.Contains(aImage), "Huh?"); + if (!mImages.PutEntry(aImage)) { + NS_RUNTIMEABORT("OOM"); + } +} + +void +ImageLoader::RemoveImage(nsCSSValue::Image* aImage) +{ + NS_ASSERTION(mImages.Contains(aImage), "Huh?"); + mImages.RemoveEntry(aImage); +} + +nsPresContext* +ImageLoader::GetPresContext() +{ + if (!mDocument) { + return nsnull; + } + + nsIPresShell* shell = mDocument->GetShell(); + if (!shell) { + return nsnull; + } + + return shell->GetPresContext(); +} + +void +ImageLoader::DoRedraw(FrameSet* aFrameSet) +{ + NS_ASSERTION(aFrameSet, "Must have a frame set"); + NS_ASSERTION(mDocument, "Should have returned earlier!"); + NS_ASSERTION(mHavePainted, "Should have returned earlier!"); + + FrameSet::size_type length = aFrameSet->Length(); + for (FrameSet::size_type i = 0; i < length; i++) { + nsIFrame* frame = aFrameSet->ElementAt(i); + + // NOTE: It is not sufficient to invalidate only the size of the image: + // the image may be tiled! + // The best option is to call into the frame, however lacking this + // we have to at least invalidate the frame's bounds, hence + // as long as we have a frame we'll use its size. + // + + // Invalidate the entire frame + // XXX We really only need to invalidate the client area of the frame... + + nsRect bounds(nsPoint(0, 0), frame->GetSize()); + + if (frame->GetType() == nsGkAtoms::canvasFrame) { + // The canvas's background covers the whole viewport. + bounds = frame->GetVisualOverflowRect(); + } + + if (frame->GetStyleVisibility()->IsVisible()) { + frame->Invalidate(bounds); + } + } +} + +NS_IMPL_ADDREF(ImageLoader) +NS_IMPL_RELEASE(ImageLoader) + +NS_INTERFACE_MAP_BEGIN(ImageLoader) + NS_INTERFACE_MAP_ENTRY(imgIDecoderObserver) + NS_INTERFACE_MAP_ENTRY(imgIContainerObserver) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +ImageLoader::OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage) +{ + nsPresContext* presContext = GetPresContext(); + if (!presContext) { + return NS_OK; + } + + aImage->SetAnimationMode(presContext->ImageAnimationMode()); + + return NS_OK; +} + +NS_IMETHODIMP +ImageLoader::OnImageIsAnimated(imgIRequest* aRequest) +{ + // NB: Don't ignore this when cloning, it's our only chance to register + // the request with the refresh driver. + if (!mDocument) { + return NS_OK; + } + + // Register with the refresh driver now that we are aware that + // we are animated. + nsPresContext* presContext = GetPresContext(); + if (presContext) { + nsLayoutUtils::RegisterImageRequest(presContext, + aRequest, + nsnull); + } + + return NS_OK; +} + +NS_IMETHODIMP +ImageLoader::OnStopFrame(imgIRequest *aRequest, PRUint32 aFrame) +{ + if (!mDocument || !mHavePainted || mInClone) { + return NS_OK; + } + + FrameSet* frameSet = nsnull; + if (!mRequestToFrameMap.Get(aRequest, &frameSet)) { + return NS_OK; + } + + NS_ASSERTION(frameSet, "This should never be null!"); + + DoRedraw(frameSet); + + return NS_OK; +} + +NS_IMETHODIMP +ImageLoader::FrameChanged(imgIRequest *aRequest, + imgIContainer *aContainer, + const nsIntRect *aDirtyRect) +{ + if (!mDocument || !mHavePainted || mInClone) { + return NS_OK; + } + + FrameSet* frameSet = nsnull; + if (!mRequestToFrameMap.Get(aRequest, &frameSet)) { + return NS_OK; + } + + NS_ASSERTION(frameSet, "This should never be null!"); + + DoRedraw(frameSet); + + return NS_OK; +} + +} // namespace css +} // namespace mozilla diff --git a/layout/style/ImageLoader.h b/layout/style/ImageLoader.h new file mode 100644 index 00000000000..c5753012717 --- /dev/null +++ b/layout/style/ImageLoader.h @@ -0,0 +1,124 @@ +/* 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/. */ + +// A class that handles style system image loads (other image loads are handled +// by the nodes in the content tree). + +#include "nsAutoPtr.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" +#include "nsInterfaceHashtable.h" +#include "nsCSSValue.h" +#include "imgIRequest.h" +#include "nsStubImageDecoderObserver.h" + +class nsIFrame; +class nsIDocument; +class nsPresContext; +class nsIURI; +class nsIPrincipal; + +namespace mozilla { +namespace css { + +class ImageLoader : public nsStubImageDecoderObserver { +public: + ImageLoader(nsIDocument* aDocument) + : mDocument(aDocument), + mHavePainted(false), + mInClone(false) + { } + + NS_DECL_ISUPPORTS + + // imgIDecoderObserver (override nsStubImageDecoderObserver) + NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage); + NS_IMETHOD OnStopFrame(imgIRequest *aRequest, PRUint32 aFrame); + NS_IMETHOD OnImageIsAnimated(imgIRequest *aRequest); + // Do not override OnDataAvailable since background images are not + // displayed incrementally; they are displayed after the entire image + // has been loaded. + + // imgIContainerObserver (override nsStubImageDecoderObserver) + NS_IMETHOD FrameChanged(imgIRequest* aRequest, + imgIContainer *aContainer, + const nsIntRect *aDirtyRect); + + nsresult Init(); + + inline void NotifyPaint() + { + mHavePainted = true; + } + + void DropDocumentReference(); + + void MaybeRegisterCSSImage(nsCSSValue::Image* aImage); + void DeregisterCSSImage(nsCSSValue::Image* aImage); + + void AssociateRequestToFrame(imgIRequest* aRequest, + nsIFrame* aFrame); + + void DisassociateRequestFromFrame(imgIRequest* aRequest, + nsIFrame* aFrame); + + void DropRequestsForFrame(nsIFrame* aFrame); + + void SetAnimationMode(PRUint16 aMode); + + void ClearAll(); + + void LoadImage(nsIURI* aURI, nsIPrincipal* aPrincipal, nsIURI* aReferrer, + nsCSSValue::Image* aCSSValue); + + void DestroyRequest(imgIRequest* aRequest); + +private: + // We need to be able to look up the frames associated with a request (for + // delivering notifications) and the requests associated with a frame (when + // the frame goes away). Thus we maintain hashtables going both ways. These + // should always be in sync. + + typedef nsTArray FrameSet; + typedef nsTArray > RequestSet; + typedef nsTHashtable > ImageHashSet; + typedef nsClassHashtable RequestToFrameMap; + typedef nsClassHashtable, + RequestSet> FrameToRequestMap; + + void AddImage(nsCSSValue::Image* aCSSImage); + void RemoveImage(nsCSSValue::Image* aCSSImage); + + nsPresContext* GetPresContext(); + + void DoRedraw(FrameSet* aFrameSet); + + static PLDHashOperator + SetAnimationModeEnumerator(nsISupports* aKey, FrameSet* aValue, + void* aClosure); + + // A map of imgIRequests to the nsIFrames that are using them. + RequestToFrameMap mRequestToFrameMap; + + // A map of nsIFrames to the imgIRequests they use. + FrameToRequestMap mFrameToRequestMap; + + // A weak pointer to our document. Nulled out by DropDocumentReference. + nsIDocument* mDocument; + + // The set of all nsCSSValue::Images (whether they're associated a frame or + // not). We'll need this when we go away to remove any requests associated + // with our document from those Images. + ImageHashSet mImages; + + // Have we painted yet? If not, no need to deliver notifications. + bool mHavePainted; + + // Are we cloning? If so, ignore any notifications we get. + bool mInClone; +}; + +} // namespace css +} // namespace mozilla diff --git a/layout/style/Makefile.in b/layout/style/Makefile.in index dd0e3d3c1d5..81a62a8c446 100644 --- a/layout/style/Makefile.in +++ b/layout/style/Makefile.in @@ -101,6 +101,7 @@ EXPORTS = \ EXPORTS_mozilla/css = \ Declaration.h \ GroupRule.h \ + ImageLoader.h \ ImportRule.h \ Loader.h \ NameSpaceRule.h \ @@ -114,6 +115,7 @@ CPPSRCS = \ nsCSSDataBlock.cpp \ Declaration.cpp \ nsCSSKeywords.cpp \ + ImageLoader.cpp \ Loader.cpp \ nsAnimationManager.cpp \ nsCSSParser.cpp \ diff --git a/layout/style/nsCSSDataBlock.cpp b/layout/style/nsCSSDataBlock.cpp index adc51d42900..fde2c4523cc 100644 --- a/layout/style/nsCSSDataBlock.cpp +++ b/layout/style/nsCSSDataBlock.cpp @@ -42,6 +42,7 @@ #include "nsCSSDataBlock.h" #include "mozilla/css/Declaration.h" +#include "mozilla/css/ImageLoader.h" #include "nsRuleData.h" #include "nsStyleSet.h" #include "nsStyleContext.h" @@ -79,16 +80,25 @@ ShouldIgnoreColors(nsRuleData *aRuleData) static void TryToStartImageLoadOnValue(const nsCSSValue& aValue, nsIDocument* aDocument) { + MOZ_ASSERT(aDocument); + if (aValue.GetUnit() == eCSSUnit_URL) { aValue.StartImageLoad(aDocument); } + else if (aValue.GetUnit() == eCSSUnit_Image) { + // If we already have a request, see if this document needs to clone it. + imgIRequest* request = aValue.GetImageValue(nsnull); + + if (request) { + aDocument->StyleImageLoader()->MaybeRegisterCSSImage(aValue.GetImageStructValue()); + } + } else if (aValue.EqualsFunction(eCSSKeyword__moz_image_rect)) { nsCSSValue::Array* arguments = aValue.GetArrayValue(); NS_ABORT_IF_FALSE(arguments->Count() == 6, "unexpected num of arguments"); const nsCSSValue& image = arguments->Item(1); - if (image.GetUnit() == eCSSUnit_URL) - image.StartImageLoad(aDocument); + TryToStartImageLoadOnValue(image, aDocument); } } diff --git a/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp index 16e72e0a8c5..ded5b9563ad 100644 --- a/layout/style/nsCSSValue.cpp +++ b/layout/style/nsCSSValue.cpp @@ -47,6 +47,7 @@ #include "nsStyleUtil.h" #include "CSSCalc.h" #include "nsNetUtil.h" +#include "mozilla/css/ImageLoader.h" namespace css = mozilla::css; @@ -272,10 +273,10 @@ double nsCSSValue::GetAngleValueInRadians() const } } -imgIRequest* nsCSSValue::GetImageValue() const +imgIRequest* nsCSSValue::GetImageValue(nsIDocument* aDocument) const { NS_ABORT_IF_FALSE(mUnit == eCSSUnit_Image, "not an Image value"); - return mValue.mImage->mRequest; + return mValue.mImage->mRequests.GetWeak(aDocument); } nscoord nsCSSValue::GetFixedLength(nsPresContext* aPresContext) const @@ -1679,17 +1680,43 @@ nsCSSValue::Image::Image(nsIURI* aURI, nsStringBuffer* aString, if (aDocument->GetOriginalDocument()) { aDocument = aDocument->GetOriginalDocument(); } - if (aURI && - nsContentUtils::CanLoadImage(aURI, aDocument, aDocument, - aOriginPrincipal)) { - nsContentUtils::LoadImage(aURI, aDocument, aOriginPrincipal, aReferrer, - nsnull, nsIRequest::LOAD_NORMAL, - getter_AddRefs(mRequest)); + + if (!mRequests.Init()) { + NS_RUNTIMEABORT("out of memory"); } + + aDocument->StyleImageLoader()->LoadImage(aURI, aOriginPrincipal, aReferrer, + this); +} + +static PLDHashOperator +ClearRequestHashtable(nsISupports* aKey, nsCOMPtr& aValue, + void* aClosure) +{ + nsCSSValue::Image* image = static_cast(aClosure); + nsIDocument* doc = static_cast(aKey); + +#ifdef DEBUG + { + nsCOMPtr slowDoc = do_QueryInterface(aKey); + MOZ_ASSERT(slowDoc == doc); + } +#endif + + if (doc) { + doc->StyleImageLoader()->DeregisterCSSImage(image); + } + + if (aValue) { + aValue->CancelAndForgetObserver(NS_BINDING_ABORTED); + } + + return PL_DHASH_REMOVE; } nsCSSValue::Image::~Image() { + mRequests.Enumerate(&ClearRequestHashtable, this); } nsCSSValueGradientStop::nsCSSValueGradientStop() diff --git a/layout/style/nsCSSValue.h b/layout/style/nsCSSValue.h index 53779f40427..07fe7c2626a 100644 --- a/layout/style/nsCSSValue.h +++ b/layout/style/nsCSSValue.h @@ -48,6 +48,7 @@ #include "nsCSSProperty.h" #include "nsColor.h" #include "nsCoord.h" +#include "nsInterfaceHashtable.h" #include "nsString.h" #include "nsStringBuffer.h" #include "nsTArray.h" @@ -58,6 +59,8 @@ class nsIDocument; class nsIPrincipal; class nsPresContext; class nsIURI; +template +class nsPtrHashKey; // Deletes a linked list iteratively to avoid blowing up the stack (bug 456196). #define NS_CSS_DELETE_LIST_MEMBER(type_, ptr_, member_) \ @@ -386,6 +389,12 @@ public: return mValue.mURL; } + Image* GetImageStructValue() const + { + NS_ABORT_IF_FALSE(mUnit == eCSSUnit_Image, "not an Image value"); + return mValue.mImage; + } + const PRUnichar* GetOriginalURLValue() const { NS_ABORT_IF_FALSE(mUnit == eCSSUnit_URL || mUnit == eCSSUnit_Image, @@ -398,7 +407,7 @@ public: // Not making this inline because that would force us to include // imgIRequest.h, which leads to REQUIRES hell, since this header is included // all over. - imgIRequest* GetImageValue() const; + imgIRequest* GetImageValue(nsIDocument* aDocument) const; nscoord GetFixedLength(nsPresContext* aPresContext) const; nscoord GetPixelLength() const; @@ -517,7 +526,7 @@ public: // Inherit operator== from nsCSSValue::URL - nsCOMPtr mRequest; // null == image load blocked or somehow failed + nsInterfaceHashtable mRequests; // Override AddRef and Release to not only log ourselves correctly, but // also so that we delete correctly without a virtual destructor diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 949a46e9608..378cd0b3875 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -103,6 +103,12 @@ using namespace mozilla::dom; method_(req); \ } +#define NS_SET_IMAGE_REQUEST_WITH_DOC(method_, context_, requestgetter_) \ + { \ + nsIDocument* doc = (context_)->PresContext()->Document(); \ + NS_SET_IMAGE_REQUEST(method_, context_, requestgetter_(doc)) \ + } + /* * For storage of an |nsRuleNode|'s children in a PLDHashTable. */ @@ -933,9 +939,9 @@ static void SetStyleImageToImageRect(nsStyleContext* aStyleContext, // if (arr->Item(1).GetUnit() == eCSSUnit_Image) { - NS_SET_IMAGE_REQUEST(aResult.SetImageData, - aStyleContext, - arr->Item(1).GetImageValue()) + NS_SET_IMAGE_REQUEST_WITH_DOC(aResult.SetImageData, + aStyleContext, + arr->Item(1).GetImageValue) } else { NS_WARNING("nsCSSValue::Image::Image() failed?"); } @@ -965,9 +971,9 @@ static void SetStyleImage(nsStyleContext* aStyleContext, switch (aValue.GetUnit()) { case eCSSUnit_Image: - NS_SET_IMAGE_REQUEST(aResult.SetImageData, - aStyleContext, - aValue.GetImageValue()) + NS_SET_IMAGE_REQUEST_WITH_DOC(aResult.SetImageData, + aStyleContext, + aValue.GetImageValue) break; case eCSSUnit_Function: if (aValue.EqualsFunction(eCSSKeyword__moz_image_rect)) { @@ -3625,9 +3631,10 @@ nsRuleNode::ComputeUserInterfaceData(void* aStartStruct, cursorUnit).get()); const nsCSSValueList* list = cursorValue->GetListValue(); const nsCSSValueList* list2 = list; + nsIDocument* doc = aContext->PresContext()->Document(); PRUint32 arrayLength = 0; for ( ; list->mValue.GetUnit() == eCSSUnit_Array; list = list->mNext) - if (list->mValue.GetArrayValue()->Item(0).GetImageValue()) + if (list->mValue.GetArrayValue()->Item(0).GetImageValue(doc)) ++arrayLength; if (arrayLength != 0) { @@ -3639,7 +3646,7 @@ nsRuleNode::ComputeUserInterfaceData(void* aStartStruct, list2->mValue.GetUnit() == eCSSUnit_Array; list2 = list2->mNext) { nsCSSValue::Array *arr = list2->mValue.GetArrayValue(); - imgIRequest *req = arr->Item(0).GetImageValue(); + imgIRequest *req = arr->Item(0).GetImageValue(doc); if (req) { item->SetImage(req); if (arr->Item(1).GetUnit() != eCSSUnit_Null) { @@ -5727,11 +5734,13 @@ nsRuleNode::ComputeBorderData(void* aStartStruct, // border-image-source const nsCSSValue* borderImageSource = aRuleData->ValueForBorderImageSource(); if (borderImageSource->GetUnit() == eCSSUnit_Image) { - NS_SET_IMAGE_REQUEST(border->SetBorderImage, aContext, - borderImageSource->GetImageValue()); + NS_SET_IMAGE_REQUEST_WITH_DOC(border->SetBorderImage, + aContext, + borderImageSource->GetImageValue); } else if (borderImageSource->GetUnit() == eCSSUnit_Inherit) { canStoreInRuleTree = false; - NS_SET_IMAGE_REQUEST(border->SetBorderImage, aContext, + NS_SET_IMAGE_REQUEST(border->SetBorderImage, + aContext, parentBorder->GetBorderImage()); } else if (borderImageSource->GetUnit() == eCSSUnit_Initial || borderImageSource->GetUnit() == eCSSUnit_None) { @@ -5982,9 +5991,9 @@ nsRuleNode::ComputeListData(void* aStartStruct, // list-style-image: url, none, inherit const nsCSSValue* imageValue = aRuleData->ValueForListStyleImage(); if (eCSSUnit_Image == imageValue->GetUnit()) { - NS_SET_IMAGE_REQUEST(list->SetListStyleImage, - aContext, - imageValue->GetImageValue()) + NS_SET_IMAGE_REQUEST_WITH_DOC(list->SetListStyleImage, + aContext, + imageValue->GetImageValue) } else if (eCSSUnit_None == imageValue->GetUnit() || eCSSUnit_Initial == imageValue->GetUnit()) { @@ -6303,7 +6312,9 @@ nsRuleNode::ComputeContentData(void* aStartStruct, } data.mType = type; if (type == eStyleContentType_Image) { - NS_SET_IMAGE_REQUEST(data.SetImage, aContext, value.GetImageValue()); + NS_SET_IMAGE_REQUEST_WITH_DOC(data.SetImage, + aContext, + value.GetImageValue); } else if (type <= eStyleContentType_Attr) { value.GetStringValue(buffer);