Bug 502694 - Images should not have individual discard timers.r=jrmuizel

This commit is contained in:
Bobby Holley 2010-07-01 10:39:44 -07:00
Родитель 283b92b9e1
Коммит 43f9633453
6 изменённых файлов: 344 добавлений и 78 удалений

Просмотреть файл

@ -58,6 +58,7 @@
#include "imgRequest.h"
#include "imgRequestProxy.h"
#include "imgTools.h"
#include "imgDiscardTracker.h"
#ifdef IMG_BUILD_DECODER_gif
// gif
@ -295,6 +296,7 @@ static void
imglib_Shutdown(nsIModule* aSelf)
{
imgLoader::Shutdown();
imgDiscardTracker::Shutdown();
}
NS_IMPL_NSGETMODULE_WITH_CTOR_DTOR(nsImageLib2Module, components,

Просмотреть файл

@ -58,6 +58,7 @@ CPPSRCS = \
imgRequestProxy.cpp \
imgTools.cpp \
imgContainerRequest.cpp \
imgDiscardTracker.cpp \
$(NULL)
include $(topsrcdir)/config/rules.mk

Просмотреть файл

@ -144,7 +144,6 @@ imgContainer::imgContainer() :
mLoopCount(-1),
mObserver(nsnull),
mLockCount(0),
mDiscardTimer(nsnull),
mDecoder(nsnull),
mWorker(nsnull),
mBytesDecoded(0),
@ -161,6 +160,10 @@ imgContainer::imgContainer() :
mInDecoder(PR_FALSE),
mError(PR_FALSE)
{
// Set up the discard tracker node.
mDiscardTrackerNode.curr = this;
mDiscardTrackerNode.prev = mDiscardTrackerNode.next = nsnull;
// Statistics
num_containers++;
}
@ -190,10 +193,7 @@ imgContainer::~imgContainer()
discardable_source_bytes));
}
if (mDiscardTimer) {
mDiscardTimer->Cancel();
mDiscardTimer = nsnull;
}
imgDiscardTracker::Remove(&mDiscardTrackerNode);
// If we have a decoder open, shut it down
if (mDecoder) {
@ -1026,9 +1026,9 @@ NS_IMETHODIMP imgContainer::DecodingComplete(void)
// We now have one of the qualifications for discarding. Re-evaluate.
if (CanDiscard()) {
NS_ABORT_IF_FALSE(!mDiscardTimer,
NS_ABORT_IF_FALSE(!DiscardingActive(),
"We shouldn't have been discardable before this");
rv = ResetDiscardTimer();
rv = imgDiscardTracker::Reset(&mDiscardTrackerNode);
CONTAINER_ENSURE_SUCCESS(rv);
}
@ -1345,7 +1345,7 @@ NS_IMETHODIMP imgContainer::SourceDataComplete()
// We now have one of the qualifications for discarding. Re-evaluate.
if (CanDiscard()) {
nsresult rv = ResetDiscardTimer();
nsresult rv = imgDiscardTracker::Reset(&mDiscardTrackerNode);
CONTAINER_ENSURE_SUCCESS(rv);
}
return NS_OK;
@ -2014,46 +2014,32 @@ NS_IMETHODIMP imgContainer::GetKeys(PRUint32 *count, char ***keys)
return mProperties->GetKeys(count, keys);
}
static int
get_discard_timer_ms (void)
{
/* FIXME: don't hardcode this */
return 15000; /* 15 seconds */
}
void
imgContainer::sDiscardTimerCallback(nsITimer *aTimer, void *aClosure)
imgContainer::Discard()
{
// Retrieve self pointer and null out the expired timer
imgContainer *self = (imgContainer *) aClosure;
NS_ABORT_IF_FALSE(aTimer == self->mDiscardTimer,
"imgContainer::DiscardTimerCallback() got a callback "
"for an unknown timer");
self->mDiscardTimer = nsnull;
// We should be ok for discard
NS_ABORT_IF_FALSE(self->CanDiscard(), "Hit discard callback but can't discard!");
NS_ABORT_IF_FALSE(CanDiscard(), "Asked to discard but can't!");
// We should never discard when we have an active decoder
NS_ABORT_IF_FALSE(!self->mDecoder, "Discard callback fired with open decoder!");
NS_ABORT_IF_FALSE(!mDecoder, "Asked to discard with open decoder!");
// As soon as an image becomes animated, it becomes non-discardable and any
// timers are cancelled.
NS_ABORT_IF_FALSE(!self->mAnim, "Discard callback fired for animated image!");
NS_ABORT_IF_FALSE(!mAnim, "Asked to discard for animated image!");
// For post-operation logging
int old_frame_count = self->mFrames.Length();
int old_frame_count = mFrames.Length();
// Delete all the decoded frames, then clear the array.
for (int i = 0; i < old_frame_count; ++i)
delete self->mFrames[i];
self->mFrames.Clear();
delete mFrames[i];
mFrames.Clear();
// Flag that we no longer have decoded frames for this image
self->mDecoded = PR_FALSE;
mDecoded = PR_FALSE;
// Notify that we discarded
nsCOMPtr<imgIDecoderObserver> observer(do_QueryReferent(self->mObserver));
nsCOMPtr<imgIDecoderObserver> observer(do_QueryReferent(mObserver));
if (observer)
observer->OnDiscard(nsnull);
@ -2063,43 +2049,16 @@ imgContainer::sDiscardTimerCallback(nsITimer *aTimer, void *aClosure)
"data from imgContainer %p (%s) - %d frames (cached count: %d); "
"Total Containers: %d, Discardable containers: %d, "
"Total source bytes: %lld, Source bytes for discardable containers %lld",
self,
self->mSourceDataMimeType.get(),
this,
mSourceDataMimeType.get(),
old_frame_count,
self->mFrames.Length(),
mFrames.Length(),
num_containers,
num_discardable_containers,
total_source_bytes,
discardable_source_bytes));
}
nsresult
imgContainer::ResetDiscardTimer()
{
// We should not call this function if we can't discard
NS_ABORT_IF_FALSE(CanDiscard(), "Calling ResetDiscardTimer but can't discard!");
// As soon as an image becomes animated it is set non-discardable
NS_ABORT_IF_FALSE(!mAnim, "Trying to reset discard timer on animated image!");
// If we have a timer already ticking, cancel it
if (mDiscardTimer) {
nsresult rv = mDiscardTimer->Cancel();
CONTAINER_ENSURE_SUCCESS(rv);
mDiscardTimer = nsnull;
}
// Create a new timer
mDiscardTimer = do_CreateInstance("@mozilla.org/timer;1");
CONTAINER_ENSURE_TRUE(mDiscardTimer, NS_ERROR_OUT_OF_MEMORY);
// Activate the timer
return mDiscardTimer->InitWithFuncCallback(sDiscardTimerCallback,
(void *) this,
get_discard_timer_ms (),
nsITimer::TYPE_ONE_SHOT);
}
// Helper method to determine if we can discard an image
PRBool
imgContainer::CanDiscard() {
@ -2110,6 +2069,13 @@ imgContainer::CanDiscard() {
mDecoded); // ...and have something to discard.
}
// Helper method to tell us whether the clock is currently running for
// discarding this image. Mainly for assertions.
PRBool
imgContainer::DiscardingActive() {
return !!(mDiscardTrackerNode.prev || mDiscardTrackerNode.next);
}
// Helper method to determine if we're storing the source data in a buffer
// or just writing it directly to the decoder
PRBool
@ -2130,7 +2096,7 @@ imgContainer::InitDecoder (PRUint32 dFlags)
NS_ABORT_IF_FALSE(!mDecoded, "Calling InitDecoder() but already decoded!");
// Since we're not decoded, we should not have a discard timer active
NS_ABORT_IF_FALSE(!mDiscardTimer, "Discard Timer active in InitDecoder()!");
NS_ABORT_IF_FALSE(!DiscardingActive(), "Discard Timer active in InitDecoder()!");
// Find and instantiate the decoder
nsCAutoString decoderCID(NS_LITERAL_CSTRING("@mozilla.org/image/decoder;3?type=") +
@ -2277,10 +2243,11 @@ imgContainer::WantDecodedFrames()
{
nsresult rv;
// If we can discard, we should have a timer already. reset it.
// If we can discard, the clock should be running. Reset it.
if (CanDiscard()) {
NS_ABORT_IF_FALSE(mDiscardTimer, "Decoded and discardable but timer not set!");
rv = ResetDiscardTimer();
NS_ABORT_IF_FALSE(DiscardingActive(),
"Decoded and discardable but discarding not activated!");
rv = imgDiscardTracker::Reset(&mDiscardTrackerNode);
CONTAINER_ENSURE_SUCCESS(rv);
}
@ -2445,11 +2412,7 @@ imgContainer::LockImage()
return NS_ERROR_FAILURE;
// Cancel the discard timer if it's there
if (mDiscardTimer) {
mDiscardTimer->Cancel();
mDiscardTimer = nsnull; // It's wasteful to null out the discard timers each
// time, but we'll wait to fix that until bug 502694.
}
imgDiscardTracker::Remove(&mDiscardTrackerNode);
// Increment the lock count
mLockCount++;
@ -2471,15 +2434,15 @@ imgContainer::UnlockImage()
if (mLockCount == 0)
return NS_ERROR_ABORT;
// We're locked, so we shouldn't have a discard timer set
NS_ABORT_IF_FALSE(!mDiscardTimer, "Locked, but discard timer set!");
// We're locked, so discarding should not be active
NS_ABORT_IF_FALSE(!DiscardingActive(), "Locked, but discarding activated");
// Decrement our lock count
mLockCount--;
// We now _might_ have one of the qualifications for discarding. Re-evaluate.
if (CanDiscard()) {
nsresult rv = ResetDiscardTimer();
nsresult rv = imgDiscardTracker::Reset(&mDiscardTrackerNode);
CONTAINER_ENSURE_SUCCESS(rv);
}

Просмотреть файл

@ -63,6 +63,7 @@
#include "nsTArray.h"
#include "imgFrame.h"
#include "nsThreadUtils.h"
#include "imgDiscardTracker.h"
#define NS_IMGCONTAINER_CID \
{ /* c76ff2c1-9bf6-418a-b143-3340c00112f7 */ \
@ -157,6 +158,9 @@ public:
PRUint32 GetDecodedDataSize();
PRUint32 GetSourceDataSize();
/* Triggers discarding. */
void Discard();
private:
struct Anim
{
@ -321,13 +325,14 @@ private: // data
// Discard members
PRUint32 mLockCount;
nsCOMPtr<nsITimer> mDiscardTimer;
imgDiscardTrackerNode mDiscardTrackerNode;
// Source data members
nsTArray<char> mSourceData;
nsCString mSourceDataMimeType;
friend class imgDecodeWorker;
friend class imgDiscardTracker;
// Decoder and friends
nsCOMPtr<imgIDecoder> mDecoder;
@ -353,10 +358,6 @@ private: // data
PRPackedBool mError:1; // Error handling
// Discard code
nsresult ResetDiscardTimer();
static void sDiscardTimerCallback(nsITimer *aTimer, void *aClosure);
// Decoding
nsresult WantDecodedFrames();
nsresult SyncDecode();
@ -377,6 +378,7 @@ private: // data
// Helpers
void DoError();
PRBool CanDiscard();
PRBool DiscardingActive();
PRBool StoringSourceData();
};

Просмотреть файл

@ -0,0 +1,215 @@
/* -*- 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):
* 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"
#include "imgContainer.h"
#include "imgDiscardTracker.h"
static PRBool sInitialized = PR_FALSE;
static PRBool sTimerOn = PR_FALSE;
/* TODO - don't hardcode. See bug 478398. */
static PRUint32 sMinDiscardTimeoutMs = 10000;
static nsITimer *sTimer = nsnull;
static struct imgDiscardTrackerNode sHead, sSentinel, sTail;
/*
* Puts an image in the back of the tracker queue. If the image is already
* in the tracker, this removes it first.
*/
nsresult
imgDiscardTracker::Reset(imgDiscardTrackerNode *node)
{
nsresult rv;
PRBool 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!");
// Initialize the first time through
if (NS_UNLIKELY(!sInitialized)) {
rv = Initialize();
NS_ENSURE_SUCCESS(rv, rv);
}
// Remove the node if it's in the list.
Remove(node);
// 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 = TimerOn();
NS_ENSURE_SUCCESS(rv,rv);
return NS_OK;
}
/*
* Removes a node from the tracker. No-op if the node is currently untracked.
*/
void
imgDiscardTracker::Remove(imgDiscardTrackerNode *node)
{
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;
}
/**
* Initialize the tracker.
*/
nsresult
imgDiscardTracker::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;
// 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 = PR_TRUE;
return NS_OK;
}
/**
* Shut down the tracker, deallocating the timer.
*/
void
imgDiscardTracker::Shutdown()
{
if (sTimer) {
sTimer->Cancel();
NS_RELEASE(sTimer);
sTimer = nsnull;
}
}
/**
* Enables the timer. No-op if the timer is already running.
*/
nsresult
imgDiscardTracker::TimerOn()
{
// Nothing to do if the timer's already on.
if (sTimerOn)
return NS_OK;
sTimerOn = PR_TRUE;
// Activate
return sTimer->InitWithFuncCallback(TimerCallback,
nsnull,
sMinDiscardTimeoutMs,
nsITimer::TYPE_REPEATING_SLACK);
}
/*
* Disables the timer. No-op if the timer isn't running.
*/
void
imgDiscardTracker::TimerOff()
{
// Nothing to do if the timer's already off.
if (!sTimerOn)
return;
sTimerOn = PR_FALSE;
// Deactivate
sTimer->Cancel();
}
/**
* Routine activated when the timer fires. This discards everything
* in front of sentinel, and resets the sentinel to the back of the
* list.
*/
void
imgDiscardTracker::TimerCallback(nsITimer *aTimer, void *aClosure)
{
imgDiscardTrackerNode *node;
// 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();
}
// Append the sentinel to the back of the list
Reset(&sSentinel);
// 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();
}

Просмотреть файл

@ -0,0 +1,83 @@
/* -*- 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):
* 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 __imgDiscardTracker_h__
#define __imgDiscardTracker_h__
class imgContainer;
class nsITimer;
// Struct to make an imgContainer insertable into the tracker list. This
// is embedded within each imgContainer object, and we do 'this->curr = this'
// on imgContainer construction. Thus, an imgContainer must always call
// imgDiscardTracker::Remove() in its destructor to avoid having the tracker
// point to bogus memory.
struct imgDiscardTrackerNode
{
// Pointer to the imgContainer that this node tracks
imgContainer *curr;
// Pointers to the previous and next nodes in the list
imgDiscardTrackerNode *prev, *next;
};
/**
* This static class maintains a linked list of imgContainer 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 imgDiscardTracker
{
public:
static nsresult Reset(struct imgDiscardTrackerNode *node);
static void Remove(struct imgDiscardTrackerNode *node);
static void Shutdown();
private:
static nsresult Initialize();
static nsresult TimerOn();
static void TimerOff();
static void TimerCallback(nsITimer *aTimer, void *aClosure);
};
#endif /* __imgDiscardTracker_h__ */