Bug 527667 - DOM Storage (localStorage, sessionStorage) data is not cleared when 'Clear Recent History' is used with Time range not 'Everything', r+sr=jst, a=blocking2.0:betaN
☠☠ backed out by c6ba385c4435 ☠ ☠
authorHonza Bambas <honzab.moz@firemni.cz>
Mon, 25 Oct 2010 20:40:55 +0200
changeset 56447 2ccadd55dc4c338acd480c9731463c0889dcbfe6
parent 56446 142c7b20fd8c0141acbb8860b8450fe241d5117b
child 56448 c6ba385c44350ab6b117a2779ebb5f2adb76d68d
push idunknown
push userunknown
push dateunknown
reviewersblocking2
bugs527667
milestone2.0b8pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 527667 - DOM Storage (localStorage, sessionStorage) data is not cleared when 'Clear Recent History' is used with Time range not 'Everything', r+sr=jst, a=blocking2.0:betaN
browser/base/content/sanitize.js
dom/interfaces/storage/nsIDOMStorageManager.idl
dom/src/storage/nsDOMStorage.cpp
dom/src/storage/nsDOMStorage.h
dom/src/storage/nsDOMStorageDBWrapper.cpp
dom/src/storage/nsDOMStorageDBWrapper.h
dom/src/storage/nsDOMStorageMemoryDB.cpp
dom/src/storage/nsDOMStorageMemoryDB.h
dom/src/storage/nsDOMStoragePersistentDB.cpp
dom/src/storage/nsDOMStoragePersistentDB.h
dom/tests/mochitest/Makefile.in
dom/tests/mochitest/globalstorage/Makefile.in
dom/tests/mochitest/globalstorage/test_globalStorageDeleteSinceAPI.html
dom/tests/mochitest/localstorage/Makefile.in
dom/tests/mochitest/localstorage/test_localStorageDeleteSinceAPI.html
dom/tests/mochitest/localstorage/test_localStorageDeleteSinceAPIPrivateBrowsing.html
dom/tests/mochitest/sessionstorage/Makefile.in
dom/tests/mochitest/sessionstorage/test_sessionStorageDeleteSinceAPI.html
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -147,16 +147,21 @@ Sanitizer.prototype = {
           var cookiesEnum = cookieMgr.enumerator;
           while (cookiesEnum.hasMoreElements()) {
             var cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
             
             if (cookie.creationTime > this.range[0])
               // This cookie was created after our cutoff, clear it
               cookieMgr.remove(cookie.host, cookie.name, cookie.path, false);
           }
+
+          // Also handle all DOM storage data created after the cutoff.
+          var domStorageManager = Components.classes["@mozilla.org/dom/storagemanager;1"]
+                                            .getService(Ci.nsIDOMStorageManager);
+          domStorageManager.clearStorageDataSince(this.range[0]);
         }
         else {
           // Remove everything
           cookieMgr.removeAll();
         }
 
         // clear any network geolocation provider sessions
         var psvc = Components.classes["@mozilla.org/preferences-service;1"]
--- a/dom/interfaces/storage/nsIDOMStorageManager.idl
+++ b/dom/interfaces/storage/nsIDOMStorageManager.idl
@@ -35,17 +35,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 interface nsIDOMStorage;
 interface nsIPrincipal;
 
-[scriptable, uuid(fd91ec36-7da8-43bb-b8f2-4b57a862a467)]
+[scriptable, uuid(9b729267-00ed-4e5c-a3d2-b5572ca0934d)]
 interface nsIDOMStorageManager : nsISupports
 {
   /**
    * Return the amount of disk space used by a domain.  Usage is checked
    * against the domain of the page that set the key (the owner domain), not
    * the domain of the storage object.
    *
    * @param aOwnerDomain The domain to check.
@@ -55,15 +55,24 @@ interface nsIDOMStorageManager : nsISupp
 
   /**
    * Clear keys owned by offline applications.  All data owned by a domain
    * with the "offline-app" permission will be removed from the database.
    */
   void clearOfflineApps();
 
   /**
+   * Clears any data stored in DOM storage since the provided time
+   * @param since
+   *    Any storage value that has been inserted after the time specified
+   *    with 'since' will be deleted.  The time is in microseconds, defined
+   *    as PR_Now() function result.
+   */
+  void clearStorageDataSince(in PRInt64 since);
+
+  /**
    * Returns instance of localStorage object for aURI's origin.
    * This method ensures there is always only a single instance
    * for a single origin.
    */
   nsIDOMStorage getLocalStorageForPrincipal(in nsIPrincipal aPrincipal,
                                             in DOMString aDocumentURI);
 };
--- a/dom/src/storage/nsDOMStorage.cpp
+++ b/dom/src/storage/nsDOMStorage.cpp
@@ -60,16 +60,17 @@
 #include "nsIPermissionManager.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIOfflineCacheUpdate.h"
 #include "nsIJSContextStack.h"
 #include "nsIPrivateBrowsingService.h"
 #include "nsDOMString.h"
 #include "nsNetCID.h"
 #include "nsIProxyObjectManager.h"
+#include "nsISupportsPrimitives.h"
 
 static const PRUint32 ASK_BEFORE_ACCEPT = 1;
 static const PRUint32 ACCEPT_SESSION = 2;
 static const PRUint32 BEHAVIOR_REJECT = 2;
 
 static const PRUint32 DEFAULT_QUOTA = 5 * 1024;
 // Be generous with offline apps by default...
 static const PRUint32 DEFAULT_OFFLINE_APP_QUOTA = 200 * 1024;
