mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-28 05:10:49 +00:00
Back out bug 731419 and bug 732820 due to orange.
Backs out changesets 55e63a03ccad 45d2f5e2fe31 f78900832562 6184b50776fc 35d8045aeadd 42e887fec034.
This commit is contained in:
parent
c9c317eca0
commit
53959f7e57
@ -3247,14 +3247,6 @@ nsIDocument::TakeFrameRequestCallbacks(FrameRequestCallbackList& aCallbacks)
|
||||
mFrameRequestCallbacks.Clear();
|
||||
}
|
||||
|
||||
PLDHashOperator RequestDiscardEnumerator(imgIRequest* aKey,
|
||||
PRUint32 aData,
|
||||
void* userArg)
|
||||
{
|
||||
aKey->RequestDiscard();
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
void
|
||||
nsDocument::DeleteShell()
|
||||
{
|
||||
@ -3263,11 +3255,6 @@ nsDocument::DeleteShell()
|
||||
RevokeAnimationFrameNotifications();
|
||||
}
|
||||
|
||||
// When our shell goes away, request that all our images be immediately
|
||||
// discarded, so we don't carry around decoded image data for a document we
|
||||
// no longer intend to paint.
|
||||
mImageTracker.EnumerateRead(RequestDiscardEnumerator, nsnull);
|
||||
|
||||
mPresShell = nsnull;
|
||||
}
|
||||
|
||||
@ -8322,32 +8309,26 @@ nsDocument::RemoveImage(imgIRequest* aImage)
|
||||
|
||||
// If the count is now zero, remove from the tracker.
|
||||
// Otherwise, set the new value.
|
||||
if (count != 0) {
|
||||
if (count == 0) {
|
||||
mImageTracker.Remove(aImage);
|
||||
} else {
|
||||
mImageTracker.Put(aImage, count);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mImageTracker.Remove(aImage);
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
// Now that we're no longer tracking this image, unlock it if we'd
|
||||
// previously locked it.
|
||||
if (mLockingImages) {
|
||||
// If we removed the image from the tracker and we're locking images, unlock
|
||||
// this image.
|
||||
if (count == 0 && mLockingImages)
|
||||
rv = aImage->UnlockImage();
|
||||
}
|
||||
|
||||
// If we're animating images, remove our request to animate this one.
|
||||
if (mAnimatingImages) {
|
||||
// If we removed the image from the tracker and we're animating images,
|
||||
// remove our request to animate this image.
|
||||
if (count == 0 && mAnimatingImages) {
|
||||
nsresult rv2 = aImage->DecrementAnimationConsumers();
|
||||
rv = NS_SUCCEEDED(rv) ? rv2 : rv;
|
||||
}
|
||||
|
||||
// Request that the image be discarded if nobody else holds a lock on it.
|
||||
// Do this even if !mLockingImages, because even if we didn't just unlock
|
||||
// this image, it might still be a candidate for discarding.
|
||||
aImage->RequestDiscard();
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ native gfxGraphicsFilter(gfxPattern::GraphicsFilter);
|
||||
*
|
||||
* Internally, imgIContainer also manages animation of images.
|
||||
*/
|
||||
[scriptable, uuid(8bf87433-be67-413b-9497-00071c5002bd)]
|
||||
[scriptable, uuid(2506249c-e0a1-4d8f-846c-2d478247f8d8)]
|
||||
interface imgIContainer : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -283,12 +283,6 @@ interface imgIContainer : nsISupports
|
||||
*/
|
||||
void unlockImage();
|
||||
|
||||
/**
|
||||
* If this image is unlocked, discard its decoded data. If the image is
|
||||
* locked or has already been discarded, do nothing.
|
||||
*/
|
||||
void requestDiscard();
|
||||
|
||||
/**
|
||||
* Indicates that this imgIContainer has been triggered to update
|
||||
* its internal animation state. Likely this should only be called
|
||||
|
@ -52,7 +52,7 @@ interface nsIPrincipal;
|
||||
* @version 0.1
|
||||
* @see imagelib2
|
||||
*/
|
||||
[scriptable, uuid(d35a9adb-8328-4b64-b06f-72a165acd080)]
|
||||
[scriptable, uuid(c3bf4e2a-f64b-4ac1-a84e-18631b1802ab)]
|
||||
interface imgIRequest : nsIRequest
|
||||
{
|
||||
/**
|
||||
@ -194,12 +194,6 @@ interface imgIRequest : nsIRequest
|
||||
*/
|
||||
void unlockImage();
|
||||
|
||||
/**
|
||||
* If this image is unlocked, discard the image's decoded data. If the image
|
||||
* is locked or is already discarded, do nothing.
|
||||
*/
|
||||
void requestDiscard();
|
||||
|
||||
/**
|
||||
* If this request is for an animated image, the method creates a new
|
||||
* request which contains the current frame of the image.
|
||||
|
@ -1,7 +1,39 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
/* -*- 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 Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Bobby Holley <bobbyholley@gmail.com>
|
||||
*
|
||||
* 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 "nsComponentManagerUtils.h"
|
||||
#include "nsITimer.h"
|
||||
@ -12,95 +44,77 @@
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
static const char* sDiscardTimeoutPref = "image.mem.min_discard_timeout_ms";
|
||||
|
||||
/* static */ LinkedList<DiscardTracker::Node> DiscardTracker::sDiscardableImages;
|
||||
/* static */ nsCOMPtr<nsITimer> DiscardTracker::sTimer;
|
||||
/* static */ bool DiscardTracker::sInitialized = false;
|
||||
/* static */ bool DiscardTracker::sTimerOn = false;
|
||||
/* static */ bool DiscardTracker::sDiscardRunnablePending = false;
|
||||
/* static */ PRUint64 DiscardTracker::sCurrentDecodedImageBytes = 0;
|
||||
/* static */ PRUint32 DiscardTracker::sMinDiscardTimeoutMs = 10000;
|
||||
/* static */ PRUint32 DiscardTracker::sMaxDecodedImageKB = 42 * 1024;
|
||||
static bool sInitialized = false;
|
||||
static bool sTimerOn = false;
|
||||
static PRUint32 sMinDiscardTimeoutMs = 10000; /* Default if pref unreadable. */
|
||||
static nsITimer *sTimer = nsnull;
|
||||
static struct DiscardTrackerNode sHead, sSentinel, sTail;
|
||||
|
||||
/*
|
||||
* When we notice we're using too much memory for decoded images, we enqueue a
|
||||
* DiscardRunnable, which runs this code.
|
||||
* Puts an image in the back of the tracker queue. If the image is already
|
||||
* in the tracker, this removes it first.
|
||||
*/
|
||||
NS_IMETHODIMP
|
||||
DiscardTracker::DiscardRunnable::Run()
|
||||
{
|
||||
sDiscardRunnablePending = false;
|
||||
DiscardTracker::DiscardNow();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
int
|
||||
DiscardTimeoutChangedCallback(const char* aPref, void *aClosure)
|
||||
{
|
||||
DiscardTracker::ReloadTimeout();
|
||||
return 0;
|
||||
}
|
||||
|
||||
nsresult
|
||||
DiscardTracker::Reset(Node *node)
|
||||
DiscardTracker::Reset(DiscardTrackerNode *node)
|
||||
{
|
||||
// We shouldn't call Reset() with a null |img| pointer, on images which can't
|
||||
// be discarded, or on animated images (which should be marked as
|
||||
// non-discardable, anyway).
|
||||
MOZ_ASSERT(node->img);
|
||||
MOZ_ASSERT(node->img->CanDiscard());
|
||||
MOZ_ASSERT(!node->img->mAnim);
|
||||
|
||||
// Initialize the first time through.
|
||||
nsresult rv;
|
||||
#ifdef DEBUG
|
||||
bool isSentinel = (node == &sSentinel);
|
||||
|
||||
// Sanity check the node.
|
||||
NS_ABORT_IF_FALSE(isSentinel || node->curr, "Node doesn't point to anything!");
|
||||
|
||||
// We should not call this function if we can't discard
|
||||
NS_ABORT_IF_FALSE(isSentinel || node->curr->CanDiscard(),
|
||||
"trying to reset discarding but can't discard!");
|
||||
|
||||
// As soon as an image becomes animated it is set non-discardable
|
||||
NS_ABORT_IF_FALSE(isSentinel || !node->curr->mAnim,
|
||||
"Trying to reset discarding on animated image!");
|
||||
#endif
|
||||
|
||||
// Initialize the first time through
|
||||
if (NS_UNLIKELY(!sInitialized)) {
|
||||
rv = Initialize();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// Insert the node at the front of the list and note when it was inserted.
|
||||
bool wasInList = node->isInList();
|
||||
if (wasInList) {
|
||||
node->remove();
|
||||
}
|
||||
node->timestamp = TimeStamp::Now();
|
||||
sDiscardableImages.insertFront(node);
|
||||
// Remove the node if it's in the list.
|
||||
Remove(node);
|
||||
|
||||
// If the node wasn't already in the list of discardable images, then we may
|
||||
// need to discard some images to stay under the sMaxDecodedImageKB limit.
|
||||
// Call MaybeDiscardSoon to do this check.
|
||||
if (!wasInList) {
|
||||
MaybeDiscardSoon();
|
||||
}
|
||||
// Append it to the list.
|
||||
node->prev = sTail.prev;
|
||||
node->next = &sTail;
|
||||
node->prev->next = sTail.prev = node;
|
||||
|
||||
// Make sure the timer is running.
|
||||
rv = EnableTimer();
|
||||
// Make sure the timer is running
|
||||
rv = TimerOn();
|
||||
NS_ENSURE_SUCCESS(rv,rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
DiscardTracker::Remove(Node *node)
|
||||
{
|
||||
if (node->isInList())
|
||||
node->remove();
|
||||
|
||||
if (sDiscardableImages.isEmpty())
|
||||
DisableTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shut down the tracker, deallocating the timer.
|
||||
/*
|
||||
* Removes a node from the tracker. No-op if the node is currently untracked.
|
||||
*/
|
||||
void
|
||||
DiscardTracker::Shutdown()
|
||||
DiscardTracker::Remove(DiscardTrackerNode *node)
|
||||
{
|
||||
if (sTimer) {
|
||||
sTimer->Cancel();
|
||||
sTimer = NULL;
|
||||
NS_ABORT_IF_FALSE(node != nsnull, "Can't pass null node");
|
||||
|
||||
// If we're not in a list, we have nothing to do.
|
||||
if ((node->prev == nsnull) || (node->next == nsnull)) {
|
||||
NS_ABORT_IF_FALSE(node->prev == node->next,
|
||||
"Node is half in a list!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Connect around ourselves
|
||||
node->prev->next = node->next;
|
||||
node->next->prev = node->prev;
|
||||
|
||||
// Clean up the node we removed.
|
||||
node->prev = node->next = nsnull;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -112,23 +126,31 @@ DiscardTracker::DiscardAll()
|
||||
if (!sInitialized)
|
||||
return;
|
||||
|
||||
sDiscardableImages.clear();
|
||||
// Remove the sentinel from the list so that the only elements in the list
|
||||
// which don't track an image are the head and tail.
|
||||
Remove(&sSentinel);
|
||||
|
||||
// The list is empty, so there's no need to leave the timer on.
|
||||
DisableTimer();
|
||||
// Discard all tracked images.
|
||||
for (DiscardTrackerNode *node = sHead.next;
|
||||
node != &sTail; node = sHead.next) {
|
||||
NS_ABORT_IF_FALSE(node->curr, "empty node!");
|
||||
Remove(node);
|
||||
node->curr->Discard();
|
||||
}
|
||||
|
||||
// Add the sentinel back to the (now empty) list.
|
||||
Reset(&sSentinel);
|
||||
|
||||
// Because the sentinel is the only element in the list, the next timer event
|
||||
// would be a no-op. Disable the timer as an optimization.
|
||||
TimerOff();
|
||||
}
|
||||
|
||||
void
|
||||
DiscardTracker::InformAllocation(PRUint64 bytes)
|
||||
static int
|
||||
DiscardTimeoutChangedCallback(const char* aPref, void *aClosure)
|
||||
{
|
||||
// This function is called back e.g. from RasterImage::Discard(); be careful!
|
||||
|
||||
sCurrentDecodedImageBytes += bytes;
|
||||
MOZ_ASSERT(sCurrentDecodedImageBytes >= 0);
|
||||
|
||||
// If we're using too much memory for decoded images, MaybeDiscardSoon will
|
||||
// enqueue a callback to discard some images.
|
||||
MaybeDiscardSoon();
|
||||
DiscardTracker::ReloadTimeout();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -137,57 +159,82 @@ DiscardTracker::InformAllocation(PRUint64 bytes)
|
||||
nsresult
|
||||
DiscardTracker::Initialize()
|
||||
{
|
||||
nsresult rv;
|
||||
|
||||
// Set up the list. Head<->Sentinel<->Tail
|
||||
sHead.curr = sTail.curr = sSentinel.curr = nsnull;
|
||||
sHead.prev = sTail.next = nsnull;
|
||||
sHead.next = sTail.prev = &sSentinel;
|
||||
sSentinel.prev = &sHead;
|
||||
sSentinel.next = &sTail;
|
||||
|
||||
// Watch the timeout pref for changes.
|
||||
Preferences::RegisterCallback(DiscardTimeoutChangedCallback,
|
||||
sDiscardTimeoutPref);
|
||||
DISCARD_TIMEOUT_PREF);
|
||||
|
||||
Preferences::AddUintVarCache(&sMaxDecodedImageKB,
|
||||
"image.mem.max_decoded_image_kb",
|
||||
50 * 1024);
|
||||
|
||||
// Create the timer.
|
||||
sTimer = do_CreateInstance("@mozilla.org/timer;1");
|
||||
|
||||
// Read the timeout pref and start the timer.
|
||||
ReloadTimeout();
|
||||
|
||||
// Create and start the timer
|
||||
nsCOMPtr<nsITimer> t = do_CreateInstance("@mozilla.org/timer;1");
|
||||
NS_ENSURE_TRUE(t, NS_ERROR_OUT_OF_MEMORY);
|
||||
t.forget(&sTimer);
|
||||
rv = TimerOn();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Mark us as initialized
|
||||
sInitialized = true;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shut down the tracker, deallocating the timer.
|
||||
*/
|
||||
void
|
||||
DiscardTracker::Shutdown()
|
||||
{
|
||||
if (sTimer) {
|
||||
sTimer->Cancel();
|
||||
NS_RELEASE(sTimer);
|
||||
sTimer = nsnull;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the discard timeout from about:config.
|
||||
*/
|
||||
void
|
||||
DiscardTracker::ReloadTimeout()
|
||||
{
|
||||
// Read the timeout pref.
|
||||
PRInt32 discardTimeout;
|
||||
nsresult rv = Preferences::GetInt(sDiscardTimeoutPref, &discardTimeout);
|
||||
nsresult rv;
|
||||
|
||||
// If we got something bogus, return.
|
||||
// read the timeout pref
|
||||
PRInt32 discardTimeout;
|
||||
rv = Preferences::GetInt(DISCARD_TIMEOUT_PREF, &discardTimeout);
|
||||
|
||||
// If we got something bogus, return
|
||||
if (!NS_SUCCEEDED(rv) || discardTimeout <= 0)
|
||||
return;
|
||||
|
||||
// If the value didn't change, return.
|
||||
// If the value didn't change, return
|
||||
if ((PRUint32) discardTimeout == sMinDiscardTimeoutMs)
|
||||
return;
|
||||
|
||||
// Update the value.
|
||||
// Update the value
|
||||
sMinDiscardTimeoutMs = (PRUint32) discardTimeout;
|
||||
|
||||
// Restart the timer so the new timeout takes effect.
|
||||
DisableTimer();
|
||||
EnableTimer();
|
||||
// If the timer's on, restart the clock to make changes take effect
|
||||
if (sTimerOn) {
|
||||
TimerOff();
|
||||
TimerOn();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the timer. No-op if the timer is already running.
|
||||
*/
|
||||
nsresult
|
||||
DiscardTracker::EnableTimer()
|
||||
DiscardTracker::TimerOn()
|
||||
{
|
||||
// Nothing to do if the timer's already on.
|
||||
if (sTimerOn)
|
||||
@ -205,7 +252,7 @@ DiscardTracker::EnableTimer()
|
||||
* Disables the timer. No-op if the timer isn't running.
|
||||
*/
|
||||
void
|
||||
DiscardTracker::DisableTimer()
|
||||
DiscardTracker::TimerOff()
|
||||
{
|
||||
// Nothing to do if the timer's already off.
|
||||
if (!sTimerOn)
|
||||
@ -217,59 +264,29 @@ DiscardTracker::DisableTimer()
|
||||
}
|
||||
|
||||
/**
|
||||
* Routine activated when the timer fires. This discards everything that's
|
||||
* older than sMinDiscardTimeoutMs, and tries to discard enough images so that
|
||||
* we go under sMaxDecodedImageKB.
|
||||
* Routine activated when the timer fires. This discards everything
|
||||
* in front of sentinel, and resets the sentinel to the back of the
|
||||
* list.
|
||||
*/
|
||||
void
|
||||
DiscardTracker::TimerCallback(nsITimer *aTimer, void *aClosure)
|
||||
{
|
||||
DiscardNow();
|
||||
}
|
||||
DiscardTrackerNode *node;
|
||||
|
||||
void
|
||||
DiscardTracker::DiscardNow()
|
||||
{
|
||||
// Assuming the list is ordered with oldest discard tracker nodes at the back
|
||||
// and newest ones at the front, iterate from back to front discarding nodes
|
||||
// until we encounter one which is new enough to keep and until we go under
|
||||
// our sMaxDecodedImageKB limit.
|
||||
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
Node* node;
|
||||
while ((node = sDiscardableImages.getLast())) {
|
||||
if ((now - node->timestamp).ToMilliseconds() > sMinDiscardTimeoutMs ||
|
||||
sCurrentDecodedImageBytes > sMaxDecodedImageKB * 1024) {
|
||||
|
||||
// Discarding the image should cause sCurrentDecodedImageBytes to
|
||||
// decrease via a call to InformAllocation().
|
||||
node->img->Discard();
|
||||
|
||||
// Careful: Discarding may have caused the node to have been removed
|
||||
// from the list.
|
||||
Remove(node);
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
// Remove and discard everything before the sentinel
|
||||
for (node = sSentinel.prev; node != &sHead; node = sSentinel.prev) {
|
||||
NS_ABORT_IF_FALSE(node->curr, "empty node!");
|
||||
Remove(node);
|
||||
node->curr->Discard();
|
||||
}
|
||||
|
||||
// If the list is empty, disable the timer.
|
||||
if (sDiscardableImages.isEmpty())
|
||||
DisableTimer();
|
||||
}
|
||||
// Append the sentinel to the back of the list
|
||||
Reset(&sSentinel);
|
||||
|
||||
void
|
||||
DiscardTracker::MaybeDiscardSoon()
|
||||
{
|
||||
// Are we carrying around too much decoded image data? If so, enqueue an
|
||||
// event to try to get us down under our limit.
|
||||
if (sCurrentDecodedImageBytes > sMaxDecodedImageKB * 1024 &&
|
||||
!sDiscardableImages.isEmpty() && !sDiscardRunnablePending) {
|
||||
sDiscardRunnablePending = true;
|
||||
nsRefPtr<DiscardRunnable> runnable = new DiscardRunnable();
|
||||
NS_DispatchToCurrentThread(runnable);
|
||||
}
|
||||
// If there's nothing in front of the sentinel, the next callback
|
||||
// is guaranteed to be a no-op. Disable the timer as an optimization.
|
||||
if (sSentinel.prev == &sHead)
|
||||
TimerOff();
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
|
@ -1,113 +1,87 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
/* -*- 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 Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Bobby Holley <bobbyholley@gmail.com>
|
||||
*
|
||||
* 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 mozilla_imagelib_DiscardTracker_h_
|
||||
#define mozilla_imagelib_DiscardTracker_h_
|
||||
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#define DISCARD_TIMEOUT_PREF "image.mem.min_discard_timeout_ms"
|
||||
|
||||
class nsITimer;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class RasterImage;
|
||||
|
||||
// Struct to make a RasterImage insertable into the tracker list. This
|
||||
// is embedded within each RasterImage object, and we do 'this->curr = this'
|
||||
// on RasterImage construction. Thus, a RasterImage must always call
|
||||
// DiscardTracker::Remove() in its destructor to avoid having the tracker
|
||||
// point to bogus memory.
|
||||
struct DiscardTrackerNode
|
||||
{
|
||||
// Pointer to the RasterImage that this node tracks
|
||||
RasterImage *curr;
|
||||
|
||||
// Pointers to the previous and next nodes in the list
|
||||
DiscardTrackerNode *prev, *next;
|
||||
};
|
||||
|
||||
/**
|
||||
* This static class maintains a linked list of RasterImage objects which are
|
||||
* eligible for discarding.
|
||||
*
|
||||
* When Reset() is called, the node is removed from its position in the list
|
||||
* (if it was there before) and appended to the beginnings of the list.
|
||||
*
|
||||
* Periodically (on a timer and when we notice that we're using more memory
|
||||
* than we'd like for decoded images), we go through the list and discard
|
||||
* decoded data from images at the end of the list.
|
||||
* This static class maintains a linked list of RasterImage nodes. When Reset()
|
||||
* is called, the node is removed from its position in the list (if it was there
|
||||
* before) and appended to the end. When Remove() is called, the node is removed
|
||||
* from the list. The timer fires once every MIN_DISCARD_TIMEOUT_MS ms. When it
|
||||
* does, it calls Discard() on each container preceding it, and then appends
|
||||
* itself to the end of the list. Thus, the discard timeout varies between
|
||||
* MIN_DISCARD_TIMEOUT_MS and 2*MIN_DISCARD_TIMEOUT_MS.
|
||||
*/
|
||||
class DiscardTracker
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* The DiscardTracker keeps a linked list of Node objects. Each object
|
||||
* points to a RasterImage and contains a timestamp indicating when the
|
||||
* node was inserted into the tracker.
|
||||
*
|
||||
* This structure is embedded within each RasterImage object, and we do
|
||||
* |mDiscardTrackerNode.img = this| on RasterImage construction. Thus, a
|
||||
* RasterImage must always call DiscardTracker::Remove() in its destructor
|
||||
* to avoid having the tracker point to bogus memory.
|
||||
*/
|
||||
struct Node : public LinkedListElement<Node>
|
||||
{
|
||||
RasterImage *img;
|
||||
TimeStamp timestamp;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an image to the front of the tracker's list, or move it to the front
|
||||
* if it's already in the list.
|
||||
*/
|
||||
static nsresult Reset(struct Node* node);
|
||||
|
||||
/**
|
||||
* Remove a node from the tracker; do nothing if the node is currently
|
||||
* untracked.
|
||||
*/
|
||||
static void Remove(struct Node* node);
|
||||
|
||||
/**
|
||||
* Shut the discard tracker down. This should be called on XPCOM shutdown
|
||||
* so we destroy the discard timer's nsITimer.
|
||||
*/
|
||||
static nsresult Reset(struct DiscardTrackerNode *node);
|
||||
static void Remove(struct DiscardTrackerNode *node);
|
||||
static void Shutdown();
|
||||
|
||||
/**
|
||||
* Discard the decoded image data for all images tracked by the discard
|
||||
* tracker.
|
||||
*/
|
||||
static void DiscardAll();
|
||||
|
||||
/**
|
||||
* Inform the discard tracker that we've allocated or deallocated some
|
||||
* memory for a decoded image. We use this to determine when we've
|
||||
* allocated too much memory and should discard some images.
|
||||
*/
|
||||
static void InformAllocation(PRUint64 bytes);
|
||||
|
||||
private:
|
||||
/**
|
||||
* This is called when the discard timer fires; it calls into DiscardNow().
|
||||
*/
|
||||
friend int DiscardTimeoutChangedCallback(const char* aPref, void *aClosure);
|
||||
|
||||
/**
|
||||
* When run, this runnable sets sDiscardRunnablePending to false and calls
|
||||
* DiscardNow().
|
||||
*/
|
||||
class DiscardRunnable : public nsRunnable
|
||||
{
|
||||
NS_IMETHOD Run();
|
||||
};
|
||||
|
||||
static nsresult Initialize();
|
||||
static void ReloadTimeout();
|
||||
static nsresult EnableTimer();
|
||||
static void DisableTimer();
|
||||
static void MaybeDiscardSoon();
|
||||
static void DiscardAll();
|
||||
private:
|
||||
static nsresult Initialize();
|
||||
static nsresult TimerOn();
|
||||
static void TimerOff();
|
||||
static void TimerCallback(nsITimer *aTimer, void *aClosure);
|
||||
static void DiscardNow();
|
||||
|
||||
static LinkedList<Node> sDiscardableImages;
|
||||
static nsCOMPtr<nsITimer> sTimer;
|
||||
static bool sInitialized;
|
||||
static bool sTimerOn;
|
||||
static bool sDiscardRunnablePending;
|
||||
static PRUint64 sCurrentDecodedImageBytes;
|
||||
static PRUint32 sMinDiscardTimeoutMs;
|
||||
static PRUint32 sMaxDecodedImageKB;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
|
@ -214,7 +214,8 @@ RasterImage::RasterImage(imgStatusTracker* aStatusTracker) :
|
||||
mAnimationFinished(false)
|
||||
{
|
||||
// Set up the discard tracker node.
|
||||
mDiscardTrackerNode.img = this;
|
||||
mDiscardTrackerNode.curr = this;
|
||||
mDiscardTrackerNode.prev = mDiscardTrackerNode.next = nsnull;
|
||||
Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0);
|
||||
|
||||
// Statistics
|
||||
@ -2181,7 +2182,8 @@ RasterImage::Discard(bool force)
|
||||
if (observer)
|
||||
observer->OnDiscard(nsnull);
|
||||
|
||||
DiscardTracker::Remove(&mDiscardTrackerNode);
|
||||
if (force)
|
||||
DiscardTracker::Remove(&mDiscardTrackerNode);
|
||||
|
||||
// Log
|
||||
PR_LOG(gCompressedImageAccountingLog, PR_LOG_DEBUG,
|
||||
@ -2219,7 +2221,7 @@ RasterImage::CanForciblyDiscard() {
|
||||
// discarding this image. Mainly for assertions.
|
||||
bool
|
||||
RasterImage::DiscardingActive() {
|
||||
return mDiscardTrackerNode.isInList();
|
||||
return !!(mDiscardTrackerNode.prev || mDiscardTrackerNode.next);
|
||||
}
|
||||
|
||||
// Helper method to determine if we're storing the source data in a buffer
|
||||
@ -2694,18 +2696,6 @@ RasterImage::UnlockImage()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
/* void requestDiscard() */
|
||||
NS_IMETHODIMP
|
||||
RasterImage::RequestDiscard()
|
||||
{
|
||||
if (CanDiscard()) {
|
||||
Discard();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Flushes up to aMaxBytes to the decoder.
|
||||
nsresult
|
||||
RasterImage::DecodeSomeData(PRUint32 aMaxBytes)
|
||||
|
@ -198,7 +198,6 @@ public:
|
||||
NS_SCRIPTABLE NS_IMETHOD RequestDecode(void);
|
||||
NS_SCRIPTABLE NS_IMETHOD LockImage(void);
|
||||
NS_SCRIPTABLE NS_IMETHOD UnlockImage(void);
|
||||
NS_SCRIPTABLE NS_IMETHOD RequestDiscard(void);
|
||||
NS_SCRIPTABLE NS_IMETHOD ResetAnimation(void);
|
||||
NS_IMETHOD_(void) RequestRefresh(const mozilla::TimeStamp& aTime);
|
||||
// END NS_DECL_IMGICONTAINER
|
||||
@ -635,7 +634,7 @@ private: // data
|
||||
|
||||
// Discard members
|
||||
PRUint32 mLockCount;
|
||||
DiscardTracker::Node mDiscardTrackerNode;
|
||||
DiscardTrackerNode mDiscardTrackerNode;
|
||||
|
||||
// Source data members
|
||||
FallibleTArray<char> mSourceData;
|
||||
|
@ -626,15 +626,6 @@ VectorImage::UnlockImage()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
/* void requestDiscard() */
|
||||
NS_IMETHODIMP
|
||||
VectorImage::RequestDiscard()
|
||||
{
|
||||
// This method is for image-discarding, which only applies to RasterImages.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
/* void resetAnimation (); */
|
||||
NS_IMETHODIMP
|
||||
|
@ -81,7 +81,6 @@ public:
|
||||
NS_SCRIPTABLE NS_IMETHOD RequestDecode(void);
|
||||
NS_SCRIPTABLE NS_IMETHOD LockImage(void);
|
||||
NS_SCRIPTABLE NS_IMETHOD UnlockImage(void);
|
||||
NS_SCRIPTABLE NS_IMETHOD RequestDiscard(void);
|
||||
NS_SCRIPTABLE NS_IMETHOD ResetAnimation(void);
|
||||
NS_IMETHOD_(void) RequestRefresh(const mozilla::TimeStamp& aTime);
|
||||
// END NS_DECL_IMGICONTAINER
|
||||
|
@ -37,7 +37,6 @@
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "imgFrame.h"
|
||||
#include "DiscardTracker.h"
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
@ -68,8 +67,6 @@ static PRUint32 gTotalDDBSize = 0;
|
||||
|
||||
#endif
|
||||
|
||||
using namespace mozilla::image;
|
||||
|
||||
// Returns true if an image of aWidth x aHeight is allowed and legal.
|
||||
static bool AllowedImageSize(PRInt32 aWidth, PRInt32 aHeight)
|
||||
{
|
||||
@ -150,7 +147,6 @@ imgFrame::imgFrame() :
|
||||
, mIsDDBSurface(false)
|
||||
#endif
|
||||
, mLocked(false)
|
||||
, mInformedDiscardTracker(false)
|
||||
{
|
||||
static bool hasCheckedOptimize = false;
|
||||
if (!hasCheckedOptimize) {
|
||||
@ -170,10 +166,6 @@ imgFrame::~imgFrame()
|
||||
gTotalDDBSize -= mSize.width * mSize.height * 4;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (mInformedDiscardTracker) {
|
||||
DiscardTracker::InformAllocation(-4 * mSize.height * mSize.width);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult imgFrame::Init(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight,
|
||||
@ -235,14 +227,6 @@ nsresult imgFrame::Init(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight,
|
||||
#endif
|
||||
}
|
||||
|
||||
// Inform the discard tracker that we've allocated some memory, but only if
|
||||
// we're not a paletted image (paletted images are not usually large and are
|
||||
// used only for animated frames, which we don't discard).
|
||||
if (!mPalettedImageData) {
|
||||
DiscardTracker::InformAllocation(4 * mSize.width * mSize.height);
|
||||
mInformedDiscardTracker = true;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -287,14 +271,6 @@ nsresult imgFrame::Optimize()
|
||||
#ifdef XP_MACOSX
|
||||
mQuartzSurface = nsnull;
|
||||
#endif
|
||||
|
||||
// We just dumped most of our allocated memory, so tell the discard
|
||||
// tracker that we're not using any at all.
|
||||
if (mInformedDiscardTracker) {
|
||||
DiscardTracker::InformAllocation(-4 * mSize.width * mSize.height);
|
||||
mInformedDiscardTracker = false;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
@ -195,9 +195,6 @@ private: // data
|
||||
/** Indicates if the image data is currently locked */
|
||||
bool mLocked;
|
||||
|
||||
/** Have we called DiscardTracker::InformAllocation()? */
|
||||
bool mInformedDiscardTracker;
|
||||
|
||||
#ifdef XP_WIN
|
||||
bool mIsDDBSurface;
|
||||
#endif
|
||||
|
@ -370,16 +370,6 @@ imgRequestProxy::UnlockImage()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* void requestDiscard (); */
|
||||
NS_IMETHODIMP
|
||||
imgRequestProxy::RequestDiscard()
|
||||
{
|
||||
if (mImage) {
|
||||
return mImage->RequestDiscard();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
imgRequestProxy::IncrementAnimationConsumers()
|
||||
{
|
||||
|
@ -88,7 +88,6 @@ _TEST_FILES = imgutils.js \
|
||||
test_bug671906.html \
|
||||
test_error_events.html \
|
||||
error-early.png \
|
||||
test_drawDiscardedImage.html \
|
||||
$(NULL)
|
||||
|
||||
# Tests disabled due to intermittent orange
|
||||
|
@ -1,85 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=731419
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 731419 - Draw an ostensibly discarded image to a canvas</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!--
|
||||
Load an image in an iframe, then draw that image to a canvas. Then set the
|
||||
iframe to display:none (after bug 731419, this causes the image's decoded
|
||||
data to be discarded) and draw the image to a canvas again. We should draw
|
||||
the same image data both times.
|
||||
-->
|
||||
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var data1;
|
||||
|
||||
function drawImage()
|
||||
{
|
||||
var canvas = document.getElementById('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
var iframeDoc = document.getElementById('iframe').contentDocument;
|
||||
|
||||
ctx.clearRect(0, 0, canvas.height, canvas.width);
|
||||
ctx.drawImage(iframeDoc.getElementById('image'), 0, 0);
|
||||
return canvas.toDataURL();
|
||||
}
|
||||
|
||||
function iframeLoad()
|
||||
{
|
||||
data1 = drawImage();
|
||||
document.getElementById('iframe').style.display = 'none';
|
||||
|
||||
// Spin the event loop a few times to give the image in the display:none
|
||||
// iframe a chance to be discarded.
|
||||
SimpleTest.executeSoon(function() {
|
||||
SimpleTest.executeSoon(function() {
|
||||
SimpleTest.executeSoon(function() {
|
||||
step2();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function step2()
|
||||
{
|
||||
is(drawImage(), data1, "Same image before and after iframe display:none");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<canvas id='canvas'></canvas>
|
||||
|
||||
<iframe id='iframe' onload='iframeLoad()' src='data:text/html,<img id="image"
|
||||
src="data:image/png;base64,
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADRElEQVQ4EQXBTWgcZQCA4ff7ZmZ3
|
||||
NpvNZLObTWpCuoZGIxWJplAKbVUKavUiHrQHaRG1XrV4SNuD4MFcRDwUoR4qEq2gFUlBEWmtppYi
|
||||
acSmMakxtfkx/5tNdmd35/8bn0cAzJ7IXwKGH/q8NDF48vy+7vk/3tzVXDs8nj9cAAiDcD70gwVi
|
||||
vvvr4tsjAAAAAmD2RD4GOL34wge21XHsnHWh9/aUjX1pC4C1UpXrP08zN7vMvvujPx3P/PD+0VH3
|
||||
BoAcTspXAbK9iuGe78+csy70ZnsVvh+xWQ8p1QI8dNK7CiT9CmeO28/4ZsuVX9/IvQwgmzLaU9LS
|
||||
AGh/3KJ5jw6A6ynyL7Xx7UCORiwQGRN0g7C4m4FX9poNV35681ShU6ZbxKDRLJVuZQl9RdSQRB4c
|
||||
OtDGoQNtPGHBuh0SaAa+ZvLjHYt8fwfZrpTl2cFp2ZwVDyQzSgLgVIndGN/tIP/c61y/WWb14gaV
|
||||
asTWioPSDabnfCqVkK7BHKHtPK0n06oFGQHgewJtbw8AujGNkYTNpTJxbYfaygqR0piYkaRkhMya
|
||||
eI2oX9dTQRIFmtrmz7EGpS9vESZjAN7tfo/UL2PouoZwbfxIo9jaoLWlzI7jEPmhLjVEbXs5IPAE
|
||||
jx5M0Z5RZDJwqjCENFN8XBtmOP0FXq1O6NR5snsRtsv4C+voCdHQpcfVtTn/xUKXTrMlyfck6BCC
|
||||
a02fkDZDqirF5JVrRA8ewagu8NbADN6az9btMoTqjnasKDTHjp5PSM3I5DQy7UliZbCz7bCwFDD/
|
||||
b52h3BCviVHOHv2bvmydyvwOM5MSmch9Ji4/SxMNcaNJTw707zdJmBqeo+G5BuO/V6AzQ5Oo01MI
|
||||
KBaTOOis3rPZrKeqrbn2hwXA10fY7zvicqeZKPQ8YpKxJCgIpEQXisBVhG6MYcQ0pGJp2XWnSpx8
|
||||
52o0ogF8c5/ltMlGIlYHo0qQrq9HxHWFvx3RqCoCFzwn4L+tiIVV5Y5MhWc/mlDnATQAgMkynbMb
|
||||
opoN4z2hUAlPBdpO6FNp+JTtkPVaHE7NYX94K/xqrBT/BvwDIAAAgALQAfT1aWJwtyYea9VEXoAo
|
||||
RfHGYhTfvRfF48BdYB3YAPgfnOuE39kFlREAAAAASUVORK5CYII=">'></iframe>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -355,18 +355,6 @@ public:
|
||||
return !sentinel.isInList();
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove all the elements from the list.
|
||||
*
|
||||
* This runs in time linear to the list's length, because we have to mark
|
||||
* each element as not in the list.
|
||||
*/
|
||||
void clear()
|
||||
{
|
||||
while (popFirst())
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* In a debug build, make sure that the list is sane (no cycles, consistent
|
||||
* next/prev pointers, only one sentinel). Has no effect in release builds.
|
||||
|
@ -3333,10 +3333,6 @@ pref("image.mem.max_ms_before_yield", 5);
|
||||
// The maximum source data size for which we auto sync decode
|
||||
pref("image.mem.max_bytes_for_sync_decode", 150000);
|
||||
|
||||
// The maximum amount of decoded image data we'll willingly keep around (we
|
||||
// might keep around more than this, but we'll try to get down to this value).
|
||||
pref("image.mem.max_decoded_image_kb", 50 * 1024);
|
||||
|
||||
// WebGL prefs
|
||||
pref("webgl.force-enabled", false);
|
||||
pref("webgl.disabled", false);
|
||||
|
Loading…
x
Reference in New Issue
Block a user