Bug 638241. Use a cache to avoid redoing SurfaceForElement and DoDrawImageSecurityCheck. r=bzbarsky

This commit is contained in:
Robert O'Callahan 2011-03-24 16:13:56 +13:00
parent c83de15a66
commit 0788ccd0dd
8 changed files with 321 additions and 37 deletions

View File

@ -0,0 +1,196 @@
/* -*- 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 Corporation code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Robert O'Callahan <robert@ocallahan.org>
*
* 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 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 ***** */
#include "CanvasImageCache.h"
#include "nsIImageLoadingContent.h"
#include "nsExpirationTracker.h"
#include "imgIRequest.h"
#include "gfxASurface.h"
#include "gfxPoint.h"
#include "nsIDOMElement.h"
#include "nsTHashtable.h"
#include "nsHTMLCanvasElement.h"
namespace mozilla {
struct ImageCacheKey {
ImageCacheKey(nsIDOMElement* aImage, nsHTMLCanvasElement* aCanvas)
: mImage(aImage), mCanvas(aCanvas) {}
nsIDOMElement* mImage;
nsHTMLCanvasElement* mCanvas;
};
struct ImageCacheEntryData {
ImageCacheEntryData(const ImageCacheEntryData& aOther)
: mImage(aOther.mImage)
, mCanvas(aOther.mCanvas)
, mRequest(aOther.mRequest)
, mSurface(aOther.mSurface)
, mSize(aOther.mSize)
{}
ImageCacheEntryData(const ImageCacheKey& aKey)
: mImage(aKey.mImage)
, mCanvas(aKey.mCanvas)
{}
nsExpirationState* GetExpirationState() { return &mState; }
// Key
nsCOMPtr<nsIDOMElement> mImage;
nsRefPtr<nsHTMLCanvasElement> mCanvas;
// Value
nsCOMPtr<imgIRequest> mRequest;
nsRefPtr<gfxASurface> mSurface;
gfxIntSize mSize;
nsExpirationState mState;
};
class ImageCacheEntry : public PLDHashEntryHdr {
public:
typedef ImageCacheKey KeyType;
typedef const ImageCacheKey* KeyTypePointer;
ImageCacheEntry(const KeyType *key) :
mData(new ImageCacheEntryData(*key)) {}
ImageCacheEntry(const ImageCacheEntry &toCopy) :
mData(new ImageCacheEntryData(*toCopy.mData)) {}
~ImageCacheEntry() {}
PRBool KeyEquals(KeyTypePointer key) const
{
return mData->mImage == key->mImage && mData->mCanvas == key->mCanvas;
}
static KeyTypePointer KeyToPointer(KeyType& key) { return &key; }
static PLDHashNumber HashKey(KeyTypePointer key)
{
return (NS_PTR_TO_INT32(key->mImage) ^ NS_PTR_TO_INT32(key->mCanvas)) >> 2;
}
enum { ALLOW_MEMMOVE = PR_TRUE };
nsAutoPtr<ImageCacheEntryData> mData;
};
class ImageCache : public nsExpirationTracker<ImageCacheEntryData,4> {
public:
// We use 3 generations of 1 second each to get a 2-3 seconds timeout.
enum { GENERATION_MS = 1000 };
ImageCache()
: nsExpirationTracker<ImageCacheEntryData,4>(GENERATION_MS)
{
mCache.Init();
}
~ImageCache() {
AgeAllGenerations();
}
virtual void NotifyExpired(ImageCacheEntryData* aObject)
{
RemoveObject(aObject);
// Deleting the entry will delete aObject since the entry owns aObject
mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mCanvas));
}
nsTHashtable<ImageCacheEntry> mCache;
};
static ImageCache* gImageCache = nsnull;
void
CanvasImageCache::NotifyDrawImage(nsIDOMElement* aImage,
nsHTMLCanvasElement* aCanvas,
imgIRequest* aRequest,
gfxASurface* aSurface,
const gfxIntSize& aSize)
{
if (!gImageCache) {
gImageCache = new ImageCache();
}
ImageCacheEntry* entry = gImageCache->mCache.PutEntry(ImageCacheKey(aImage, aCanvas));
if (entry) {
if (entry->mData->mSurface) {
// We are overwriting an existing entry.
gImageCache->RemoveObject(entry->mData);
}
gImageCache->AddObject(entry->mData);
nsCOMPtr<nsIImageLoadingContent> ilc = do_QueryInterface(aImage);
if (ilc) {
ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
getter_AddRefs(entry->mData->mRequest));
}
entry->mData->mSurface = aSurface;
entry->mData->mSize = aSize;
}
}
gfxASurface*
CanvasImageCache::Lookup(nsIDOMElement* aImage,
nsHTMLCanvasElement* aCanvas,
gfxIntSize* aSize)
{
if (!gImageCache)
return nsnull;
ImageCacheEntry* entry = gImageCache->mCache.GetEntry(ImageCacheKey(aImage, aCanvas));
if (!entry)
return nsnull;
nsCOMPtr<nsIImageLoadingContent> ilc = do_QueryInterface(aImage);
if (!ilc)
return nsnull;
nsCOMPtr<imgIRequest> request;
ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(request));
if (request != entry->mData->mRequest)
return nsnull;
gImageCache->MarkUsed(entry->mData);
*aSize = entry->mData->mSize;
return entry->mData->mSurface;
}
void
CanvasImageCache::Shutdown()
{
delete gImageCache;
gImageCache = nsnull;
}
}

