forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			550 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			550 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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) 1998
 | |
|  * the Initial Developer. All Rights Reserved.
 | |
|  *
 | |
|  * Contributor(s):
 | |
|  *   Conrad Carlen <ccarlen@netscape.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 "nsEmbedGlobalHistory.h"
 | |
| #include "nsIObserver.h"
 | |
| #include "nsIObserverService.h"
 | |
| #include "nsWeakReference.h"
 | |
| #include "nsAppDirectoryServiceDefs.h"
 | |
| #include "nsHashtable.h"
 | |
| #include "nsInt64.h"
 | |
| #include "prtypes.h"
 | |
| #include "nsFixedSizeAllocator.h"
 | |
| #include "nsVoidArray.h"
 | |
| #include "nsIPrefService.h"
 | |
| 
 | |
| // Constants
 | |
| static const PRInt32 kNewEntriesBetweenFlush = 10;
 | |
| 
 | |
| static const PRUint32 kDefaultExpirationIntervalDays = 7;
 | |
| 
 | |
| static const PRInt64 kMSecsPerDay = LL_INIT(0, 60 * 60 * 24 * 1000);
 | |
| static const PRInt64 kOneThousand = LL_INIT(0, 1000);
 | |
| 
 | |
| #define PREF_BROWSER_HISTORY_EXPIRE_DAYS "browser.history_expire_days"
 | |
| 
 | |
| // Static Routine Prototypes
 | |
| static nsresult readEntry(FILE *inStream, nsCString& url, HistoryEntry **entry);
 | |
| static nsresult writeEntry(FILE *outStm, nsCStringKey *url, HistoryEntry *entry);
 | |
| 
 | |
| static PRIntn PR_CALLBACK enumWriteEntry(nsHashKey *aKey, void *aData, void* closure);
 | |
| static PRIntn PR_CALLBACK enumWriteEntryIfUnwritten(nsHashKey *aKey, void *aData, void* closure);
 | |
| static PRIntn PR_CALLBACK enumDeleteEntry(nsHashKey *aKey, void *aData, void* closure);
 | |
| 
 | |
| //*****************************************************************************
 | |
| // HistoryEntry
 | |
| //*****************************************************************************   
 | |
| 
 | |
| class HistoryEntry {
 | |
| public:
 | |
|                 HistoryEntry() :
 | |
|                   mWritten(PR_FALSE) {}
 | |
|   
 | |
| 
 | |
|   void          OnVisited()
 | |
|                 {
 | |
|                   mLastVisitTime = PR_Now(); 
 | |
|                   LL_DIV(mLastVisitTime, mLastVisitTime, kOneThousand);
 | |
|                 }
 | |
|                 
 | |
|   PRInt64       GetLastVisitTime()
 | |
|                 { return mLastVisitTime; }
 | |
|   void          SetLastVisitTime(const PRInt64& aTime)
 | |
|                 { mLastVisitTime = aTime; }
 | |
| 
 | |
|   PRBool        GetIsWritten()
 | |
|                 { return mWritten; }
 | |
|   void          SetIsWritten(PRBool written = PR_TRUE)
 | |
|                 { mWritten = PR_TRUE; }
 | |
|                   
 | |
|   // Memory management stuff    
 | |
|   static void*  operator new(size_t size) CPP_THROW_NEW;
 | |
|   static void   operator delete(void *p, size_t size);
 | |
|   
 | |
|   // Must be called when done with all HistoryEntry objects
 | |
|   static void ReleasePool();
 | |
|  
 | |
|  private:
 | |
|   PRInt64       mLastVisitTime; // Millisecs
 | |
|   PRPackedBool  mWritten; // TRUE if ever persisted
 | |
| 
 | |
|   static nsresult InitPool();
 | |
|   static nsFixedSizeAllocator *sPool;
 | |
| };
 | |
| 
 | |
| nsFixedSizeAllocator *HistoryEntry::sPool;
 | |
| 
 | |
| //*****************************************************************************   
 | |
| 
 | |
| void* HistoryEntry::operator new(size_t size) CPP_THROW_NEW
 | |
| {
 | |
|   if (size != sizeof(HistoryEntry))
 | |
|     return ::operator new(size);
 | |
|   if (!sPool && NS_FAILED(InitPool()))
 | |
|     return nsnull;
 | |
|     
 | |
|   return sPool->Alloc(size);
 | |
| }
 | |
| 
 | |