@@ -434,16 +435,43 @@ nsDOMStorageManager::GetUsage(const nsAS
   nsresult rv = nsDOMStorage::InitDB();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return nsDOMStorage::gStorageDB->GetUsage(NS_ConvertUTF16toUTF8(aDomain),
                                             PR_FALSE, aUsage);
 }
 
 NS_IMETHODIMP
+nsDOMStorageManager::ClearStorageDataSince(PRInt64 aSince)
+{
+  nsresult rv;
+
+  rv = nsDOMStorage::InitDB();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = nsDOMStorage::gStorageDB->RemoveTimeRange(aSince);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIObserverService> obsserv = mozilla::services::GetObserverService();
+  if (obsserv) {
+    nsCOMPtr<nsISupportsPRTime> time = do_CreateInstance(
+          "@mozilla.org/supports-PRTime;1", &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = time->SetData(aSince);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = obsserv->NotifyObservers(time, NS_DOMSTORAGE_CUTOFF_OBSERVER, nsnull);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMStorageManager::ClearOfflineApps()
 {
     nsresult rv = nsDOMStorage::InitDB();
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsTArray<nsString> domains;
     rv = GetOfflineDomains(domains);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -655,16 +683,18 @@ nsDOMStorage::InitAsSessionStorage(nsIPr
   mDocumentURI = aDocumentURI;
 
 #ifdef MOZ_STORAGE
   mUseDB = PR_FALSE;
   mScopeDBKey.Truncate();
   mQuotaDomainDBKey.Truncate();
 #endif
 
+  RegisterObservers(false);
+
   mStorageType = SessionStorage;
   return NS_OK;
 }
 
 nsresult
 nsDOMStorage::InitAsLocalStorage(nsIPrincipal *aPrincipal, const nsSubstring &aDocumentURI)
 {
   nsCOMPtr<nsIURI> domainURI;
@@ -698,17 +728,17 @@ nsDOMStorage::InitAsLocalStorage(nsIPrin
 
   mStorageType = LocalStorage;
 
   nsCOMPtr<nsIURI> URI;
   if (NS_SUCCEEDED(aPrincipal->GetURI(getter_AddRefs(URI))) && URI) {
     mCanUseChromePersist = URICanUseChromePersist(URI);
   }
 
-  RegisterObservers();
+  RegisterObservers(true);
 
   return NS_OK;
 }
 
 nsresult
 nsDOMStorage::InitAsGlobalStorage(const nsACString &aDomainDemanded)
 {
   mDomain = aDomainDemanded;
@@ -726,17 +756,17 @@ nsDOMStorage::InitAsGlobalStorage(const 
       PR_TRUE, PR_FALSE, mQuotaDomainDBKey);
   nsDOMStorageDBWrapper::CreateQuotaDomainDBKey(aDomainDemanded,
       PR_TRUE, PR_TRUE, mQuotaETLDplus1DomainDBKey);
 #endif
 
   mStorageType = GlobalStorage;
   mEventBroadcaster = this;
 
-  RegisterObservers();
+  RegisterObservers(true);
 
   return NS_OK;
 }
 
 static PLDHashOperator
 CopyStorageItems(nsSessionStorageEntry* aEntry, void* userArg)
 {
   nsDOMStorage* newstorage = static_cast<nsDOMStorage*>(userArg);
@@ -1086,16 +1116,22 @@ nsDOMStorage::SetItem(const nsAString& a
       new nsDOMStorageItem(this, aKey, aData, isCallerSecure);
     if (!newitem)
       return NS_ERROR_OUT_OF_MEMORY;
     entry = mItems.PutEntry(aKey);
     NS_ENSURE_TRUE(entry, NS_ERROR_OUT_OF_MEMORY);
     entry->mItem = newitem;
   }
 
+  if (!UseDB()) {
+    // This is used only for sessionStorage, no need to setup the time also when
+    // loading items from the database etc.
+    entry->mItem->SetInsertTimeToNow();
+  }
+
   if ((oldValue != aData || mStorageType == GlobalStorage) && mEventBroadcaster)
     mEventBroadcaster->BroadcastChangeNotification(aKey, oldValue, aData);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP nsDOMStorage::RemoveItem(const nsAString& aKey)
 {
@@ -1527,23 +1563,26 @@ nsDOMStorage::MaybeCommitTemporaryTable(
 
   return gStorageDB->FlushAndDeleteTemporaryTableForStorage(this);
 #endif
 
   return NS_OK;
 }
 
 nsresult
-nsDOMStorage::RegisterObservers()
+nsDOMStorage::RegisterObservers(bool persistent)
 {
   nsCOMPtr<nsIObserverService> obsserv = mozilla::services::GetObserverService();
   if (obsserv) {
-    obsserv->AddObserver(this, "profile-before-change", PR_TRUE);
-    obsserv->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_TRUE);
-    obsserv->AddObserver(this, NS_DOMSTORAGE_FLUSH_TIMER_OBSERVER, PR_TRUE);
+    if (persistent) {
+      obsserv->AddObserver(this, "profile-before-change", PR_TRUE);
+      obsserv->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_TRUE);
+      obsserv->AddObserver(this, NS_DOMSTORAGE_FLUSH_TIMER_OBSERVER, PR_TRUE);
+    }
+    obsserv->AddObserver(this, NS_DOMSTORAGE_CUTOFF_OBSERVER, PR_TRUE);
   }
   return NS_OK;
 }
 
 bool
 nsDOMStorage::WasTemporaryTableLoaded()
 {
   return mLoadedTemporaryTable;
@@ -1556,16 +1595,27 @@ nsDOMStorage::SetTemporaryTableLoaded(bo
     mLastTemporaryTableAccessTime = TimeStamp::Now();
     if (!mLoadedTemporaryTable)
       mTemporaryTableAge = mLastTemporaryTableAccessTime;
   }
 
   mLoadedTemporaryTable = loaded;
 }
 
+static PLDHashOperator
+StorageCutOffEnum(nsSessionStorageEntry* aEntry, void* userArg)
+{
+  PRInt64 since = *(PRInt64*)userArg;
+
+  if (aEntry->mItem->ShouldBeCutOff(since))
+    return PL_DHASH_REMOVE;
+
+  return PL_DHASH_NEXT;
+}
+
 NS_IMETHODIMP
 nsDOMStorage::Observe(nsISupports *subject,
                       const char *topic,
                       const PRUnichar *data)
 {
   bool isProfileBeforeChange = !strcmp(topic, "profile-before-change");
   bool isXPCOMShutdown = !strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
   bool isFlushTimer = !strcmp(topic, NS_DOMSTORAGE_FLUSH_TIMER_OBSERVER);
@@ -1574,16 +1624,40 @@ nsDOMStorage::Observe(nsISupports *subje
     nsresult rv = MaybeCommitTemporaryTable(isXPCOMShutdown || isProfileBeforeChange);
     if (NS_FAILED(rv)) {
       NS_WARNING("DOMStorage: temporary table commit failed");
     }
 
     return NS_OK;
   }
 
+  if (!strcmp(topic, NS_DOMSTORAGE_CUTOFF_OBSERVER)) {
+    if (UseDB()) {
+      // If this storage is using the database (localStorage, globalStorage)
+      // then just re-cache the items from the database, database is now up to
+      // date.
+      mItemsCached = PR_FALSE;
+      CacheKeysFromDB();
+    }
+    else {
+      // This is sessionStorage. In that case we need to prune the mItems hash
+      // table. Insert times are properly set in nsDOMStorage::SetItem.
+      nsresult rv;
+      nsCOMPtr<nsISupportsPRTime> time = do_QueryInterface(subject, &rv);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      PRInt64 since;
+      rv = time->GetData(&since);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      mItems.EnumerateEntries(StorageCutOffEnum, &since);
+    }
+    return NS_OK;
+  }
+
   NS_WARNING("Unrecognized topic in nsDOMStorage::Observe");
   return NS_OK;
 }
 
 //
 // nsDOMStorage2
 //
 
@@ -2026,16 +2100,17 @@ NS_INTERFACE_MAP_END
 
 nsDOMStorageItem::nsDOMStorageItem(nsDOMStorage* aStorage,
                                    const nsAString& aKey,
                                    const nsAString& aValue,
                                    PRBool aSecure)
   : mSecure(aSecure),
     mKey(aKey),
     mValue(aValue),
+    mInsertTime(0),
     mStorage(aStorage)
 {
 }
 
 nsDOMStorageItem::~nsDOMStorageItem()
 {
 }
 
--- a/dom/src/storage/nsDOMStorage.h
+++ b/dom/src/storage/nsDOMStorage.h
@@ -59,16 +59,17 @@
 #include "nsIDOMStorageManager.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIObserver.h"
 #include "nsITimer.h"
 #include "nsWeakReference.h"
 #include "mozilla/TimeStamp.h"
 
 #define NS_DOMSTORAGE_FLUSH_TIMER_OBSERVER "domstorage-flush-timer"
+#define NS_DOMSTORAGE_CUTOFF_OBSERVER "domstorage-cutoff"
 
 #ifdef MOZ_STORAGE
 #include "nsDOMStorageDBWrapper.h"
 #endif
 
 #define IS_PERMISSION_ALLOWED(perm) \
       ((perm) != nsIPermissionManager::UNKNOWN_ACTION && \
       (perm) != nsIPermissionManager::DENY_ACTION)
@@ -237,17 +238,17 @@ public:
 
   nsIDOMStorageItem* GetNamedItem(const nsAString& aKey, nsresult* aResult);
 
   static nsDOMStorage* FromSupports(nsISupports* aSupports)
   {
     return static_cast<nsDOMStorage*>(static_cast<nsIDOMStorageObsolete*>(aSupports));
   }
 
-  nsresult RegisterObservers();
+  nsresult RegisterObservers(bool persistent);
   nsresult MaybeCommitTemporaryTable(bool force);
 
   bool WasTemporaryTableLoaded();
   void SetTemporaryTableLoaded(bool loaded);
 
 protected:
 
   friend class nsDOMStorageManager;
@@ -459,27 +460,40 @@ public:
     mValue = aValue;
   }
 
   void ClearValue()
   {
     mValue.Truncate();
   }
 
+  void SetInsertTimeToNow()
+  {
+    mInsertTime = PR_Now();
+  }
+
+  bool ShouldBeCutOff(PRInt64 since)
+  {
+    return mInsertTime > since;
+  }
+
 protected:
 
   // true if this value is for secure sites only
   PRBool mSecure;
 
   // key for the item
   nsString mKey;
 
   // value of the item
   nsString mValue;
 
+  // insertion/update time
+  PRInt64 mInsertTime;
+
   // If this item came from the db, mStorage points to the storage
   // object where this item came from.
   nsRefPtr<nsDOMStorage> mStorage;
 };
 
 class nsDOMStorageEvent : public nsDOMEvent,
                           public nsIDOMStorageEvent
 {
--- a/dom/src/storage/nsDOMStorageDBWrapper.cpp
+++ b/dom/src/storage/nsDOMStorageDBWrapper.cpp
@@ -258,16 +258,35 @@ nsDOMStorageDBWrapper::RemoveOwner(const
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mPersistentDB.RemoveOwner(aOwner, aIncludeSubDomains);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return rv;
 }
 
+nsresult
+nsDOMStorageDBWrapper::RemoveTimeRange(PRInt64 aSince)
+{
+  nsresult rv;
+
+  rv = mPrivateBrowsingDB.RemoveTimeRange(aSince);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (nsDOMStorageManager::gStorageManager->InPrivateBrowsingMode())
+    return NS_OK;
+
+  rv = mSessionOnlyDB.RemoveTimeRange(aSince);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mPersistentDB.RemoveTimeRange(aSince);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return rv;
+}
 
 nsresult
 nsDOMStorageDBWrapper::RemoveOwners(const nsTArray<nsString> &aOwners,
                                     PRBool aIncludeSubDomains, PRBool aMatch)
 {
   nsresult rv;
 
   rv = mPrivateBrowsingDB.RemoveOwners(aOwners, aIncludeSubDomains, aMatch);
--- a/dom/src/storage/nsDOMStorageDBWrapper.h
+++ b/dom/src/storage/nsDOMStorageDBWrapper.h
@@ -173,16 +173,23 @@ public:
    * Removes keys owned by domains that either match or don't match the
    * list.
    */
   nsresult
   RemoveOwners(const nsTArray<nsString>& aOwners,
                PRBool aIncludeSubDomains, PRBool aMatch);
 
   /**
+   * Removes all keys that were created after the time specified by 'aSince'.
+   * 'aSince' value is compatible with PR_Now() function.
+   */
+  nsresult
+  RemoveTimeRange(PRInt64 aSince);
+
+  /**
    * Removes all keys from storage. Used when clearing storage.
    */
   nsresult
   RemoveAll();
 
   /**
     * Returns usage for a storage using its GetQuotaDomainDBKey() as a key.
     */
--- a/dom/src/storage/nsDOMStorageMemoryDB.cpp
+++ b/dom/src/storage/nsDOMStorageMemoryDB.cpp
@@ -225,16 +225,17 @@ nsDOMStorageMemoryDB::SetKey(nsDOMStorag
       return NS_ERROR_DOM_QUOTA_REACHED;
     }
   }
 
   storage->mUsageDelta += aValue.Length() - item->mValue.Length();
 
   item->mValue = aValue;
   item->mSecure = aSecure;
+  item->mInsertTime = PR_Now();
 
   *aNewUsage = usage;
 
   return NS_OK;
 }
 
 nsresult
 nsDOMStorageMemoryDB::SetSecure(nsDOMStorage* aStorage,
@@ -374,16 +375,44 @@ nsDOMStorageMemoryDB::RemoveOwners(const
     struc.mSubDomain = &quotaKey;
     struc.mMatch = aMatch;
     mData.Enumerate(RemoveOwnersEnum, &struc);
   }
 
   return NS_OK;
 }
 
+static PLDHashOperator
+RemoveTimeRangeEnum(const nsAString& keyname,
+               nsAutoPtr<nsDOMStorageMemoryDB::nsInMemoryItem>& item,
+               void *closure)
+{
+  if (item->mInsertTime > *(PRInt64*)closure)
+    return PL_DHASH_REMOVE;
+
+  return PL_DHASH_NEXT;
+}
+
+static PLDHashOperator
+RemoveTimeRangeStoragesEnum(const nsACString& key,
+                 nsAutoPtr<nsDOMStorageMemoryDB::nsInMemoryStorage>& storage,
+                 void *closure)
+{
+  storage->mTable.Enumerate(RemoveTimeRangeEnum, closure);
+  return PL_DHASH_NEXT;
+}
+
+nsresult
+nsDOMStorageMemoryDB::RemoveTimeRange(PRInt64 since)
+{
+  mData.Enumerate(RemoveTimeRangeStoragesEnum, &since);
+
+  return NS_OK;
+}
+
 nsresult
 nsDOMStorageMemoryDB::RemoveAll()
 {
   mData.Clear(); // XXX Check this releases all instances
   return NS_OK;
 }
 
 nsresult
--- a/dom/src/storage/nsDOMStorageMemoryDB.h
+++ b/dom/src/storage/nsDOMStorageMemoryDB.h
@@ -51,16 +51,17 @@ public:
   nsDOMStorageMemoryDB() : mPreloading(PR_FALSE) {}
   ~nsDOMStorageMemoryDB() {}
 
   class nsInMemoryItem
   {
   public:
     PRBool mSecure;
     nsString mValue;
+    PRInt64 mInsertTime;
   };
 
   typedef nsClassHashtable<nsStringHashKey, nsInMemoryItem> nsStorageItemsTable;
 
   class nsInMemoryStorage
   {
   public:
     nsStorageItemsTable mTable;
@@ -155,16 +156,24 @@ public:
    * Removes keys owned by domains that either match or don't match the
    * list.
    */
   nsresult
   RemoveOwners(const nsTArray<nsString>& aOwners,
                PRBool aIncludeSubDomains, PRBool aMatch);
 
   /**
+   * Remove all values from all scopes not marked as offline that has been
+   * created after the time specified with 'aSince'.  Used by the Clear Private
+   * Data dialog.  'aSince' value is compatible with PR_Now() function.
+   */
+  nsresult
+  RemoveTimeRange(PRInt64 aSince);
+
+  /**
    * Removes all keys from storage. Used when clearing storage.
    */
   nsresult
   RemoveAll();
 
   /**
     * Returns usage for a storage using its GetQuotaDomainDBKey() as a key.
     */
--- a/dom/src/storage/nsDOMStoragePersistentDB.cpp
+++ b/dom/src/storage/nsDOMStoragePersistentDB.cpp
@@ -200,47 +200,85 @@ nsDOMStoragePersistentDB::Init(const nsS
     rv = service->OpenDatabase(storageFile, getter_AddRefs(mConnection));
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
         "PRAGMA temp_store = MEMORY"));
   NS_ENSURE_SUCCESS(rv, rv);
 
+  PRInt32 schemaVersion;
+  rv = mConnection->GetSchemaVersion(&schemaVersion);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   mozStorageTransaction transaction(mConnection, PR_FALSE);
 
   // Ensure Gecko 1.9.1 storage table
-  rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-         "CREATE TABLE IF NOT EXISTS webappsstore2 ("
-         "scope TEXT, "
-         "key TEXT, "
-         "value TEXT, "
-         "secure INTEGER, "
-         "owner TEXT)"));
+  PRBool exists;
+
+  rv = mConnection->TableExists(NS_LITERAL_CSTRING("webappsstore2"),
+                                &exists);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  if (!exists) {
+    rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+           "CREATE TABLE webappsstore2 ("
+           "scope TEXT, "
+           "key TEXT, "
+           "value TEXT, "
+           "secure INTEGER, "
+           "owner TEXT, "
+           "inserttime BIGINT DEFAULT 0)"));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  else {
+    if (schemaVersion == 0) {
+      rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+           "ALTER TABLE webappsstore2 "
+           "ADD COLUMN inserttime BIGINT DEFAULT 0"));
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  }
+
+  if (schemaVersion == 0) {
+    rv = mConnection->SetSchemaVersion(1);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
         "CREATE UNIQUE INDEX IF NOT EXISTS scope_key_index"
         " ON webappsstore2(scope, key)"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        "CREATE INDEX IF NOT EXISTS inserttime_index"
+        " ON webappsstore2(inserttime)"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+
+  rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
          "CREATE TEMPORARY TABLE webappsstore2_temp ("
          "scope TEXT, "
          "key TEXT, "
          "value TEXT, "
          "secure INTEGER, "
-         "owner TEXT)"));
+         "owner TEXT, "
+         "inserttime BIGINT DEFAULT 0)"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
         "CREATE UNIQUE INDEX scope_key_index_temp"
         " ON webappsstore2_temp(scope, key)"));
   NS_ENSURE_SUCCESS(rv, rv);
 