View File

@ -0,0 +1,77 @@
/* -*- 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 Corporation code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Robert O'Callahan <robert@ocallahan.org>
*
* 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 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 ***** */
#ifndef CANVASIMAGECACHE_H_
#define CANVASIMAGECACHE_H_
class nsIDOMElement;
class nsHTMLCanvasElement;
class imgIRequest;
class gfxASurface;
struct gfxIntSize;
namespace mozilla {
class CanvasImageCache {
public:
/**
* Notify that image element aImage was (or is about to be) drawn to aCanvas
* using the first frame of aRequest's image. The data for the surface is
* in aSurface, and the image size is in aSize.
*/
static void NotifyDrawImage(nsIDOMElement* aImage,
nsHTMLCanvasElement* aCanvas,
imgIRequest* aRequest,
gfxASurface* aSurface,
const gfxIntSize& aSize);
/**
* Check whether aImage has recently been drawn into aCanvas. If we return
* a non-null surface, then the image was recently drawn into the canvas
* (with the same image request) and the returned surface contains the image
* data, and the image size will be returned in aSize.
*/
static gfxASurface* Lookup(nsIDOMElement* aImage,
nsHTMLCanvasElement* aCanvas,
gfxIntSize* aSize);
static void Shutdown();
};
}
#endif /* CANVASIMAGECACHE_H_ */

View File

@ -52,6 +52,7 @@ EXPORTS = \
$(NULL)
CPPSRCS = \
CanvasImageCache.cpp \
CanvasUtils.cpp \
nsCanvasRenderingContext2D.cpp \
$(NULL)

View File