| void HistoryEntry::operator delete(void *p, size_t size)
 | |
| {
 | |
|   if (!p)
 | |
|     return;
 | |
|   if (size != sizeof(HistoryEntry))
 | |
|     ::operator delete(p);
 | |
|   if (!sPool) {
 | |
|     NS_ERROR("HistoryEntry outlived its memory pool");
 | |
|     return;
 | |
|   }
 | |
|   sPool->Free(p, size);
 | |
| }
 | |
| 
 | |
| nsresult HistoryEntry::InitPool()
 | |
| {
 | |
|   if (!sPool) {
 | |
|     sPool = new nsFixedSizeAllocator;
 | |
|     if (!sPool)
 | |
|       return NS_ERROR_OUT_OF_MEMORY;
 | |
| 
 | |
|     static const size_t kBucketSizes[] =
 | |
|       { sizeof(HistoryEntry) };
 | |
|     static const PRInt32 kInitialPoolSize =
 | |
|       NS_SIZE_IN_HEAP(sizeof(HistoryEntry)) * 256;
 | |
| 
 | |
|     nsresult rv = sPool->Init("EmbedLite HistoryEntry Pool", kBucketSizes, 1, kInitialPoolSize);
 | |
|     if (NS_FAILED(rv))
 | |
|       return rv;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void HistoryEntry::ReleasePool()
 | |
| {
 | |
|   delete sPool;
 | |
|   sPool = nsnull;
 | |
| }
 | |
| 
 | |
| //*****************************************************************************
 | |
| // nsEmbedGlobalHistory - Creation/Destruction
 | |
| //*****************************************************************************   
 | |
| 
 | |
| NS_IMPL_ISUPPORTS3(nsEmbedGlobalHistory, nsIGlobalHistory, nsIObserver, nsISupportsWeakReference)
 | |
| 
 | |
| nsEmbedGlobalHistory::nsEmbedGlobalHistory() :
 | |
|   mDataIsLoaded(PR_FALSE), mEntriesAddedSinceFlush(0),
 | |
|   mURLTable(nsnull)
 | |
| {  
 | |
|   LL_I2L(mExpirationInterval, kDefaultExpirationIntervalDays);
 | |
|   LL_MUL(mExpirationInterval, mExpirationInterval, kMSecsPerDay);
 | |
| }
 | |
| 
 | |
| nsEmbedGlobalHistory::~nsEmbedGlobalHistory()
 | |
| {
 | |
|   FlushData();
 | |
|   delete mURLTable;
 | |
|   HistoryEntry::ReleasePool();
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP nsEmbedGlobalHistory::Init()
 | |
| {
 | |
|   mURLTable = new nsHashtable;
 | |
|   NS_ENSURE_TRUE(mURLTable, NS_ERROR_OUT_OF_MEMORY);
 | |
|   
 | |
|   // Get Pref and convert to millisecs
 | |
|   nsCOMPtr<nsIPrefBranch> prefs(do_GetService("@mozilla.org/preferences-service;1"));
 | |
|   if (prefs) {
 | |
|     PRInt32 expireDays;
 | |
|     prefs->GetIntPref(PREF_BROWSER_HISTORY_EXPIRE_DAYS, &expireDays);
 | |
|     LL_I2L(mExpirationInterval, expireDays);
 | |
|     LL_MUL(mExpirationInterval, mExpirationInterval, kMSecsPerDay);
 | |
|   }
 | |
|   
 | |
|   // register to observe profile changes
 | |
|   nsCOMPtr<nsIObserverService> observerService = 
 | |
|        do_GetService("@mozilla.org/observer-service;1");
 | |
|   NS_ASSERTION(observerService, "failed to get observer service");
 | |
|   if (observerService)
 | |
|     observerService->AddObserver(this, "profile-before-change", PR_TRUE);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| //*****************************************************************************
 | |
| // nsEmbedGlobalHistory::nsIGlobalHistory
 | |
| //*****************************************************************************   
 | |
| 
 | |
| NS_IMETHODIMP nsEmbedGlobalHistory::AddPage(const char *aURL)
 | |
| {
 | |
|   NS_ENSURE_ARG(aURL);
 | |
| 
 | |
|   nsresult rv = LoadData();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   
 | |
|   nsCStringKey asKey(aURL);
 | |
|   HistoryEntry *entry = static_cast<HistoryEntry *>(mURLTable->Get(&asKey));
 | |
|   if (!entry) {
 | |
|     
 | |
|     if (++mEntriesAddedSinceFlush >= kNewEntriesBetweenFlush)
 | |
|       FlushData(kFlushModeAppend);
 | |
| 
 | |
|     HistoryEntry *newEntry = new HistoryEntry;
 | |
|     if (!newEntry)
 | |
|       return NS_ERROR_FAILURE;
 | |
|     (void)mURLTable->Put(&asKey, newEntry);
 | |
|     entry = newEntry;
 | |
|   }
 | |
|   entry->OnVisited(); 
 | |
|   
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP nsEmbedGlobalHistory::IsVisited(const char *aURL, PRBool *_retval)
 | |
| {
 | |
|   NS_ENSURE_ARG(aURL);
 | |
|   NS_ENSURE_ARG_POINTER(_retval);
 | |
|   
 | |
|   nsresult rv = LoadData();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   nsCStringKey asKey(aURL);
 | |
|   
 | |
|   *_retval = (mURLTable->Exists(&asKey));
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| //*****************************************************************************
 | |
| // nsEmbedGlobalHistory::nsIObserver
 | |
| //*****************************************************************************   
 | |
| 
 | |
| NS_IMETHODIMP nsEmbedGlobalHistory::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData)
 | |
| {
 | |
|   nsresult rv = NS_OK;
 | |
|     
 | |
|   if (strcmp(aTopic, "profile-before-change") == 0) {
 | |
|     (void)FlushData();
 | |
|     (void)ResetData();
 | |
|   }
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| //*****************************************************************************
 | |
| // nsEmbedGlobalHistory
 | |
| //*****************************************************************************   
 | |
| 
 | |
| nsresult nsEmbedGlobalHistory::LoadData()
 | |
| {
 | |
|   if (!mDataIsLoaded) {
 | |
|     
 | |
|     nsresult rv;            
 | |
|     PRBool exists;
 | |
| 
 | |
|     mDataIsLoaded = PR_TRUE;
 | |
| 
 | |
|     rv = GetHistoryFile();
 | |
|     if (NS_FAILED(rv))
 | |
|       return rv;
 | |
|     rv = mHistoryFile->Exists(&exists);
 | |
|     if (NS_FAILED(rv))
 | |
|       return rv;
 | |
|     if (!exists)
 | |
|       return NS_OK;
 | |
|     
 | |
|     FILE *stdFile;
 | |
|     rv = mHistoryFile->OpenANSIFileDesc("r", &stdFile);
 | |
|     if (NS_FAILED(rv))
 | |
|       return rv;
 | |
|       
 | |
|     nsCAutoString outString;
 | |
|     HistoryEntry *newEntry;      
 | |
|     while (NS_SUCCEEDED(readEntry(stdFile, outString, &newEntry))) {
 | |
|       if (EntryHasExpired(newEntry)) {
 | |
|         delete newEntry;
 | |
|       }
 | |
|       else {
 | |
|         nsCStringKey asKey(outString);
 | |
|         mURLTable->Put(&asKey, newEntry);
 | |
|       }
 | |
|     }
 | |
|     
 | |
|     fclose(stdFile);
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsEmbedGlobalHistory::FlushData(PRIntn mode)
 | |
| {  
 | |
|   if (mHistoryFile) {
 | |
| 
 | |
|     const char* openMode = (mode == kFlushModeAppend ? "a" : "w");
 | |
|     FILE *stdFile;
 | |
|     nsresult rv = mHistoryFile->OpenANSIFileDesc(openMode, &stdFile);
 | |
|     if (NS_FAILED(rv)) return rv;
 | |
| 
 | |
|     // Before flushing either way, remove dead entries
 | |
|     mURLTable->Enumerate(enumRemoveEntryIfExpired, this);  
 | |
|     
 | |
|     if (mode == kFlushModeAppend)
 | |
|         mURLTable->Enumerate(enumWriteEntryIfUnwritten, stdFile);
 | |
|     else
 | |
|         mURLTable->Enumerate(enumWriteEntry, stdFile);
 | |
|     
 | |
|     mEntriesAddedSinceFlush = 0;
 | |
|     fclose(stdFile);
 | |
|   }
 | |
|   return NS_OK; 
 | |
| }
 | |
| 
 | |
| nsresult nsEmbedGlobalHistory::ResetData()
 | |
| {
 | |
|   mURLTable->Reset(enumDeleteEntry);
 | |
|   mHistoryFile = 0;
 | |
|   mDataIsLoaded = PR_FALSE;
 | |
|   mEntriesAddedSinceFlush = 0;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsEmbedGlobalHistory::GetHistoryFile()
 | |
| {
 | |
|   nsresult rv;
 | |
| 
 | |
|   // Get the history file in our profile dir.
 | |
|   // Notice we are not just getting NS_APP_HISTORY_50_FILE
 | |
|   // because it is used by the "real" global history component.
 | |
|   
 | |
|   nsCOMPtr<nsIFile> aFile; 
 | |
|   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(aFile));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   rv = aFile->Append(NS_LITERAL_STRING("history.txt"));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   mHistoryFile = do_QueryInterface(aFile);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| PRBool nsEmbedGlobalHistory::EntryHasExpired(HistoryEntry *entry)
 | |
| {
 | |
|   // convert "now" from microsecs to millisecs
 | |
|   PRInt64 nowInMilliSecs = PR_Now(); 
 | |
|   LL_DIV(nowInMilliSecs, nowInMilliSecs, kOneThousand);
 | |
| 
 | |
|   // determine when the entry would have expired
 | |
|   PRInt64 expirationIntervalAgo;
 | |
|   LL_SUB(expirationIntervalAgo, nowInMilliSecs, mExpirationInterval);
 | |
| 
 | |
|   PRInt64 lastVisitTime = entry->GetLastVisitTime();
 | |
|   return (LL_CMP(lastVisitTime, <, expirationIntervalAgo));
 | |
| }
 | |
| 
 | |
| PRIntn PR_CALLBACK nsEmbedGlobalHistory::enumRemoveEntryIfExpired(nsHashKey *aKey, void *aData, void* closure)
 | |
| {
 | |
|   HistoryEntry *entry = static_cast<HistoryEntry*>(aData);
 | |
|   if (!entry)
 | |
|     return PR_FALSE;
 | |
|   nsEmbedGlobalHistory *history = static_cast<nsEmbedGlobalHistory*>(closure);
 | |
|   if (!history)
 | |
|     return kHashEnumerateStop;
 | |
|       
 | |
|   if (history->EntryHasExpired(entry)) {
 | |
|     delete entry;
 | |
|     return kHashEnumerateRemove;
 | |
|   }
 | |
|   return kHashEnumerateNext;
 | |
| }
 | |
| 
 | |
| 
 | |
| //*****************************************************************************
 | |
| // Static Functions
 | |
| //*****************************************************************************   
 | |
| 
 | |
| static nsresult parsePRInt64(FILE *inStm, PRInt64& outValue)
 | |
| {
 | |
|   int c, charsRead = 0;
 | |
|   nsInt64 value = 0;
 | |
| 
 | |
|   while (PR_TRUE) {
 | |
|     c = fgetc(inStm);
 | |
|     if (c == EOF || !isdigit(c))
 | |
|       break;
 | |
|       
 | |
|     ++charsRead;
 | |
|     PRInt32 digit = c - '0';
 | |
|     value *= nsInt64(10);
 | |
|     value += nsInt64(digit);
 | |
|   }
 | |
|   if (!charsRead)
 | |
|     return NS_ERROR_FAILURE;
 | |
|   outValue = value;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult readEntry(FILE *inStream, nsCString& outURL, HistoryEntry **outEntry)
 | |
| {
 | |
|   nsresult rv;
 | |
| 
 | |
|   // Get the last visted date
 | |
|   PRInt64 value;
 | |
|   rv = parsePRInt64(inStream, value);
 | |
|   if (NS_FAILED(rv))
 | |
|     return rv;
 | |
|   
 | |
|   // Get the URL
 | |
|   int c;
 | |
|   char buf[1024];
 | |
|   char *next, *end = buf + sizeof(buf);
 | |
|     
 | |
|   outURL.Truncate(0);
 | |
|   next = buf;
 | |
|   
 | |
|   while (PR_TRUE) {
 | |
|     c = fgetc(inStream);
 | |
|     
 | |
|     if (c == EOF)
 | |
|       break;
 | |
|     else if (c == '\n')
 | |
|       break;
 | |
|     else if (c == '\r') {
 | |
|       c = fgetc(inStream);
 | |
|       if (c != '\n')
 | |
|         ungetc(c, inStream);
 | |
|       break;
 | |
|     }
 | |
|     else {
 | |
|       *next++ = c;
 | |
|       if (next >= end) {
 | |
|         outURL.Append(buf, next - buf);
 | |
|         next = buf;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (next > buf)
 | |
|     outURL.Append(buf, next - buf);
 | |
|     
 | |
|   if (!outURL.Length() && c == EOF)
 | |
|     return NS_ERROR_FAILURE;
 | |
|     
 | |
|   *outEntry = new HistoryEntry;
 | |
|   if (!*outEntry)
 | |
|     return NS_ERROR_OUT_OF_MEMORY;
 | |
|   (*outEntry)->SetLastVisitTime(value);
 | |
|   (*outEntry)->SetIsWritten();
 | |
|     
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| static nsresult writePRInt64(FILE *outStm, const PRInt64& inValue)
 | |
| {
 | |
|   nsInt64 value(inValue);
 | |
|   
 | |
|   if (value == nsInt64(0)) {
 | |
|     fputc('0', outStm);
 | |
|     return NS_OK;
 | |
|   }
 | |
|   
 | |
|   nsCAutoString tempString;
 | |
| 
 | |
|   while (value != nsInt64(0)) {
 | |
|     PRInt32 ones = PRInt32(value % nsInt64(10));
 | |
|     value /= nsInt64(10);
 | |
|     tempString.Insert(char('0' + ones), 0);
 | |
|   }
 | |
|   int result = fputs(tempString.get(), outStm);
 | |
|   return (result == EOF) ? NS_ERROR_FAILURE : NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult writeEntry(FILE *outStm, nsCStringKey *url, HistoryEntry *entry)
 | |
| {
 | |
|   writePRInt64(outStm, entry->GetLastVisitTime());
 | |
|   fputc(':', outStm);
 | |
| 
 | |
|   fputs(url->GetString(), outStm);
 | |
|   entry->SetIsWritten();
 | |
| 
 | |
| #if defined (XP_WIN) || defined(XP_OS2)
 | |
|   fputc('\r', outStm);
 | |
|   fputc('\n', outStm);
 | |
| #elif defined(XP_UNIX)
 | |
|   fputc('\n', outStm);
 | |
| #else
 | |
|   fputc('\r', outStm);
 | |
| #endif
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| PRIntn PR_CALLBACK enumWriteEntry(nsHashKey *aKey, void *aData, void* closure)
 | |
| {
 | |
|   FILE *outStm = static_cast<FILE*>(closure);
 | |
|   if (!outStm)
 | |
|     return kHashEnumerateStop;
 | |
|   nsCStringKey *stringKey = static_cast<nsCStringKey*>(aKey);
 | |
|   if (!stringKey)
 | |
|     return kHashEnumerateStop;
 | |
|   HistoryEntry *entry = static_cast<HistoryEntry*>(aData);
 | |
|   if (!entry)
 | |
|     return kHashEnumerateStop;
 | |
| 
 | |
|   nsresult rv = writeEntry(outStm, stringKey, entry);
 | |
|     
 | |
|   return NS_SUCCEEDED(rv) ? kHashEnumerateNext : kHashEnumerateStop;
 | |
| }
 | |
| 
 | |
| PRIntn PR_CALLBACK enumWriteEntryIfUnwritten(nsHashKey *aKey, void *aData, void* closure)
 | |
| {
 | |
|   FILE *outStm = static_cast<FILE*>(closure);
 | |
|   if (!outStm)
 | |
|     return kHashEnumerateStop;
 | |
|   nsCStringKey *stringKey = static_cast<nsCStringKey*>(aKey);
 | |
|   if (!stringKey)
 | |
|     return kHashEnumerateStop;
 | |
|   HistoryEntry *entry = static_cast<HistoryEntry*>(aData);
 | |
|   if (!entry)
 | |
|     return kHashEnumerateStop;
 | |
| 
 | |
|   nsresult rv = NS_OK;
 | |
|   if (!entry->GetIsWritten())
 | |
|     rv = writeEntry(outStm, stringKey, entry);
 | |
|     
 | |
|   return NS_SUCCEEDED(rv) ? kHashEnumerateNext : kHashEnumerateStop;
 | |
| }
 | |
| 
 | |
| PRIntn PR_CALLBACK enumDeleteEntry(nsHashKey *aKey, void *aData, void* closure)
 | |
| {
 | |
|   HistoryEntry *entry = static_cast<HistoryEntry*>(aData);
 | |
|   delete entry;
 | |
|   
 | |
|   return kHashEnumerateNext;
 | |
| }
 | 