+  rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        "CREATE INDEX IF NOT EXISTS inserttime_index_temp"
+        " ON webappsstore2_temp(inserttime)"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
 
   rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
         "CREATE TEMPORARY VIEW webappsstore2_view AS "
         "SELECT * FROM webappsstore2_temp "
         "UNION ALL "
         "SELECT * FROM webappsstore2 "
           "WHERE NOT EXISTS ("
             "SELECT scope, key FROM webappsstore2_temp "
@@ -266,18 +304,16 @@ nsDOMStoragePersistentDB::Init(const nsS
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<mozIStorageFunction> function2(new nsIsOfflineSQLFunction());
   NS_ENSURE_TRUE(function2, NS_ERROR_OUT_OF_MEMORY);
 
   rv = mConnection->CreateFunction(NS_LITERAL_CSTRING("ISOFFLINE"), 1, function2);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  PRBool exists;
-
   // Check if there is storage of Gecko 1.9.0 and if so, upgrade that storage
   // to actual webappsstore2 table and drop the obsolete table. First process
   // this newer table upgrade to priority potential duplicates from older
   // storage table.
   rv = mConnection->TableExists(NS_LITERAL_CSTRING("webappsstore"),
                                 &exists);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -350,18 +386,18 @@ nsDOMStoragePersistentDB::Init(const nsS
          "WHERE scope = :scope "
          "AND key = :key"),
          getter_AddRefs(mGetKeyValueStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // insert a new key
   rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
          "INSERT OR REPLACE INTO "
-         "webappsstore2_temp(scope, key, value, secure) "
-         "VALUES (:scope, :key, :value, :secure)"),
+         "webappsstore2_temp(scope, key, value, secure, inserttime) "
+         "VALUES (:scope, :key, :value, :secure, :inserttime)"),
          getter_AddRefs(mInsertKeyStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // update the secure status of an existing key
   rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
          "UPDATE webappsstore2_temp "
          "SET secure = :secure "
          "WHERE scope = :scope "
@@ -386,16 +422,24 @@ nsDOMStoragePersistentDB::Init(const nsS
 
   // remove keys belonging exactly only to a specific domain
   rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
          "DELETE FROM webappsstore2_view "
          "WHERE scope = :scope"),
          getter_AddRefs(mRemoveStorageStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // remove keys that are junger then a specific time
+  rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
+         "DELETE FROM webappsstore2_view "
+         "WHERE inserttime > :time "
+         "AND NOT ISOFFLINE(scope)"),
+         getter_AddRefs(mRemoveTimeRangeStatement));
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // remove all keys
   rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
          "DELETE FROM webappsstore2_view"),
          getter_AddRefs(mRemoveAllStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // check the usage for a given owner that is an offline-app allowed domain
   rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
@@ -684,16 +728,19 @@ nsDOMStoragePersistentDB::SetKey(nsDOMSt
                                 aKey);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = binder->BindStringByName(NS_LITERAL_CSTRING("value"),
                                 aValue);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = binder->BindInt32ByName(NS_LITERAL_CSTRING("secure"),
                                aSecure ? 1 : 0);
   NS_ENSURE_SUCCESS(rv, rv);
+  rv = binder->BindInt64ByName(NS_LITERAL_CSTRING("inserttime"),
+                               PR_Now());
+  NS_ENSURE_SUCCESS(rv, rv);
 
   rv = binder.Add();
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mInsertKeyStatement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!aStorage->GetQuotaDomainDBKey(!aExcludeOfflineFromUsage).IsEmpty()) {
@@ -801,16 +848,42 @@ nsDOMStoragePersistentDB::ClearStorage(n
 
   rv = mRemoveStorageStatement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
+nsDOMStoragePersistentDB::RemoveTimeRange(PRInt64 since)
+{
+  nsresult rv;
+
+  rv = MaybeCommitInsertTransaction();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mozStorageStatementScoper scope(mRemoveTimeRangeStatement);
+
+  Binder binder(mRemoveTimeRangeStatement, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = binder->BindInt64ByName(NS_LITERAL_CSTRING("time"),
+                                    since);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = binder.Add();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mRemoveTimeRangeStatement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
 nsDOMStoragePersistentDB::RemoveOwner(const nsACString& aOwner,
                                       PRBool aIncludeSubDomains)
 {
   nsresult rv;
 
   rv = MaybeCommitInsertTransaction();
   NS_ENSURE_SUCCESS(rv, rv);
 
--- a/dom/src/storage/nsDOMStoragePersistentDB.h
+++ b/dom/src/storage/nsDOMStoragePersistentDB.h
@@ -124,16 +124,24 @@ public:
    * Removes keys owned by domains that either match or don't match the
    * list.
    */
   nsresult
   RemoveOwners(const nsTArray<nsString>& aOwners,
                PRBool aIncludeSubDomains, PRBool aMatch);
 
   /**
+   * Remove all values from all scopes not marked as offline that has been
+   * created after the time specified with 'aSince'.  Used by the Clear Private
+   * Data dialog.  'aSince' value is compatible with PR_Now() function.
+   */
+  nsresult
+  RemoveTimeRange(PRInt64 aSince);
+
+  /**
    * Removes all keys from storage. Used when clearing storage.
    */
   nsresult
   RemoveAll();
 
   /**
     * Returns usage for a storage using its GetQuotaDomainDBKey() as a key.
     */
@@ -171,16 +179,17 @@ protected:
   nsCOMPtr<mozIStorageStatement> mDeleteTemporaryTableStatement;
   nsCOMPtr<mozIStorageStatement> mGetAllKeysStatement;
   nsCOMPtr<mozIStorageStatement> mGetKeyValueStatement;
   nsCOMPtr<mozIStorageStatement> mInsertKeyStatement;
   nsCOMPtr<mozIStorageStatement> mSetSecureStatement;
   nsCOMPtr<mozIStorageStatement> mRemoveKeyStatement;
   nsCOMPtr<mozIStorageStatement> mRemoveOwnerStatement;
   nsCOMPtr<mozIStorageStatement> mRemoveStorageStatement;
+  nsCOMPtr<mozIStorageStatement> mRemoveTimeRangeStatement;
   nsCOMPtr<mozIStorageStatement> mRemoveAllStatement;
   nsCOMPtr<mozIStorageStatement> mGetOfflineExcludedUsageStatement;
   nsCOMPtr<mozIStorageStatement> mGetFullUsageStatement;
 
   nsCString mCachedOwner;
   PRInt32 mCachedUsage;
 
   friend class nsDOMStorageDBWrapper;
--- a/dom/tests/mochitest/Makefile.in
+++ b/dom/tests/mochitest/Makefile.in
@@ -49,16 +49,17 @@ DIRS	+= \
 	dom-level2-html \
 	ajax \
 	bugs \
 	chrome \
 	general \
 	whatwg \
 	geolocation \
 	localstorage \
+	globalstorage \
 	sessionstorage \
 	storageevent \
 	$(NULL)
 
 #needs IPC support
 ifneq (mobile,$(MOZ_BUILD_APP))
 DIRS	+= notification
 endif
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/globalstorage/Makefile.in
@@ -0,0 +1,54 @@
+#
+# ***** 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 Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Jan Bambas <honzab@firemni.cz>
+#
+# 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 *****
+
+DEPTH		= ../../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir	= dom/tests/mochitest/globalstorage
+
+include $(DEPTH)/config/autoconf.mk
+
+include $(topsrcdir)/config/rules.mk
+
+_TEST_FILES	= \
+    test_globalStorageDeleteSinceAPI.html \
+    $(NULL)
+
+libs::	$(_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/globalstorage/test_globalStorageDeleteSinceAPI.html
@@ -0,0 +1,73 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>globalStorage delete since API test</title>
+
+<script type="text/javascript" src="/MochiKit/packed.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+
+function cleanup()
+{
+  globalStorage[window.location.hostname].removeItem("key1");
+  globalStorage[window.location.hostname].removeItem("key2");
+  globalStorage[window.location.hostname].removeItem("key3");
+  globalStorage[window.location.hostname].removeItem("key4");
+  SimpleTest.finish();
+}
+
+function doTest()
+{
+  var since = 0;
+
+  setTimeout('globalStorage[window.location.hostname].key1 = "value1"',     0);
+  setTimeout('globalStorage[window.location.hostname].key2 = "value2"',   100);
+  setTimeout('globalStorage[window.location.hostname].key3 = "value3"',   200);
+
+  // Since now, all added/changed items will be deleted
+  setTimeout(function() {
+    // Now in microseconds...
+    since = (new Date()).getTime() * 1000;
+  }, 300);
+
+  // Rather give the timer chance to move forward, on some systems the
+  // granularity may be too grainy.
+
+  setTimeout('globalStorage[window.location.hostname].key4 = "value4"',   500);
+  setTimeout('globalStorage[window.location.hostname].key1 = "value1-2"', 600);
+
+  setTimeout(function() {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+    is(globalStorage[window.location.hostname].length, 4, "Expected 4 items before delete");
+
+    try {
+      var domStorageManager = Components.classes["@mozilla.org/dom/storagemanager;1"]
+                                        .getService(Components.interfaces.nsIDOMStorageManager);
+      domStorageManager.clearStorageDataSince(since);
+    }
+    catch(ex) {
+      ok(false, ex);
+    }
+
+    todo(globalStorage[window.location.hostname].key1 == null, "key1 deleted (Bug 600366)");
+    is(globalStorage[window.location.hostname].key2, "value2", "key2 left");
+    is(globalStorage[window.location.hostname].key3, "value3", "key3 left");
+    todo(globalStorage[window.location.hostname].key4 == null, "key4 deleted (Bug 600366)");
+    is(globalStorage[window.location.hostname].length, 2, "Expected 2 items after delete");
+
+    cleanup();
+  }, 700);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+
+</head>
+
+<body onload="doTest();">
+
+</body>
+</html>
--- a/dom/tests/mochitest/localstorage/Makefile.in
+++ b/dom/tests/mochitest/localstorage/Makefile.in
@@ -63,16 +63,18 @@ include $(topsrcdir)/config/rules.mk
     test_brokenUTF-16.html \
     test_cookieBlock.html \
     test_cookieSession-phase1.html \
     test_cookieSession-phase2.html \
     test_embededNulls.html \
     test_localStorageBase.html \
     test_localStorageBasePrivateBrowsing.html \
     test_localStorageBaseSessionOnly.html \
+    test_localStorageDeleteSinceAPI.html \
+    test_localStorageDeleteSinceAPIPrivateBrowsing.html \
     test_localStorageCookieSettings.html \
     test_localStorageEnablePref.html \
     test_localStorageOriginsEquals.html \
     test_localStorageOriginsDiff.html \
     test_localStorageOriginsPortDiffs.html \
     test_localStorageOriginsDomainDiffs.html \
     test_localStorageOriginsSchemaDiffs.html \
     test_localStorageReplace.html \
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/localstorage/test_localStorageDeleteSinceAPI.html
@@ -0,0 +1,70 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>localStorage delete since API test</title>
+
+<script type="text/javascript" src="/MochiKit/packed.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+
+function cleanup()
+{
+  localStorage.clear();
+  SimpleTest.finish();
+}
+
+function doTest()
+{
+  var since = 0;
+
+  setTimeout('localStorage.key1 = "value1"',     0);
+  setTimeout('localStorage.key2 = "value2"',   100);
+  setTimeout('localStorage.key3 = "value3"',   200);
+
+  // Since now, all added/changed items will be deleted
+  setTimeout(function() {
+    // Now in microseconds...
+    since = (new Date()).getTime() * 1000;
+  }, 300);
+
+  // Rather give the timer chance to move forward, on some systems the
+  // granularity may be too grainy.
+
+  setTimeout('localStorage.key4 = "value4"',   500);
+  setTimeout('localStorage.key1 = "value1-2"', 600);
+
+  setTimeout(function() {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+    is(localStorage.length, 4, "Expected 4 items before delete in localStorage");
+
+    try {
+      var domStorageManager = Components.classes["@mozilla.org/dom/storagemanager;1"]
+                                        .getService(Components.interfaces.nsIDOMStorageManager);
+      domStorageManager.clearStorageDataSince(since);
+    }
+    catch(ex) {
+      ok(false, ex);
+    }
+
+    is(localStorage.key1, null, "key1 deleted (if this fails see bug 600366 first)");
+    is(localStorage.key2, "value2", "key2 left");
+    is(localStorage.key3, "value3", "key3 left");
+    is(localStorage.key4, null, "key4 deleted (if this fails see bug 600366 first)");
+    is(localStorage.length, 2, "Expected 2 items after delete in localStorage");
+
+    cleanup();
+  }, 700);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+
+</head>
+
+<body onload="doTest();">
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/localstorage/test_localStorageDeleteSinceAPIPrivateBrowsing.html
@@ -0,0 +1,85 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>localStorage delete since API test in Private Browsing</title>
+
+<script type="text/javascript" src="/MochiKit/packed.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="pbSwitch.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+
+function cleanup()
+{
+  localStorage.clear();
+  leavePrivateBrowsing();
+
+  SimpleTest.finish();
+}
+
+function startTest()
+{
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+  if (get_PBSvc())
+    doTest();
+  else
+    ok(true, "No private browsing service, test could not be performed");
+}
+
+function doTest()
+{
+  var since = 0;
+
+  enterPrivateBrowsing();
+
+  setTimeout('localStorage.key1 = "value1"',     0);
+  setTimeout('localStorage.key2 = "value2"',   100);
+  setTimeout('localStorage.key3 = "value3"',   200);
+
+  // Since now, all added/changed items will be deleted
+  setTimeout(function() {
+    // Now in microseconds...
+    since = (new Date()).getTime() * 1000;
+  }, 300);
+
+  // Rather give the timer chance to move forward, on some systems the
+  // granularity may be too grainy.
+
+  setTimeout('localStorage.key4 = "value4"',   500);
+  setTimeout('localStorage.key1 = "value1-2"', 600);
+
+  setTimeout(function() {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+    is(localStorage.length, 4, "Expected 4 items before delete in localStorage");
+
+    try {
+      var domStorageManager = Components.classes["@mozilla.org/dom/storagemanager;1"]
+                                        .getService(Components.interfaces.nsIDOMStorageManager);
+      domStorageManager.clearStorageDataSince(since);
+    }
+    catch(ex) {
+      ok(false, ex);
+    }
+
+    is(localStorage.key1, null, "key1 deleted (if this fails see bug 600366 first)");
+    is(localStorage.key2, "value2", "key2 left");
+    is(localStorage.key3, "value3", "key3 left");
+    is(localStorage.key4, null, "key4 deleted (if this fails see bug 600366 first)");
+    is(localStorage.length, 2, "Expected 2 items after delete in localStorage");
+
+    cleanup();
+  }, 700);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+
+</head>
+
+<body onload="startTest();">
+
+</body>
+</html>
--- a/dom/tests/mochitest/sessionstorage/Makefile.in
+++ b/dom/tests/mochitest/sessionstorage/Makefile.in
@@ -48,16 +48,17 @@ include $(topsrcdir)/config/rules.mk
 _TEST_FILES	= \
     frameReplace.html \
     frameEqual.html \
     frameNotEqual.html \
     file_http.html \
     file_https.html \
     test_sessionStorageBase.html \
     test_sessionStorageClone.html \
+    test_sessionStorageDeleteSinceAPI.html \
     test_sessionStorageReplace.html \
     test_sessionStorageHttpHttps.html \
     interOriginSlave.js \
     interOriginTest.js \
     $(NULL)
 
 _CHROME_FILES = \
     test_sessionStorageFromChrome.xhtml \
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/sessionstorage/test_sessionStorageDeleteSinceAPI.html
@@ -0,0 +1,67 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>sessionStorage delete since API test</title>
+
+<script type="text/javascript" src="/MochiKit/packed.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+
+function cleanup()
+{
+  sessionStorage.clear();
+  SimpleTest.finish();
+}
+
+function doTest()
+{
+  var since = 0;
+
+  setTimeout('sessionStorage.key1 = "value1"',     0);
+  setTimeout('sessionStorage.key2 = "value2"',   100);
+  setTimeout('sessionStorage.key3 = "value3"',   200);
+
+  // Since now, all added/changed items will be deleted
+  setTimeout(function() {
+    // Now in microseconds...
+    since = (new Date()).getTime() * 1000;
+  }, 300);
+
+  setTimeout('sessionStorage.key4 = "value4"',   500);
+  setTimeout('sessionStorage.key1 = "value1-2"', 600);
+
+  setTimeout(function() {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+    is(sessionStorage.length, 4, "Expected 4 items before delete in sessionStorage");
+
+    try {
+      var domStorageManager = Components.classes["@mozilla.org/dom/storagemanager;1"]
+                                        .getService(Components.interfaces.nsIDOMStorageManager);
+      domStorageManager.clearStorageDataSince(since);
+    }
+    catch(ex) {
+      ok(false, ex);
+    }
+
+    is(sessionStorage.length, 2, "Expected 2 items after delete in sessionStorage");
+    is(sessionStorage.key1, null, "key1 deleted");
+    is(sessionStorage.key2, "value2", "key2 left");
+    is(sessionStorage.key3, "value3", "key3 left");
+    is(sessionStorage.key4, null, "key4 deleted");
+
+    cleanup();
+  }, 700);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+
+</head>
+
+<body onload="doTest();">
+
+</body>
+</html>