@ -111,18 +111,13 @@
#include "gfxUtils.h"
#include "nsFrameManager.h"
#include "nsFrameLoader.h"
#include "nsBidiPresUtils.h"
#include "Layers.h"
#include "CanvasUtils.h"
#include "nsIMemoryReporter.h"
#include "nsStyleUtil.h"
#include "CanvasImageCache.h"
#ifdef MOZ_IPC
# include <algorithm>
@ -3324,35 +3319,45 @@ nsCanvasRenderingContext2D::DrawImage(nsIDOMElement *imgElt, float a1,
gfxMatrix matrix;
nsRefPtr<gfxPattern> pattern;
nsRefPtr<gfxPath> path;
gfxIntSize imgSize;
nsRefPtr<gfxASurface> imgsurf =
CanvasImageCache::Lookup(imgElt, HTMLCanvasElement(), &imgSize);
// The canvas spec says that drawImage should draw the first frame
// of animated images
PRUint32 sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME;
nsLayoutUtils::SurfaceFromElementResult res =
nsLayoutUtils::SurfaceFromElement(imgElt, sfeFlags);
if (!res.mSurface) {
// Spec says to silently do nothing if the element is still loading.
return res.mIsStillLoading ? NS_OK : NS_ERROR_NOT_AVAILABLE;
}
if (!imgsurf) {
// The canvas spec says that drawImage should draw the first frame
// of animated images
PRUint32 sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME;
nsLayoutUtils::SurfaceFromElementResult res =
nsLayoutUtils::SurfaceFromElement(imgElt, sfeFlags);
if (!res.mSurface) {
// Spec says to silently do nothing if the element is still loading.
return res.mIsStillLoading ? NS_OK : NS_ERROR_NOT_AVAILABLE;
}
#ifndef WINCE
// On non-CE, force a copy if we're using drawImage with our destination
// as a source to work around some Cairo self-copy semantics issues.
if (res.mSurface == mSurface) {
sfeFlags |= nsLayoutUtils::SFE_WANT_NEW_SURFACE;
res = nsLayoutUtils::SurfaceFromElement(imgElt, sfeFlags);
if (!res.mSurface)
return NS_ERROR_NOT_AVAILABLE;
}
// On non-CE, force a copy if we're using drawImage with our destination
// as a source to work around some Cairo self-copy semantics issues.
if (res.mSurface == mSurface) {
sfeFlags |= nsLayoutUtils::SFE_WANT_NEW_SURFACE;
res = nsLayoutUtils::SurfaceFromElement(imgElt, sfeFlags);
if (!res.mSurface)
return NS_ERROR_NOT_AVAILABLE;
}
#endif
nsRefPtr<gfxASurface> imgsurf = res.mSurface;
nsCOMPtr<nsIPrincipal> principal = res.mPrincipal;
gfxIntSize imgSize = res.mSize;
PRBool forceWriteOnly = res.mIsWriteOnly;
imgsurf = res.mSurface.forget();
imgSize = res.mSize;
if (mCanvasElement)
CanvasUtils::DoDrawImageSecurityCheck(HTMLCanvasElement(), principal, forceWriteOnly);
if (mCanvasElement) {
CanvasUtils::DoDrawImageSecurityCheck(HTMLCanvasElement(),
res.mPrincipal, res.mIsWriteOnly);
}
if (res.mImageRequest) {
CanvasImageCache::NotifyDrawImage(imgElt, HTMLCanvasElement(),
res.mImageRequest, imgsurf, imgSize);
}
}
gfxContextPathAutoSaveRestore pathSR(mThebes, PR_FALSE);

View File

@ -3767,8 +3767,6 @@ nsLayoutUtils::SurfaceFromElement(nsIDOMElement *aElement,
gfxUtils::UnpremultiplyImageSurface(static_cast<gfxImageSurface*>(surf.get()));
}
nsCOMPtr<nsIPrincipal> principal = node->NodePrincipal();
result.mSurface = surf;
result.mSize = size;
result.mPrincipal = node->NodePrincipal();
@ -3821,7 +3819,7 @@ nsLayoutUtils::SurfaceFromElement(nsIDOMElement *aElement,
result.mSurface = surf;
result.mSize = size;
result.mPrincipal = principal;
result.mPrincipal = principal.forget();
result.mIsWriteOnly = PR_FALSE;
return result;
@ -3926,13 +3924,13 @@ nsLayoutUtils::SurfaceFromElement(nsIDOMElement *aElement,
result.mSurface = gfxsurf;
result.mSize = gfxIntSize(imgWidth, imgHeight);
result.mPrincipal = principal;
result.mPrincipal = principal.forget();
// SVG images could have <foreignObject> and/or <image> elements that load
// content from another domain. For safety, they make the canvas write-only.
// XXXdholbert We could probably be more permissive here if we check that our
// helper SVG document has no elements that could load remote content.
result.mIsWriteOnly = (imgContainer->GetType() == imgIContainer::TYPE_VECTOR);
result.mImageRequest = imgRequest.forget();
return result;
}

View File

@ -1271,7 +1271,7 @@ public:
};
struct SurfaceFromElementResult {
SurfaceFromElementResult() : mIsStillLoading(PR_FALSE) {}
SurfaceFromElementResult() : mIsWriteOnly(PR_TRUE), mIsStillLoading(PR_FALSE) {}
/* mSurface will contain the resulting surface, or will be NULL on error */
nsRefPtr<gfxASurface> mSurface;
@ -1279,11 +1279,13 @@ public:
gfxIntSize mSize;
/* The principal associated with the element whose surface was returned */
nsCOMPtr<nsIPrincipal> mPrincipal;
/* The image request, if the element is an nsIImageLoadingContent */
nsCOMPtr<imgIRequest> mImageRequest;
/* Whether the element was "write only", that is, the bits should not be exposed to content */
PRBool mIsWriteOnly;
PRPackedBool mIsWriteOnly;
/* Whether the element was still loading. Some consumers need to handle
this case specially. */
PRBool mIsStillLoading;
PRPackedBool mIsStillLoading;
};
static SurfaceFromElementResult SurfaceFromElement(nsIDOMElement *aElement,

View File

@ -322,6 +322,7 @@ LOCAL_INCLUDES += -I$(srcdir)/../base \
-I$(srcdir)/../xul/content/src \
-I$(srcdir)/../xul/base/src \
-I$(topsrcdir)/content/base/src \
-I$(topsrcdir)/content/canvas/src \
-I$(topsrcdir)/content/html/content/src \
-I$(topsrcdir)/content/html/document/src \
-I$(topsrcdir)/content/html/style/src \

View File

@ -129,9 +129,12 @@
#include "nsContentSink.h"
#include "nsFrameMessageManager.h"
#include "nsRefreshDriver.h"
#include "CanvasImageCache.h"
extern void NS_ShutdownChainItemPool();
using namespace mozilla;
nsrefcnt nsLayoutStatics::sLayoutStaticRefcnt = 0;
nsresult
@ -292,6 +295,7 @@ nsLayoutStatics::Initialize()
void
nsLayoutStatics::Shutdown()
{
CanvasImageCache::Shutdown();
nsFrameScriptExecutor::Shutdown();
nsFocusManager::Shutdown();
#ifdef MOZ_XUL