Bug 422526 - implement localStorage, p=Honza Bambas+Dave Camp, r=jst+dcamp+bz

This commit is contained in:
Honza Bambas 2009-03-19 00:43:45 +01:00
parent 8a5e821be1
commit 57c667fe6b
42 changed files with 2808 additions and 256 deletions

View File

@ -1792,7 +1792,7 @@ nsDocShell::GetSessionStorageForURI(nsIURI* aURI,
nsCOMPtr<nsPIDOMStorage> pistorage = do_QueryInterface(newstorage);
if (!pistorage)
return NS_ERROR_FAILURE;
pistorage->Init(NS_ConvertUTF8toUTF16(currentDomain), PR_FALSE);
pistorage->InitAsSessionStorage(aURI);
if (!mStorages.Put(currentDomain, newstorage))
return NS_ERROR_OUT_OF_MEMORY;

View File

@ -1210,6 +1210,15 @@ static nsDOMClassInfoData sClassInfoData[] = {
nsIXPCScriptable::WANT_DELPROPERTY |
nsIXPCScriptable::DONT_ENUM_STATIC_PROPS |
nsIXPCScriptable::WANT_NEWENUMERATE)
NS_DEFINE_CLASSINFO_DATA(Storage2, nsStorage2SH,
DOM_DEFAULT_SCRIPTABLE_FLAGS |
nsIXPCScriptable::WANT_NEWRESOLVE |
nsIXPCScriptable::WANT_GETPROPERTY |
nsIXPCScriptable::WANT_SETPROPERTY |
nsIXPCScriptable::WANT_DELPROPERTY |
nsIXPCScriptable::DONT_ENUM_STATIC_PROPS |
nsIXPCScriptable::WANT_NEWENUMERATE)
NS_DEFINE_CLASSINFO_DATA(StorageList, nsStorageListSH,
ARRAY_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(StorageItem, nsDOMGenericSH,
@ -3432,6 +3441,10 @@ nsDOMClassInfo::Init()
DOM_CLASSINFO_MAP_ENTRY(nsIDOMStorage)
DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN(Storage2, nsIDOMStorage2)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMStorage2)
DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN(StorageList, nsIDOMStorageList)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMStorageList)
DOM_CLASSINFO_MAP_END
@ -10495,6 +10508,208 @@ nsStorageSH::NewEnumerate(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
}
// Storage2SH
// One reason we need a newResolve hook is that in order for
// enumeration of storage object keys to work the keys we're
// enumerating need to exist on the storage object for the JS engine
// to find them.
NS_IMETHODIMP
nsStorage2SH::NewResolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
JSObject *obj, jsval id, PRUint32 flags,
JSObject **objp, PRBool *_retval)
{
JSObject *realObj;
wrapper->GetJSObject(&realObj);
// First check to see if the property is defined on our prototype,
// after converting id to a string if it's an integer.
JSString *jsstr = JS_ValueToString(cx, id);
if (!jsstr) {
return JS_FALSE;
}
JSObject *proto = ::JS_GetPrototype(cx, realObj);
JSBool hasProp;
if (proto &&
(::JS_HasUCProperty(cx, proto, ::JS_GetStringChars(jsstr),
::JS_GetStringLength(jsstr), &hasProp) &&
hasProp)) {
// We found the property we're resolving on the prototype,
// nothing left to do here then.
return NS_OK;
}
// We're resolving property that doesn't exist on the prototype,
// check if the key exists in the storage object.
nsCOMPtr<nsIDOMStorage2> storage(do_QueryWrappedNative(wrapper));
// GetItem() will return null if the caller can't access the session
// storage item.
nsAutoString data;
nsresult rv = storage->GetItem(nsDependentJSString(jsstr), data);
NS_ENSURE_SUCCESS(rv, rv);
if (!DOMStringIsNull(data)) {
if (!::JS_DefineUCProperty(cx, realObj, ::JS_GetStringChars(jsstr),
::JS_GetStringLength(jsstr), JSVAL_VOID, nsnull,
nsnull, 0)) {
return NS_ERROR_FAILURE;
}
*objp = realObj;
}
return NS_OK;
}
NS_IMETHODIMP
nsStorage2SH::GetProperty(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
JSObject *obj, jsval id, jsval *vp, PRBool *_retval)
{
nsCOMPtr<nsIDOMStorage2> storage(do_QueryWrappedNative(wrapper));
NS_ENSURE_TRUE(storage, NS_ERROR_UNEXPECTED);
nsAutoString val;
nsresult rv = NS_OK;
if (JSVAL_IS_STRING(id)) {
// For native wrappers, do not get random names on storage objects.
if (ObjectIsNativeWrapper(cx, obj)) {
return NS_ERROR_NOT_AVAILABLE;
}
rv = storage->GetItem(nsDependentJSString(id), val);
NS_ENSURE_SUCCESS(rv, rv);
} else {
PRInt32 n = GetArrayIndexFromId(cx, id);
NS_ENSURE_TRUE(n >= 0, NS_ERROR_NOT_AVAILABLE);
rv = storage->Key(n, val);
NS_ENSURE_SUCCESS(rv, rv);
}
JSAutoRequest ar(cx);
if (DOMStringIsNull(val)) {
*vp = JSVAL_NULL;
}
else {
JSString *str =
::JS_NewUCStringCopyN(cx, reinterpret_cast<const jschar *>(val.get()),
val.Length());
NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
*vp = STRING_TO_JSVAL(str);
}
return NS_SUCCESS_I_DID_SOMETHING;
}
NS_IMETHODIMP
nsStorage2SH::SetProperty(nsIXPConnectWrappedNative *wrapper,
JSContext *cx, JSObject *obj, jsval id,
jsval *vp, PRBool *_retval)
{
nsCOMPtr<nsIDOMStorage2> storage(do_QueryWrappedNative(wrapper));
NS_ENSURE_TRUE(storage, NS_ERROR_UNEXPECTED);
JSString *key = ::JS_ValueToString(cx, id);
NS_ENSURE_TRUE(key, NS_ERROR_UNEXPECTED);
JSString *value = ::JS_ValueToString(cx, *vp);
NS_ENSURE_TRUE(value, NS_ERROR_UNEXPECTED);
nsresult rv = storage->SetItem(nsDependentJSString(key),
nsDependentJSString(value));
if (NS_SUCCEEDED(rv)) {
rv = NS_SUCCESS_I_DID_SOMETHING;
}
return rv;
}
NS_IMETHODIMP
nsStorage2SH::DelProperty(nsIXPConnectWrappedNative *wrapper,
JSContext *cx, JSObject *obj, jsval id,
jsval *vp, PRBool *_retval)
{
nsCOMPtr<nsIDOMStorage2> storage(do_QueryWrappedNative(wrapper));
NS_ENSURE_TRUE(storage, NS_ERROR_UNEXPECTED);
JSString *key = ::JS_ValueToString(cx, id);
NS_ENSURE_TRUE(key, NS_ERROR_UNEXPECTED);
nsresult rv = storage->RemoveItem(nsDependentJSString(key));
if (NS_SUCCEEDED(rv)) {
rv = NS_SUCCESS_I_DID_SOMETHING;
}
return rv;
}
NS_IMETHODIMP
nsStorage2SH::NewEnumerate(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
JSObject *obj, PRUint32 enum_op, jsval *statep,
jsid *idp, PRBool *_retval)
{
nsTArray<nsString> *keys =
(nsTArray<nsString> *)JSVAL_TO_PRIVATE(*statep);
switch (enum_op) {
case JSENUMERATE_INIT:
{
nsCOMPtr<nsPIDOMStorage> storage(do_QueryWrappedNative(wrapper));
// XXXndeakin need to free the keys afterwards
keys = storage->GetKeys();
NS_ENSURE_TRUE(keys, NS_ERROR_OUT_OF_MEMORY);
*statep = PRIVATE_TO_JSVAL(keys);
if (idp) {
*idp = INT_TO_JSVAL(keys->Length());
}
break;
}
case JSENUMERATE_NEXT:
if (keys->Length() != 0) {
nsString& key = keys->ElementAt(0);
JSString *str =
JS_NewUCStringCopyN(cx, reinterpret_cast<const jschar *>
(key.get()),
key.Length());
NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
JS_ValueToId(cx, STRING_TO_JSVAL(str), idp);
keys->RemoveElementAt(0);
break;
}
// Fall through
case JSENUMERATE_DESTROY:
delete keys;
*statep = JSVAL_NULL;
break;
default:
NS_NOTREACHED("Bad call from the JS engine");
return NS_ERROR_FAILURE;
}
return NS_OK;
}
// StorageList scriptable helper
nsISupports*

View File

@ -1478,6 +1478,38 @@ public:
}
};
class nsStorage2SH : public nsDOMGenericSH
{
protected:
nsStorage2SH(nsDOMClassInfoData* aData) : nsDOMGenericSH(aData)
{
}
virtual ~nsStorage2SH()
{
}
NS_IMETHOD NewResolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
JSObject *obj, jsval id, PRUint32 flags,
JSObject **objp, PRBool *_retval);
NS_IMETHOD SetProperty(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
JSObject *obj, jsval id, jsval *vp, PRBool *_retval);
NS_IMETHOD GetProperty(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
JSObject *obj, jsval id, jsval *vp, PRBool *_retval);
NS_IMETHOD DelProperty(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
JSObject *obj, jsval id, jsval *vp, PRBool *_retval);
NS_IMETHOD NewEnumerate(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
JSObject *obj, PRUint32 enum_op, jsval *statep,
jsid *idp, PRBool *_retval);
public:
static nsIClassInfo *doCreate(nsDOMClassInfoData* aData)
{
return new nsStorage2SH(aData);
}
};
class nsStorageListSH : public nsNamedArraySH
{
protected:

View File

@ -384,6 +384,7 @@ enum nsDOMClassInfoID {
// WhatWG WebApps Objects
eDOMClassInfo_Storage_id,
eDOMClassInfo_Storage2_id,
eDOMClassInfo_StorageList_id,
eDOMClassInfo_StorageItem_id,
eDOMClassInfo_StorageEvent_id,

View File

@ -1664,6 +1664,7 @@ nsGlobalWindow::SetNewDocument(nsIDocument* aDocument,
mDocument = do_QueryInterface(aDocument);
mDoc = aDocument;
mLocalStorage = nsnull;
#ifdef DEBUG
mLastOpenedURI = aDocument->GetDocumentURI();
@ -6841,6 +6842,41 @@ nsGlobalWindow::GetGlobalStorage(nsIDOMStorageList ** aGlobalStorage)
#endif
}
NS_IMETHODIMP
nsGlobalWindow::GetLocalStorage(nsIDOMStorage2 ** aLocalStorage)
{
FORWARD_TO_INNER(GetLocalStorage, (aLocalStorage), NS_ERROR_UNEXPECTED);
NS_ENSURE_ARG(aLocalStorage);
if (!mLocalStorage) {
*aLocalStorage = nsnull;
nsresult rv;
nsIPrincipal *principal = GetPrincipal();
if (!principal)
return NS_OK;
PRPackedBool sessionOnly;
if (!nsDOMStorage::CanUseStorage(&sessionOnly))
return NS_ERROR_DOM_SECURITY_ERR;
if (sessionOnly)
return NS_ERROR_DOM_SECURITY_ERR;
nsCOMPtr<nsIDOMStorageManager> storageManager =
do_GetService("@mozilla.org/dom/storagemanager;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = storageManager->GetLocalStorageForPrincipal(principal, getter_AddRefs(mLocalStorage));
NS_ENSURE_SUCCESS(rv, rv);
}
NS_ADDREF(*aLocalStorage = mLocalStorage);
return NS_OK;
}
//*****************************************************************************
// nsGlobalWindow::nsIInterfaceRequestor
//*****************************************************************************
@ -6985,7 +7021,7 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic,
PR_FALSE,
getter_AddRefs(storage));
if (storage != aSubject) {
if (!SameCOMIdentity(storage, aSubject)) {
// A sessionStorage object changed, but not our session storage
// object.
return NS_OK;
@ -7008,8 +7044,8 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic,
return NS_OK;
}
if (!nsDOMStorageList::CanAccessDomain(nsDependentString(aData),
NS_ConvertASCIItoUTF16(currentDomain))) {
if (!nsDOMStorageList::CanAccessDomain(NS_ConvertUTF16toUTF8(aData),
currentDomain)) {
// This window can't reach the global storage object for the
// domain for which the change happened, so don't fire any
// events in this window.

View File

@ -710,6 +710,8 @@ protected:
nsCOMPtr<nsIDOMCrypto> mCrypto;
nsCOMPtr<nsIDOMPkcs11> mPkcs11;
nsCOMPtr<nsIDOMStorage2> mLocalStorage;
nsCOMPtr<nsISupports> mInnerWindowHolders[NS_STID_ARRAY_UBOUND];
nsCOMPtr<nsIPrincipal> mOpenerScriptPrincipal; // strong; used to determine
// whether to clear scope

View File

@ -52,6 +52,7 @@ EXPORTS = \
XPIDLSRCS = \
nsIDOMToString.idl \
nsIDOMStorage2.idl \
nsIDOMStorageManager.idl \
$(NULL)

View File

@ -85,7 +85,6 @@ interface nsIDOMStorage : nsISupports
*
* @param key key to set
* @param data data to associate with the key
* @returns found item or null if the key was not found
*/
void setItem(in DOMString key, in DOMString data);

View File

@ -0,0 +1,102 @@
/* -*- Mode: IDL; 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 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 ***** */
#include "domstubs.idl"
/**
* Interface for client side storage. See
* http://www.whatwg.org/specs/web-apps/current-work/multipage/structured.html#storage0
* for more information.
*
* A storage object stores an arbitrary set of key-value pairs, which
* may be retrieved, modified and removed as needed. A key may only
* exist once within a storage object, and only one value may be
* associated with a particular key. Keys are stored in a particular
* order with the condition that this order not change by merely changing
* the value associated with a key, but the order may change when a
* key is added or removed.
*/
[scriptable, uuid(A67BA00F-CCB8-4ffe-BADA-F8913CAAFB20)]
interface nsIDOMStorage2 : nsISupports
{
/**
* The number of keys stored.
*/
readonly attribute unsigned long length;
/**
* Retrieve the name of the key at a particular index.
*
* @param index index of the item to retrieve
* @returns the key at index
* @throws INDEX_SIZE_ERR if there is no key at that index
*/
DOMString key(in unsigned long index);
/**
* Retrieve an item with a given key
*
* @param key key to retrieve
* @returns found data or empty string if the key was not found
*/
DOMString getItem(in DOMString key);
/**
* Assign a value with a key. If the key does not exist already, a new
* key is added associated with that value. If the key already exists,
* then the existing value is replaced with a new value.
*
* @param key key to set
* @param data data to associate with the key
*/
void setItem(in DOMString key, in DOMString data);
/**
* Remove a key and its corresponding value.
*
* @param key key to remove
*/
void removeItem(in DOMString key);
/**
* Clear the content of this storage bound to a domain
* or an origin.
*/
void clear();
};

View File

@ -37,7 +37,10 @@
#include "nsISupports.idl"
[scriptable, uuid(6e4bc25e-f056-4c6c-b27e-89152ca91834)]
interface nsIDOMStorage2;
interface nsIPrincipal;
[scriptable, uuid(9efc2081-218f-4622-837b-40bdb870a1c8)]
interface nsIDOMStorageManager : nsISupports
{
/**
@ -55,4 +58,11 @@ interface nsIDOMStorageManager : nsISupports
* with the "offline-app" permission will be removed from the database.
*/
void clearOfflineApps();
/**
* Returns instance of localStorage object for aURI's origin.
* This method ensures there is always only a single instance
* for a single origin.
*/
nsIDOMStorage2 getLocalStorageForPrincipal(in nsIPrincipal aPrincipal);
};

View File

@ -46,9 +46,10 @@
*/
interface nsIDOMStorage;
interface nsIDOMStorage2;
interface nsIDOMStorageList;
[scriptable, uuid(55E9C181-2476-47CF-97F8-EFDAAF7B6F7A)]
[scriptable, uuid(B4F572FB-9BA5-480b-9361-C230863323E4)]
interface nsIDOMStorageWindow : nsISupports
{
/**
@ -60,4 +61,9 @@ interface nsIDOMStorageWindow : nsISupports
* Global storage, accessible by domain.
*/
readonly attribute nsIDOMStorageList globalStorage;
/**
* Local storage for the current browsing context.
*/
readonly attribute nsIDOMStorage2 localStorage;
};

View File

@ -56,13 +56,15 @@ class nsPIDOMStorage : public nsISupports
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_PIDOMSTORAGE_IID)
virtual void Init(const nsAString &aDomain, PRBool aUseDB) = 0;
virtual nsresult InitAsLocalStorage(nsIPrincipal *aPrincipal) = 0;
virtual nsresult InitAsGlobalStorage(const nsACString &aDomainDemanded) = 0;
virtual nsresult InitAsSessionStorage(nsIURI* aURI) = 0;
virtual already_AddRefed<nsIDOMStorage> Clone() = 0;
virtual nsTArray<nsString> *GetKeys() = 0;
virtual const nsString &Domain() = 0;
virtual const nsCString &Domain() = 0;
virtual PRBool CanAccess(nsIPrincipal *aPrincipal) = 0;
};

View File

@ -61,6 +61,7 @@
#include "nsIOfflineCacheUpdate.h"
#include "nsIJSContextStack.h"
#include "nsIPrivateBrowsingService.h"
#include "nsDOMString.h"
#include "nsNetCID.h"
static const PRUint32 ASK_BEFORE_ACCEPT = 1;
@ -84,7 +85,7 @@ static const char kOfflineAppQuota[] = "offline-apps.quota.max";
// The URI returned is the innermost URI that should be used for
// security-check-like stuff. aHost is its hostname, correctly canonicalized.
static nsresult
GetPrincipalURIAndHost(nsIPrincipal* aPrincipal, nsIURI** aURI, nsString& aHost)
GetPrincipalURIAndHost(nsIPrincipal* aPrincipal, nsIURI** aURI, nsCString& aHost)
{
nsresult rv = aPrincipal->GetDomain(aURI);
NS_ENSURE_SUCCESS(rv, rv);
@ -103,13 +104,11 @@ GetPrincipalURIAndHost(nsIPrincipal* aPrincipal, nsIURI** aURI, nsString& aHost)
return NS_ERROR_UNEXPECTED;
}
nsCAutoString asciiHost;
rv = innerURI->GetAsciiHost(asciiHost);
rv = innerURI->GetAsciiHost(aHost);
if (NS_FAILED(rv)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
CopyUTF8toUTF16(asciiHost, aHost);
innerURI.swap(*aURI);
return NS_OK;
@ -157,11 +156,11 @@ IsCallerSecure()
// and a limit after which a warning event will be sent to the observer
// service. The warn limit may be -1, in which case there will be no warning.
static void
GetQuota(const nsAString &aDomain, PRInt32 *aQuota, PRInt32 *aWarnQuota)
GetQuota(const nsACString &aDomain, PRInt32 *aQuota, PRInt32 *aWarnQuota)
{
// Fake a URI for the permission manager
nsCOMPtr<nsIURI> uri;
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_STRING("http://") + aDomain);
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("http://") + aDomain);
if (uri) {
nsCOMPtr<nsIPermissionManager> permissionManager =
@ -295,8 +294,11 @@ GetOfflineDomains(nsTArray<nsString>& aDomains)
PRBool hasMore;
while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
nsCOMPtr<nsIPermission> perm;
rv = enumerator->GetNext(getter_AddRefs(perm));
nsCOMPtr<nsISupports> supp;
rv = enumerator->GetNext(getter_AddRefs(supp));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPermission> perm(do_QueryInterface(supp, &rv));
NS_ENSURE_SUCCESS(rv, rv);
PRUint32 capability;
@ -330,7 +332,8 @@ nsDOMStorageManager::Observe(nsISupports *aSubject,
#ifdef MOZ_STORAGE
nsresult rv = nsDOMStorage::InitDB();
NS_ENSURE_SUCCESS(rv, rv);
return nsDOMStorage::gStorageDB->RemoveOwner(nsDependentString(aData));
return nsDOMStorage::gStorageDB->RemoveOwner(NS_ConvertUTF16toUTF8(aData),
PR_FALSE);
#endif
} else if (!strcmp(aTopic, "cookie-changed") &&
!nsCRT::strcmp(aData, NS_LITERAL_STRING("cleared").get())) {
@ -344,7 +347,7 @@ nsDOMStorageManager::Observe(nsISupports *aSubject,
nsTArray<nsString> domains;
rv = GetOfflineDomains(domains);
NS_ENSURE_SUCCESS(rv, rv);
return nsDOMStorage::gStorageDB->RemoveOwners(domains, PR_FALSE);
return nsDOMStorage::gStorageDB->RemoveOwners(domains, PR_FALSE, PR_FALSE);
#endif
} else if (!strcmp(aTopic, NS_PRIVATE_BROWSING_SWITCH_TOPIC)) {
mStorages.EnumerateEntries(ClearStorage, nsnull);
@ -364,7 +367,8 @@ nsDOMStorageManager::GetUsage(const nsAString& aDomain,
nsresult rv = nsDOMStorage::InitDB();
NS_ENSURE_SUCCESS(rv, rv);
return nsDOMStorage::gStorageDB->GetUsage(aDomain, aUsage);
return nsDOMStorage::gStorageDB->GetUsage(NS_ConvertUTF16toUTF8(aDomain),
PR_FALSE, aUsage);
}
NS_IMETHODIMP
@ -376,7 +380,30 @@ nsDOMStorageManager::ClearOfflineApps()
nsTArray<nsString> domains;
rv = GetOfflineDomains(domains);
NS_ENSURE_SUCCESS(rv, rv);
return nsDOMStorage::gStorageDB->RemoveOwners(domains, PR_TRUE);
return nsDOMStorage::gStorageDB->RemoveOwners(domains, PR_FALSE, PR_TRUE);
}
NS_IMETHODIMP
nsDOMStorageManager::GetLocalStorageForPrincipal(nsIPrincipal *aPrincipal,
nsIDOMStorage2 **aResult)
{
NS_ENSURE_ARG_POINTER(aPrincipal);
*aResult = nsnull;
nsresult rv;
nsRefPtr<nsDOMStorage2> storage = new nsDOMStorage2();
if (!storage)
return NS_ERROR_OUT_OF_MEMORY;
rv = storage->InitAsLocalStorage(aPrincipal);
if (NS_FAILED(rv))
return rv;
*aResult = storage.get();
storage.forget();
return NS_OK;
}
void
@ -390,7 +417,7 @@ nsDOMStorageManager::AddToStoragesHash(nsDOMStorage* aStorage)
void
nsDOMStorageManager::RemoveFromStoragesHash(nsDOMStorage* aStorage)
{
nsDOMStorageEntry* entry = mStorages.GetEntry(aStorage);
nsDOMStorageEntry* entry = mStorages.GetEntry(aStorage);
if (entry)
mStorages.RemoveEntry(aStorage);
}
@ -461,23 +488,26 @@ NS_NewDOMStorage(nsISupports* aOuter, REFNSIID aIID, void** aResult)
}
nsDOMStorage::nsDOMStorage()
: mUseDB(PR_FALSE), mSessionOnly(PR_TRUE), mItemsCached(PR_FALSE)
: mUseDB(PR_FALSE)
, mSessionOnly(PR_TRUE)
, mLocalStorage(PR_FALSE)
, mItemsCached(PR_FALSE)
{
mItems.Init(8);
if (nsDOMStorageManager::gStorageManager)
nsDOMStorageManager::gStorageManager->AddToStoragesHash(this);
}
nsDOMStorage::nsDOMStorage(const nsAString& aDomain, PRBool aUseDB)
: mUseDB(aUseDB),
mSessionOnly(PR_TRUE),
mItemsCached(PR_FALSE),
mDomain(aDomain)
{
#ifndef MOZ_STORAGE
mUseDB = PR_FALSE;
nsDOMStorage::nsDOMStorage(nsDOMStorage& aThat)
: mUseDB(PR_FALSE) // Any clone is not using the database
, mSessionOnly(PR_TRUE)
, mLocalStorage(PR_FALSE) // Any clone is not a localStorage
, mItemsCached(PR_FALSE)
, mDomain(aThat.mDomain)
#ifdef MOZ_STORAGE
, mScopeDBKey(aThat.mScopeDBKey)
#endif
{
mItems.Init(8);
if (nsDOMStorageManager::gStorageManager)
nsDOMStorageManager::gStorageManager->AddToStoragesHash(this);
@ -489,15 +519,74 @@ nsDOMStorage::~nsDOMStorage()
nsDOMStorageManager::gStorageManager->RemoveFromStoragesHash(this);
}
void
nsDOMStorage::Init(const nsAString& aDomain, PRBool aUseDB)
nsresult
nsDOMStorage::InitAsLocalStorage(nsIPrincipal *aPrincipal)
{
mDomain.Assign(aDomain);
nsresult rv;
nsCOMPtr<nsIURI> uri;
rv = aPrincipal->GetURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> innerUri = NS_GetInnermostURI(uri);
if (!innerUri)
return NS_ERROR_UNEXPECTED;
// No need to check for a return value. If this would fail we would not get
// here as we call GetPrincipalURIAndHost (nsDOMStorage.cpp:88) from
// nsDOMStorage::CanUseStorage before we query the storage manager for a new
// localStorage. It calls GetAsciiHost on innermost URI. If it fails, we won't
// get to InitAsLocalStorage. Actually, mDomain will get replaced with
// mPrincipal in bug 455070. It is not even used for localStorage.
innerUri->GetAsciiHost(mDomain);
#ifdef MOZ_STORAGE
mUseDB = aUseDB;
#else
mUseDB = PR_FALSE;
nsDOMStorageDB::CreateOriginScopeDBKey(innerUri, mScopeDBKey);
// XXX Bug 357323, we have to solve the issue how to define
// origin for file URLs. In that case CreateOriginScopeDBKey
// fails (the result is empty) and we must avoid database use
// in that case because it produces broken entries w/o owner.
mUseDB = !mScopeDBKey.IsEmpty();
nsDOMStorageDB::CreateQuotaDomainDBKey(mDomain, PR_TRUE, mQuotaDomainDBKey);
#endif
mLocalStorage = PR_TRUE;
return NS_OK;
}
nsresult
nsDOMStorage::InitAsGlobalStorage(const nsACString &aDomainDemanded)
{
mDomain = aDomainDemanded;
#ifdef MOZ_STORAGE
nsDOMStorageDB::CreateDomainScopeDBKey(aDomainDemanded, mScopeDBKey);
// XXX Bug 357323, we have to solve the issue how to define
// origin for file URLs. In that case CreateOriginScopeDBKey
// fails (the result is empty) and we must avoid database use
// in that case because it produces broken entries w/o owner.
if (!(mUseDB = !mScopeDBKey.IsEmpty()))
mScopeDBKey.AppendLiteral(":");
nsDOMStorageDB::CreateQuotaDomainDBKey(aDomainDemanded, PR_TRUE, mQuotaDomainDBKey);
#endif
return NS_OK;
}
nsresult
nsDOMStorage::InitAsSessionStorage(nsIURI* aURI)
{
nsCAutoString domain;
aURI->GetAsciiHost(domain);
mDomain = domain;
#ifdef MOZ_STORAGE
mUseDB = PR_FALSE;
mScopeDBKey.Truncate();
mQuotaDomainDBKey.Truncate();
#endif
return NS_OK;
}
//static
@ -524,7 +613,7 @@ nsDOMStorage::CanUseStorage(PRPackedBool* aSessionOnly)
// IsCallerChrome().
nsCOMPtr<nsIURI> subjectURI;
nsAutoString unused;
nsCAutoString unused;
if (NS_FAILED(GetPrincipalURIAndHost(subjectPrincipal,
getter_AddRefs(subjectURI),
unused))) {
@ -609,6 +698,9 @@ nsDOMStorage::GetLength(PRUint32 *aLength)
if (!CacheStoragePermissions())
return NS_ERROR_DOM_SECURITY_ERR;
// Force reload of items from database. This ensures sync localStorages for
// same origins among different windows.
mItemsCached = PR_FALSE;
if (UseDB())
CacheKeysFromDB();
@ -709,8 +801,7 @@ nsDOMStorage::GetNamedItem(const nsAString& aKey, nsresult* aResult)
else if (UseDB()) {
PRBool secure;
nsAutoString value;
nsAutoString unused;
nsresult rv = GetDBValue(aKey, value, &secure, unused);
nsresult rv = GetDBValue(aKey, value, &secure);
// return null if access isn't allowed or the key wasn't found
if (rv == NS_ERROR_DOM_SECURITY_ERR || rv == NS_ERROR_DOM_NOT_FOUND_ERR)
return nsnull;
@ -731,6 +822,32 @@ nsDOMStorage::GetNamedItem(const nsAString& aKey, nsresult* aResult)
return item;
}
nsresult
nsDOMStorage::GetItem(const nsAString& aKey, nsAString &aData)
{
nsresult rv;
// IMPORTANT:
// CacheStoragePermissions() is called inside of
// GetItem(nsAString, nsIDOMStorageItem)
// To call it particularly in this method would just duplicate
// the call. If the code changes, make sure that call to
// CacheStoragePermissions() is put here!
nsCOMPtr<nsIDOMStorageItem> item;
rv = GetItem(aKey, getter_AddRefs(item));
if (NS_FAILED(rv))
return rv;
if (item) {
rv = item->GetValue(aData);
NS_ENSURE_SUCCESS(rv, rv);
}
else
SetDOMStringToNull(aData);
return NS_OK;
}
NS_IMETHODIMP
nsDOMStorage::GetItem(const nsAString& aKey, nsIDOMStorageItem **aItem)
@ -763,10 +880,7 @@ nsDOMStorage::SetItem(const nsAString& aKey, const nsAString& aData)
}
}
else {
if (UseDB())
newitem = new nsDOMStorageItem(this, aKey, aData, PR_FALSE);
else
newitem = new nsDOMStorageItem(this, aKey, aData, PR_FALSE);
newitem = new nsDOMStorageItem(this, aKey, aData, IsCallerSecure());
if (!newitem)
return NS_ERROR_OUT_OF_MEMORY;
}
@ -810,13 +924,12 @@ NS_IMETHODIMP nsDOMStorage::RemoveItem(const nsAString& aKey)
nsAutoString value;
PRBool secureItem;
nsAutoString owner;
rv = GetDBValue(aKey, value, &secureItem, owner);
rv = GetDBValue(aKey, value, &secureItem);
if (rv == NS_ERROR_DOM_NOT_FOUND_ERR)
return NS_OK;
NS_ENSURE_SUCCESS(rv, rv);
rv = gStorageDB->RemoveKey(mDomain, aKey, owner,
rv = gStorageDB->RemoveKey(this, aKey,
aKey.Length() + value.Length());
NS_ENSURE_SUCCESS(rv, rv);
@ -839,6 +952,47 @@ NS_IMETHODIMP nsDOMStorage::RemoveItem(const nsAString& aKey)
return NS_OK;
}
PR_STATIC_CALLBACK(PLDHashOperator)
CheckSecure(nsSessionStorageEntry* aEntry, void* userArg)
{
PRBool* secure = (PRBool*)userArg;
*secure |= aEntry->mItem->IsSecure();
return PL_DHASH_NEXT;
}
nsresult
nsDOMStorage::Clear()
{
if (!CacheStoragePermissions())
return NS_ERROR_DOM_SECURITY_ERR;
if (UseDB())
CacheKeysFromDB();
PRBool foundSecureItem = PR_FALSE;
mItems.EnumerateEntries(CheckSecure, &foundSecureItem);
if (foundSecureItem && !IsCallerSecure()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
#ifdef MOZ_STORAGE
if (UseDB()) {
nsresult rv = InitDB();
NS_ENSURE_SUCCESS(rv, rv);
rv = gStorageDB->ClearStorage(this);
NS_ENSURE_SUCCESS(rv, rv);
}
#endif
mItems.Clear();
BroadcastChangeNotification();
return NS_OK;
}
nsresult
nsDOMStorage::InitDB()
{
@ -876,7 +1030,9 @@ nsDOMStorage::CacheKeysFromDB()
nsresult rv = InitDB();
NS_ENSURE_SUCCESS(rv, rv);
rv = gStorageDB->GetAllKeys(mDomain, this, &mItems);
mItems.Clear();
rv = gStorageDB->GetAllKeys(this, &mItems);
NS_ENSURE_SUCCESS(rv, rv);
mItemsCached = PR_TRUE;
@ -888,7 +1044,7 @@ nsDOMStorage::CacheKeysFromDB()
nsresult
nsDOMStorage::GetDBValue(const nsAString& aKey, nsAString& aValue,
PRBool* aSecure, nsAString& aOwner)
PRBool* aSecure)
{
aValue.Truncate();
@ -900,7 +1056,12 @@ nsDOMStorage::GetDBValue(const nsAString& aKey, nsAString& aValue,
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString value;
rv = gStorageDB->GetKeyValue(mDomain, aKey, value, aSecure, aOwner);
rv = gStorageDB->GetKeyValue(this, aKey, value, aSecure);
if (rv == NS_ERROR_DOM_NOT_FOUND_ERR && mLocalStorage) {
SetDOMStringToNull(aValue);
}
if (NS_FAILED(rv))
return rv;
@ -934,7 +1095,7 @@ nsDOMStorage::SetDBValue(const nsAString& aKey,
nsCOMPtr<nsIPrincipal> subjectPrincipal;
ssm->GetSubjectPrincipal(getter_AddRefs(subjectPrincipal));
nsAutoString currentDomain;
nsCAutoString currentDomain;
if (subjectPrincipal) {
nsCOMPtr<nsIURI> unused;
@ -963,8 +1124,7 @@ nsDOMStorage::SetDBValue(const nsAString& aKey,
GetQuota(currentDomain, &quota, &warnQuota);
PRInt32 usage;
rv = gStorageDB->SetKey(mDomain, aKey, aValue, aSecure,
currentDomain, quota, &usage);
rv = gStorageDB->SetKey(this, aKey, aValue, aSecure, quota, &usage);
NS_ENSURE_SUCCESS(rv, rv);
mItemsCached = PR_FALSE;
@ -986,7 +1146,7 @@ nsDOMStorage::SetDBValue(const nsAString& aKey,
nsCOMPtr<nsIObserverService> os =
do_GetService("@mozilla.org/observer-service;1");
os->NotifyObservers(window, "dom-storage-warn-quota-exceeded",
currentDomain.get());
NS_ConvertUTF8toUTF16(currentDomain).get());
}
BroadcastChangeNotification();
@ -1003,7 +1163,7 @@ nsDOMStorage::SetSecure(const nsAString& aKey, PRBool aSecure)
nsresult rv = InitDB();
NS_ENSURE_SUCCESS(rv, rv);
return gStorageDB->SetSecure(mDomain, aKey, aSecure);
return gStorageDB->SetSecure(this, aKey, aSecure);
}
#else
return NS_ERROR_NOT_IMPLEMENTED;
@ -1051,12 +1211,12 @@ already_AddRefed<nsIDOMStorage>
nsDOMStorage::Clone()
{
if (UseDB()) {
NS_ERROR("Uh, don't clone a global storage object.");
NS_ERROR("Uh, don't clone a global or local storage object.");
return nsnull;
}
nsDOMStorage* storage = new nsDOMStorage(mDomain, PR_FALSE);
nsDOMStorage* storage = new nsDOMStorage(*this);
if (!storage)
return nsnull;
@ -1064,6 +1224,9 @@ nsDOMStorage::Clone()
NS_ADDREF(storage);
if (nsDOMStorageManager::gStorageManager)
nsDOMStorageManager::gStorageManager->AddToStoragesHash(storage);
return storage;
}
@ -1099,16 +1262,15 @@ nsDOMStorage::GetKeys()
return keystruct.keys;
}
const nsString &
const nsCString &
nsDOMStorage::Domain()
{
return mDomain;
}
PRBool
nsDOMStorage::CanAccess(nsIPrincipal *aPrincipal)
nsDOMStorage::CanAccessSystem(nsIPrincipal *aPrincipal)
{
// Allow C++/system callers to access the storage
if (!aPrincipal)
return PR_TRUE;
@ -1120,7 +1282,17 @@ nsDOMStorage::CanAccess(nsIPrincipal *aPrincipal)
if (NS_SUCCEEDED(ssm->IsSystemPrincipal(aPrincipal, &isSystem) && isSystem))
return PR_TRUE;
nsAutoString domain;
return PR_FALSE;
}
PRBool
nsDOMStorage::CanAccess(nsIPrincipal *aPrincipal)
{
// Allow C++/system callers to access the storage
if (CanAccessSystem(aPrincipal))
return PR_TRUE;
nsCAutoString domain;
nsCOMPtr<nsIURI> unused;
nsresult rv = GetPrincipalURIAndHost(aPrincipal,
getter_AddRefs(unused), domain);
@ -1144,7 +1316,129 @@ nsDOMStorage::BroadcastChangeNotification()
// domain, but if it's a global storage object we do.
observerService->NotifyObservers((nsIDOMStorage *)this,
"dom-storage-changed",
UseDB() ? mDomain.get() : nsnull);
UseDB() ? NS_ConvertUTF8toUTF16(mDomain).get() : nsnull);
}
//
// nsDOMStorage2
//
NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMStorage2)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMStorage2)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mStorage)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMStorage2)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mStorage, nsIDOMStorage)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTING_ADDREF_AMBIGUOUS(nsDOMStorage2, nsIDOMStorage2)
NS_IMPL_CYCLE_COLLECTING_RELEASE_AMBIGUOUS(nsDOMStorage2, nsIDOMStorage2)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMStorage2)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMStorage2)
NS_INTERFACE_MAP_ENTRY(nsIDOMStorage2)
NS_INTERFACE_MAP_ENTRY(nsPIDOMStorage)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Storage2)
NS_INTERFACE_MAP_END
nsresult
nsDOMStorage2::InitAsLocalStorage(nsIPrincipal *aPrincipal)
{
mStorage = new nsDOMStorage();
if (!mStorage)
return NS_ERROR_OUT_OF_MEMORY;
mPrincipal = aPrincipal;
return mStorage->InitAsLocalStorage(aPrincipal);
}
nsresult
nsDOMStorage2::InitAsGlobalStorage(const nsACString &aDomainDemanded)
{
NS_ASSERTION(PR_FALSE, "Should not initialize nsDOMStorage2 as global storage.");
return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult
nsDOMStorage2::InitAsSessionStorage(nsIURI* aURI)
{
mStorage = new nsDOMStorage();
if (!mStorage)
return NS_ERROR_OUT_OF_MEMORY;
return mStorage->InitAsSessionStorage(aURI);
}
already_AddRefed<nsIDOMStorage>
nsDOMStorage2::Clone()
{
// XXX: this will need to be fixed before sessionStorage is moved
// to nsIDOMStorage2.
NS_ASSERTION(PR_FALSE, "Cannot clone nsDOMStorage2");
return nsnull;
}
nsTArray<nsString> *
nsDOMStorage2::GetKeys()
{
return mStorage->GetKeys();
}
const nsCString &
nsDOMStorage2::Domain()
{
return mStorage->Domain();
}
PRBool
nsDOMStorage2::CanAccess(nsIPrincipal *aPrincipal)
{
// Allow C++ callers to access the storage
if (!aPrincipal)
return PR_TRUE;
// Allow system and all weaker principals access the storage
PRBool subsumes;
nsresult rv = aPrincipal->Subsumes(mPrincipal, &subsumes);
if (NS_FAILED(rv))
return PR_FALSE;
return subsumes;
}
NS_IMETHODIMP
nsDOMStorage2::GetLength(PRUint32 *aLength)
{
return mStorage->GetLength(aLength);
}
NS_IMETHODIMP
nsDOMStorage2::Key(PRUint32 aIndex, nsAString& aKey)
{
return mStorage->Key(aIndex, aKey);
}
NS_IMETHODIMP
nsDOMStorage2::GetItem(const nsAString& aKey, nsAString &aData)
{
return mStorage->GetItem(aKey, aData);
}
NS_IMETHODIMP
nsDOMStorage2::SetItem(const nsAString& aKey, const nsAString& aData)
{
return mStorage->SetItem(aKey, aData);
}
NS_IMETHODIMP
nsDOMStorage2::RemoveItem(const nsAString& aKey)
{
return mStorage->RemoveItem(aKey);
}
NS_IMETHODIMP
nsDOMStorage2::Clear()
{
return mStorage->Clear();
}
//
@ -1189,7 +1483,7 @@ nsDOMStorageList::GetNamedItem(const nsAString& aDomain, nsresult* aResult)
*aResult = ssm->GetSubjectPrincipal(getter_AddRefs(subjectPrincipal));
NS_ENSURE_SUCCESS(*aResult, nsnull);
nsAutoString currentDomain;
nsCAutoString currentDomain;
if (subjectPrincipal) {
nsCOMPtr<nsIURI> unused;
*aResult = GetPrincipalURIAndHost(subjectPrincipal, getter_AddRefs(unused),
@ -1209,7 +1503,7 @@ nsDOMStorageList::GetNamedItem(const nsAString& aDomain, nsresult* aResult)
return nsnull;
}
return GetStorageForDomain(NS_ConvertUTF8toUTF16(requestedDomain),
return GetStorageForDomain(requestedDomain,
currentDomain, isSystem, aResult);
}
@ -1224,19 +1518,19 @@ nsDOMStorageList::NamedItem(const nsAString& aDomain,
// static
PRBool
nsDOMStorageList::CanAccessDomain(const nsAString& aRequestedDomain,
const nsAString& aCurrentDomain)
nsDOMStorageList::CanAccessDomain(const nsACString& aRequestedDomain,
const nsACString& aCurrentDomain)
{
return aRequestedDomain.Equals(aCurrentDomain);
}
nsIDOMStorage*
nsDOMStorageList::GetStorageForDomain(const nsAString& aRequestedDomain,
const nsAString& aCurrentDomain,
nsDOMStorageList::GetStorageForDomain(const nsACString& aRequestedDomain,
const nsACString& aCurrentDomain,
PRBool aNoCurrentDomainCheck,
nsresult* aResult)
{
nsTArray<nsString> requestedDomainArray;
nsTArray<nsCString> requestedDomainArray;
if ((!aNoCurrentDomainCheck &&
!CanAccessDomain(aRequestedDomain, aCurrentDomain)) ||
!ConvertDomainToArray(aRequestedDomain, &requestedDomainArray)) {
@ -1246,12 +1540,12 @@ nsDOMStorageList::GetStorageForDomain(const nsAString& aRequestedDomain,
}
// now rebuild a string for the domain.
nsAutoString usedDomain;
nsCAutoString usedDomain;
PRUint32 requestedPos = 0;
for (requestedPos = 0; requestedPos < requestedDomainArray.Length();
requestedPos++) {
if (!usedDomain.IsEmpty())
usedDomain.AppendLiteral(".");
usedDomain.Append('.');
usedDomain.Append(requestedDomainArray[requestedPos]);
}
@ -1260,11 +1554,19 @@ nsDOMStorageList::GetStorageForDomain(const nsAString& aRequestedDomain,
// now have a valid domain, so look it up in the storage table
nsIDOMStorage* storage = mStorages.GetWeak(usedDomain);
if (!storage) {
nsCOMPtr<nsIDOMStorage> newstorage = new nsDOMStorage(usedDomain, PR_TRUE);
if (newstorage && mStorages.Put(usedDomain, newstorage))
nsRefPtr<nsDOMStorage> newstorage;
newstorage = new nsDOMStorage();
if (newstorage && mStorages.Put(usedDomain, newstorage)) {
*aResult = newstorage->InitAsGlobalStorage(usedDomain);
if (NS_FAILED(*aResult)) {
mStorages.Remove(usedDomain);
return nsnull;
}
storage = newstorage;
else
}
else {
*aResult = NS_ERROR_OUT_OF_MEMORY;
}
}
return storage;
@ -1272,14 +1574,14 @@ nsDOMStorageList::GetStorageForDomain(const nsAString& aRequestedDomain,
// static
PRBool
nsDOMStorageList::ConvertDomainToArray(const nsAString& aDomain,
nsTArray<nsString> *aArray)
nsDOMStorageList::ConvertDomainToArray(const nsACString& aDomain,
nsTArray<nsCString> *aArray)
{
PRInt32 length = aDomain.Length();
PRInt32 n = 0;
while (n < length) {
PRInt32 dotpos = aDomain.FindChar('.', n);
nsAutoString domain;
nsCAutoString domain;
if (dotpos == -1) // no more dots
domain.Assign(Substring(aDomain, n));
@ -1361,8 +1663,7 @@ nsDOMStorageItem::GetSecure(PRBool* aSecure)
if (mStorage->UseDB()) {
nsAutoString value;
nsAutoString owner;
return mStorage->GetDBValue(mKey, value, aSecure, owner);
return mStorage->GetDBValue(mKey, value, aSecure);
}
*aSecure = IsSecure();
@ -1394,9 +1695,10 @@ nsDOMStorageItem::GetValue(nsAString& aValue)
if (mStorage->UseDB()) {
// GetDBValue checks the secure state so no need to do it here
PRBool secure;
nsAutoString unused;
nsresult rv = mStorage->GetDBValue(mKey, aValue, &secure, unused);
return (rv == NS_ERROR_DOM_NOT_FOUND_ERR) ? NS_OK : rv;
nsresult rv = mStorage->GetDBValue(mKey, aValue, &secure);
if (rv == NS_ERROR_DOM_NOT_FOUND_ERR)
return NS_OK;
return rv;
}
if (IsSecure() && !IsCallerSecure()) {

View File

@ -44,6 +44,7 @@
#include "nscore.h"
#include "nsAutoPtr.h"
#include "nsIDOMStorage.h"
#include "nsIDOMStorage2.h"
#include "nsIDOMStorageList.h"
#include "nsIDOMStorageItem.h"
#include "nsInterfaceHashtable.h"
@ -61,6 +62,7 @@
#endif
class nsDOMStorage;
class nsIDOMStorage2;
class nsDOMStorageItem;
class nsDOMStorageEntry : public nsVoidPtrHashKey
@ -123,7 +125,7 @@ class nsDOMStorage : public nsIDOMStorage,
{
public:
nsDOMStorage();
nsDOMStorage(const nsAString& aDomain, PRBool aUseDB);
nsDOMStorage(nsDOMStorage& aThat);
virtual ~nsDOMStorage();
// nsISupports
@ -133,11 +135,17 @@ public:
// nsIDOMStorage
NS_DECL_NSIDOMSTORAGE
// Helpers for implementing nsIDOMStorage2
nsresult GetItem(const nsAString& key, nsAString& aData);
nsresult Clear();
// nsPIDOMStorage
virtual void Init(const nsAString& aDomain, PRBool aUseDB);
virtual nsresult InitAsLocalStorage(nsIPrincipal *aPrincipal);
virtual nsresult InitAsGlobalStorage(const nsACString &aDomainDemanded);
virtual nsresult InitAsSessionStorage(nsIURI* aURI);
virtual already_AddRefed<nsIDOMStorage> Clone();
virtual nsTArray<nsString> *GetKeys();
virtual const nsString &Domain();
virtual const nsCString &Domain();
virtual PRBool CanAccess(nsIPrincipal *aPrincipal);
// If true, the contents of the storage should be stored in the
@ -165,8 +173,7 @@ public:
nsresult
GetDBValue(const nsAString& aKey,
nsAString& aValue,
PRBool* aSecure,
nsAString& aOwner);
PRBool* aSecure);
// set the value corresponding to a key in the storage. If
// aSecure is false, then attempts to modify a secure value
@ -193,6 +200,7 @@ public:
protected:
friend class nsDOMStorageManager;
friend class nsDOMStorage2;
static nsresult InitDB();
@ -201,6 +209,8 @@ protected:
void BroadcastChangeNotification();
PRBool CanAccessSystem(nsIPrincipal *aPrincipal);
// true if the storage database should be used for values
PRPackedBool mUseDB;
@ -211,18 +221,66 @@ protected:
// make sure this stays up to date.
PRPackedBool mSessionOnly;
// true if this storage was initialized as a localStorage object. localStorage
// objects are scoped to scheme/host/port in the database, while globalStorage
// objects are scoped just to host. this flag also tells the manager to map
// this storage also in mLocalStorages hash table.
PRPackedBool mLocalStorage;
// true if items from the database are cached
PRPackedBool mItemsCached;
// domain this store is associated with
nsString mDomain;
nsCString mDomain;
// the key->value item pairs
nsTHashtable<nsSessionStorageEntry> mItems;
#ifdef MOZ_STORAGE
static nsDOMStorageDB* gStorageDB;
#endif
// keys are used for database queries.
// see comments of the getters bellow.
nsCString mScopeDBKey;
nsCString mQuotaDomainDBKey;
public:
// e.g. "moc.rab.oof.:" or "moc.rab.oof.:http:80" depending
// on association with a domain (globalStorage) or
// an origin (localStorage).
nsCString& GetScopeDBKey() {return mScopeDBKey;}
// e.g. "moc.rab.%" - reversed eTLD+1 subpart of the domain or
// (in future) reversed offline application allowed domain.
nsCString& GetQuotaDomainDBKey() {return mQuotaDomainDBKey;}
#ifdef MOZ_STORAGE
static nsDOMStorageDB* gStorageDB;
#endif
};
class nsDOMStorage2 : public nsIDOMStorage2,
public nsPIDOMStorage
{
public:
// nsISupports
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsDOMStorage2, nsIDOMStorage2)
NS_DECL_NSIDOMSTORAGE2
// nsPIDOMStorage
virtual nsresult InitAsLocalStorage(nsIPrincipal *aPrincipal);
virtual nsresult InitAsGlobalStorage(const nsACString &aDomainDemanded);
virtual nsresult InitAsSessionStorage(nsIURI* aURI);
virtual already_AddRefed<nsIDOMStorage> Clone();
virtual nsTArray<nsString> *GetKeys();
virtual const nsCString &Domain();
virtual PRBool CanAccess(nsIPrincipal *aPrincipal);
private:
// storages bound to an origin hold the principal to
// make security checks against it
nsCOMPtr<nsIPrincipal> mPrincipal;
nsRefPtr<nsDOMStorage> mStorage;
};
class nsDOMStorageList : public nsIDOMStorageList
@ -247,8 +305,8 @@ public:
* Check whether aCurrentDomain has access to aRequestedDomain
*/
static PRBool
CanAccessDomain(const nsAString& aRequestedDomain,
const nsAString& aCurrentDomain);
CanAccessDomain(const nsACString& aRequestedDomain,
const nsACString& aCurrentDomain);
protected:
@ -263,8 +321,8 @@ protected:
* @param aNoCurrentDomainCheck true to skip domain comparison
*/
nsIDOMStorage*
GetStorageForDomain(const nsAString& aRequestedDomain,
const nsAString& aCurrentDomain,
GetStorageForDomain(const nsACString& aRequestedDomain,
const nsACString& aCurrentDomain,
PRBool aNoCurrentDomainCheck,
nsresult* aResult);
@ -272,10 +330,10 @@ protected:
* Convert the domain into an array of its component parts.
*/
static PRBool
ConvertDomainToArray(const nsAString& aDomain,
nsTArray<nsString>* aArray);
ConvertDomainToArray(const nsACString& aDomain,
nsTArray<nsCString>* aArray);
nsInterfaceHashtable<nsStringHashKey, nsIDOMStorage> mStorages;
nsInterfaceHashtable<nsCStringHashKey, nsIDOMStorage> mStorages;
};
class nsDOMStorageItem : public nsIDOMStorageItem,

View File

@ -41,11 +41,66 @@
#include "nsDOMStorage.h"
#include "nsDOMStorageDB.h"
#include "nsIFile.h"
#include "nsIVariant.h"
#include "nsIEffectiveTLDService.h"
#include "nsAppDirectoryServiceDefs.h"
#include "mozStorageCID.h"
#include "mozStorageHelper.h"
#include "mozIStorageService.h"
#include "mozIStorageValueArray.h"
#include "mozIStorageFunction.h"
#include "nsPrintfCString.h"
#include "nsNetUtil.h"
static
void ReverseString(const nsCSubstring& source, nsCSubstring& result)
{
nsACString::const_iterator sourceBegin, sourceEnd;
source.BeginReading(sourceBegin);
source.EndReading(sourceEnd);
result.SetLength(source.Length());
nsACString::iterator destEnd;
result.EndWriting(destEnd);
while (sourceBegin != sourceEnd) {
*(--destEnd) = *sourceBegin;
++sourceBegin;
}
}
class nsReverseStringSQLFunction : public mozIStorageFunction
{
NS_DECL_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
};
NS_IMPL_ISUPPORTS1(nsReverseStringSQLFunction, mozIStorageFunction)
NS_IMETHODIMP
nsReverseStringSQLFunction::OnFunctionCall(
mozIStorageValueArray *aFunctionArguments, nsIVariant **aResult)
{
nsresult rv;
nsCAutoString stringToReverse;
rv = aFunctionArguments->GetUTF8String(0, stringToReverse);
NS_ENSURE_SUCCESS(rv, rv);
nsCAutoString result;
ReverseString(stringToReverse, result);
nsCOMPtr<nsIWritableVariant> outVar(do_CreateInstance(
NS_VARIANT_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
rv = outVar->SetAsAUTF8String(result);
NS_ENSURE_SUCCESS(rv, rv);
*aResult = outVar.get();
outVar.forget();
return NS_OK;
}
nsresult
nsDOMStorageDB::Init()
@ -72,134 +127,156 @@ nsDOMStorageDB::Init()
}
NS_ENSURE_SUCCESS(rv, rv);
PRBool exists;
rv = mConnection->TableExists(NS_LITERAL_CSTRING("webappsstore"), &exists);
// 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)"));
NS_ENSURE_SUCCESS(rv, rv);
if (! exists) {
rv = mConnection->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("CREATE TABLE webappsstore ("
"domain TEXT, "
"key TEXT, "
"value TEXT, "
"secure INTEGER, "
"owner TEXT)"));
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);
nsCOMPtr<mozIStorageFunction> function(new nsReverseStringSQLFunction());
NS_ENSURE_TRUE(function, NS_ERROR_OUT_OF_MEMORY);
rv = mConnection->CreateFunction(NS_LITERAL_CSTRING("REVERSESTRING"), 1, function);
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);
if (exists) {
rv = mConnection->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
"webappsstore2(scope, key, value, secure, owner) "
"SELECT REVERSESTRING(domain) || '.:', key, value, secure, owner "
"FROM webappsstore"));
NS_ENSURE_SUCCESS(rv, rv);
rv = mConnection->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("DROP TABLE webappsstore"));
NS_ENSURE_SUCCESS(rv, rv);
}
// Check if there is storage of Gecko 1.8 and if so, upgrade that storage
// to actual webappsstore2 table and drop the obsolete table. Potential
// duplicates will be ignored.
rv = mConnection->TableExists(NS_LITERAL_CSTRING("moz_webappsstore"),
&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (exists) {
// upgrade an old store
// create a temporary index to handle dup checking
rv = mConnection->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("CREATE UNIQUE INDEX webappsstore_tmp "
" ON webappsstore(domain, key)"));
rv = mConnection->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
"webappsstore2(scope, key, value, secure, owner) "
"SELECT REVERSESTRING(domain) || '.:', key, value, secure, domain "
"FROM moz_webappsstore"));
NS_ENSURE_SUCCESS(rv, rv);
// if the index can't be created, there are dup domain/key combos
// in moz_webappstore2, which indicates a bug elsewhere. Fail to upgrade
// in this case
if (NS_SUCCEEDED(rv)) {
rv = mConnection->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
"webappsstore(domain, key, value, secure, owner) "
"SELECT domain, key, value, secure, domain "
"FROM moz_webappsstore"));
// try to drop the index even in case of an error
mConnection->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("DROP INDEX webappsstore_tmp"));
NS_ENSURE_SUCCESS(rv, rv);
rv = mConnection->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("DROP TABLE moz_webappsstore"));
NS_ENSURE_SUCCESS(rv, rv);
}
rv = mConnection->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("DROP TABLE moz_webappsstore"));
NS_ENSURE_SUCCESS(rv, rv);
}
// retrieve all keys associated with a domain
rv = mConnection->CreateStatement(
NS_LITERAL_CSTRING("SELECT key, secure FROM webappsstore "
"WHERE domain = ?1"),
NS_LITERAL_CSTRING("SELECT key, secure FROM webappsstore2 "
"WHERE scope = ?1"),
getter_AddRefs(mGetAllKeysStatement));
NS_ENSURE_SUCCESS(rv, rv);
// retrieve a value given a domain and a key
rv = mConnection->CreateStatement(
NS_LITERAL_CSTRING("SELECT value, secure, owner FROM webappsstore "
"WHERE domain = ?1 "
NS_LITERAL_CSTRING("SELECT value, secure FROM webappsstore2 "
"WHERE scope = ?1 "
"AND key = ?2"),
getter_AddRefs(mGetKeyValueStatement));
NS_ENSURE_SUCCESS(rv, rv);
// insert a new key
rv = mConnection->CreateStatement(
NS_LITERAL_CSTRING("INSERT INTO "
"webappsstore(domain, key, value, secure, owner) "
"VALUES (?1, ?2, ?3, ?4, ?5)"),
NS_LITERAL_CSTRING("INSERT OR REPLACE INTO "
"webappsstore2(scope, key, value, secure) "
"VALUES (?1, ?2, ?3, ?4)"),
getter_AddRefs(mInsertKeyStatement));
NS_ENSURE_SUCCESS(rv, rv);
// update an existing key
rv = mConnection->CreateStatement(
NS_LITERAL_CSTRING("UPDATE webappsstore "
"SET value = ?1, secure = ?2, owner = ?3"
"WHERE domain = ?4 "
"AND key = ?5 "),
NS_LITERAL_CSTRING("UPDATE webappsstore2 "
"SET value = ?1, secure = ?2"
"WHERE scope = ?3 "
"AND key = ?4"),
getter_AddRefs(mUpdateKeyStatement));
NS_ENSURE_SUCCESS(rv, rv);
// update the secure status of an existing key
rv = mConnection->CreateStatement(
NS_LITERAL_CSTRING("UPDATE webappsstore "
NS_LITERAL_CSTRING("UPDATE webappsstore2 "
"SET secure = ?1 "
"WHERE domain = ?2 "
"WHERE scope = ?2 "
"AND key = ?3 "),
getter_AddRefs(mSetSecureStatement));
NS_ENSURE_SUCCESS(rv, rv);
// remove a key
rv = mConnection->CreateStatement(
NS_LITERAL_CSTRING("DELETE FROM webappsstore "
"WHERE domain = ?1 "
NS_LITERAL_CSTRING("DELETE FROM webappsstore2 "
"WHERE scope = ?1 "
"AND key = ?2"),
getter_AddRefs(mRemoveKeyStatement));
NS_ENSURE_SUCCESS(rv, rv);
// remove keys owned by a specific domain
rv = mConnection->CreateStatement(
NS_LITERAL_CSTRING("DELETE FROM webappsstore "
"WHERE owner = ?1"),
NS_LITERAL_CSTRING("DELETE FROM webappsstore2 "
"WHERE scope GLOB ?1"),
getter_AddRefs(mRemoveOwnerStatement));
NS_ENSURE_SUCCESS(rv, rv);
// remove keys belonging exactly only to a specific domain
rv = mConnection->CreateStatement(
NS_LITERAL_CSTRING("DELETE FROM webappsstore2 "
"WHERE scope = ?1"),
getter_AddRefs(mRemoveStorageStatement));
NS_ENSURE_SUCCESS(rv, rv);
// remove all keys
rv = mConnection->CreateStatement(
NS_LITERAL_CSTRING("DELETE FROM webappsstore"),
NS_LITERAL_CSTRING("DELETE FROM webappsstore2"),
getter_AddRefs(mRemoveAllStatement));
NS_ENSURE_SUCCESS(rv, rv);
// check the usage for a given owner
rv = mConnection->CreateStatement(
NS_LITERAL_CSTRING("SELECT SUM(LENGTH(key) + LENGTH(value)) "
"FROM webappsstore "
"WHERE owner = ?1"),
"FROM webappsstore2 "
"WHERE scope GLOB ?1"),
getter_AddRefs(mGetUsageStatement));
return rv;
}
nsresult
nsDOMStorageDB::GetAllKeys(const nsAString& aDomain,
nsDOMStorage* aStorage,
nsDOMStorageDB::GetAllKeys(nsDOMStorage* aStorage,
nsTHashtable<nsSessionStorageEntry>* aKeys)
{
mozStorageStatementScoper scope(mGetAllKeysStatement);
nsresult rv = mGetAllKeysStatement->BindStringParameter(0, aDomain);
nsresult rv = mGetAllKeysStatement->BindUTF8StringParameter(0, aStorage->GetScopeDBKey());
NS_ENSURE_SUCCESS(rv, rv);
PRBool exists;
@ -228,15 +305,15 @@ nsDOMStorageDB::GetAllKeys(const nsAString& aDomain,
}
nsresult
nsDOMStorageDB::GetKeyValue(const nsAString& aDomain,
nsDOMStorageDB::GetKeyValue(nsDOMStorage* aStorage,
const nsAString& aKey,
nsAString& aValue,
PRBool* aSecure,
nsAString& aOwner)
PRBool* aSecure)
{
mozStorageStatementScoper scope(mGetKeyValueStatement);
nsresult rv = mGetKeyValueStatement->BindStringParameter(0, aDomain);
nsresult rv = mGetKeyValueStatement->BindUTF8StringParameter(
0, aStorage->GetScopeDBKey());
NS_ENSURE_SUCCESS(rv, rv);
rv = mGetKeyValueStatement->BindStringParameter(1, aKey);
NS_ENSURE_SUCCESS(rv, rv);
@ -252,9 +329,6 @@ nsDOMStorageDB::GetKeyValue(const nsAString& aDomain,
rv = mGetKeyValueStatement->GetInt32(1, &secureInt);
NS_ENSURE_SUCCESS(rv, rv);
rv = mGetKeyValueStatement->GetString(2, aOwner);
NS_ENSURE_SUCCESS(rv, rv);
}
else {
rv = NS_ERROR_DOM_NOT_FOUND_ERR;
@ -266,11 +340,10 @@ nsDOMStorageDB::GetKeyValue(const nsAString& aDomain,
}
nsresult
nsDOMStorageDB::SetKey(const nsAString& aDomain,
nsDOMStorageDB::SetKey(nsDOMStorage* aStorage,
const nsAString& aKey,
const nsAString& aValue,
PRBool aSecure,
const nsAString& aOwner,
PRInt32 aQuota,
PRInt32 *aNewUsage)
{
@ -278,14 +351,15 @@ nsDOMStorageDB::SetKey(const nsAString& aDomain,
PRInt32 usage = 0;
nsresult rv;
if (!aOwner.IsEmpty()) {
rv = GetUsage(aOwner, &usage);
if (!aStorage->GetQuotaDomainDBKey().IsEmpty()) {
rv = GetUsage(aStorage, &usage);
NS_ENSURE_SUCCESS(rv, rv);
}
usage += aKey.Length() + aValue.Length();
rv = mGetKeyValueStatement->BindStringParameter(0, aDomain);
rv = mGetKeyValueStatement->BindUTF8StringParameter(0,
aStorage->GetScopeDBKey());
NS_ENSURE_SUCCESS(rv, rv);
rv = mGetKeyValueStatement->BindStringParameter(1, aKey);
NS_ENSURE_SUCCESS(rv, rv);
@ -303,16 +377,11 @@ nsDOMStorageDB::SetKey(const nsAString& aDomain,
return NS_ERROR_DOM_SECURITY_ERR;
}
nsAutoString previousOwner;
rv = mGetKeyValueStatement->GetString(2, previousOwner);
nsAutoString previousValue;
rv = mGetKeyValueStatement->GetString(0, previousValue);
NS_ENSURE_SUCCESS(rv, rv);
if (previousOwner == aOwner) {
nsAutoString previousValue;
rv = mGetKeyValueStatement->GetString(0, previousValue);
NS_ENSURE_SUCCESS(rv, rv);
usage -= aKey.Length() + previousValue.Length();
}
usage -= aKey.Length() + previousValue.Length();
mGetKeyValueStatement->Reset();
@ -326,11 +395,10 @@ nsDOMStorageDB::SetKey(const nsAString& aDomain,
NS_ENSURE_SUCCESS(rv, rv);
rv = mUpdateKeyStatement->BindInt32Parameter(1, aSecure);
NS_ENSURE_SUCCESS(rv, rv);
rv = mUpdateKeyStatement->BindStringParameter(2, aOwner);
rv = mUpdateKeyStatement->BindUTF8StringParameter(2,
aStorage->GetScopeDBKey());
NS_ENSURE_SUCCESS(rv, rv);
rv = mUpdateKeyStatement->BindStringParameter(3, aDomain);
NS_ENSURE_SUCCESS(rv, rv);
rv = mUpdateKeyStatement->BindStringParameter(4, aKey);
rv = mUpdateKeyStatement->BindStringParameter(3, aKey);
NS_ENSURE_SUCCESS(rv, rv);
rv = mUpdateKeyStatement->Execute();
@ -343,7 +411,8 @@ nsDOMStorageDB::SetKey(const nsAString& aDomain,
mozStorageStatementScoper scopeinsert(mInsertKeyStatement);
rv = mInsertKeyStatement->BindStringParameter(0, aDomain);
rv = mInsertKeyStatement->BindUTF8StringParameter(0,
aStorage->GetScopeDBKey());
NS_ENSURE_SUCCESS(rv, rv);
rv = mInsertKeyStatement->BindStringParameter(1, aKey);
NS_ENSURE_SUCCESS(rv, rv);
@ -351,15 +420,13 @@ nsDOMStorageDB::SetKey(const nsAString& aDomain,
NS_ENSURE_SUCCESS(rv, rv);
rv = mInsertKeyStatement->BindInt32Parameter(3, aSecure);
NS_ENSURE_SUCCESS(rv, rv);
rv = mInsertKeyStatement->BindStringParameter(4, aOwner);
NS_ENSURE_SUCCESS(rv, rv);
rv = mInsertKeyStatement->Execute();
NS_ENSURE_SUCCESS(rv, rv);
}
if (!aOwner.IsEmpty()) {
mCachedOwner = aOwner;
if (!aStorage->GetQuotaDomainDBKey().IsEmpty()) {
mCachedOwner = aStorage->GetQuotaDomainDBKey();
mCachedUsage = usage;
}
@ -369,52 +436,37 @@ nsDOMStorageDB::SetKey(const nsAString& aDomain,
}
nsresult
nsDOMStorageDB::SetSecure(const nsAString& aDomain,
nsDOMStorageDB::SetSecure(nsDOMStorage* aStorage,
const nsAString& aKey,
const PRBool aSecure)
{
mozStorageStatementScoper scope(mGetKeyValueStatement);
nsresult rv;
nsresult rv = mGetKeyValueStatement->BindStringParameter(0, aDomain);
mozStorageStatementScoper scope(mSetSecureStatement);
rv = mSetSecureStatement->BindInt32Parameter(0, aSecure ? 1 : 0);
NS_ENSURE_SUCCESS(rv, rv);
rv = mGetKeyValueStatement->BindStringParameter(1, aKey);
rv = mSetSecureStatement->BindUTF8StringParameter(1, aStorage->GetScopeDBKey());
NS_ENSURE_SUCCESS(rv, rv);
rv = mSetSecureStatement->BindStringParameter(2, aKey);
NS_ENSURE_SUCCESS(rv, rv);
PRBool exists;
rv = mGetKeyValueStatement->ExecuteStep(&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (exists) {
mGetKeyValueStatement->Reset();
mozStorageStatementScoper scopeupdate(mUpdateKeyStatement);
rv = mSetSecureStatement->BindInt32Parameter(0, aSecure ? 1 : 0);
NS_ENSURE_SUCCESS(rv, rv);
rv = mSetSecureStatement->BindStringParameter(1, aDomain);
NS_ENSURE_SUCCESS(rv, rv);
rv = mSetSecureStatement->BindStringParameter(2, aKey);
NS_ENSURE_SUCCESS(rv, rv);
return mSetSecureStatement->Execute();
}
return NS_OK;
return mSetSecureStatement->Execute();
}
nsresult
nsDOMStorageDB::RemoveKey(const nsAString& aDomain,
nsDOMStorageDB::RemoveKey(nsDOMStorage* aStorage,
const nsAString& aKey,
const nsAString& aOwner,
PRInt32 aKeyUsage)
{
mozStorageStatementScoper scope(mRemoveKeyStatement);
if (aOwner == mCachedOwner) {
if (aStorage->GetQuotaDomainDBKey() == mCachedOwner) {
mCachedUsage -= aKeyUsage;
}
nsresult rv = mRemoveKeyStatement->BindStringParameter(0, aDomain);
nsresult rv = mRemoveKeyStatement->BindUTF8StringParameter(
0, aStorage->GetScopeDBKey());
NS_ENSURE_SUCCESS(rv, rv);
rv = mRemoveKeyStatement->BindStringParameter(1, aKey);
NS_ENSURE_SUCCESS(rv, rv);
@ -423,16 +475,42 @@ nsDOMStorageDB::RemoveKey(const nsAString& aDomain,
}
nsresult
nsDOMStorageDB::RemoveOwner(const nsAString& aOwner)
nsDOMStorageDB::ClearStorage(nsDOMStorage* aStorage)
{
mozStorageStatementScoper scope(mRemoveStorageStatement);
mCachedUsage = 0;
mCachedOwner.Truncate();
nsresult rv;
rv = mRemoveStorageStatement->BindUTF8StringParameter(
0, aStorage->GetScopeDBKey());
NS_ENSURE_SUCCESS(rv, rv);
return mRemoveStorageStatement->Execute();
}
nsresult
nsDOMStorageDB::RemoveOwner(const nsACString& aOwner, PRBool aIncludeSubDomains)
{
mozStorageStatementScoper scope(mRemoveOwnerStatement);
if (aOwner == mCachedOwner) {
nsCAutoString subdomainsDBKey;
nsDOMStorageDB::CreateDomainScopeDBKey(aOwner, subdomainsDBKey);
if (!aIncludeSubDomains)
subdomainsDBKey.AppendLiteral(":");
subdomainsDBKey.AppendLiteral("*");
if (subdomainsDBKey == mCachedOwner) {
mCachedUsage = 0;
mCachedOwner.Truncate();
}
nsresult rv = mRemoveOwnerStatement->BindStringParameter(0, aOwner);
nsresult rv;
rv = mRemoveOwnerStatement->BindUTF8StringParameter(0, subdomainsDBKey);
NS_ENSURE_SUCCESS(rv, rv);
return mRemoveOwnerStatement->Execute();
@ -440,7 +518,8 @@ nsDOMStorageDB::RemoveOwner(const nsAString& aOwner)
nsresult
nsDOMStorageDB::RemoveOwners(const nsTArray<nsString> &aOwners, PRBool aMatch)
nsDOMStorageDB::RemoveOwners(const nsTArray<nsString> &aOwners,
PRBool aIncludeSubDomains, PRBool aMatch)
{
if (aOwners.Length() == 0) {
if (aMatch) {
@ -450,20 +529,23 @@ nsDOMStorageDB::RemoveOwners(const nsTArray<nsString> &aOwners, PRBool aMatch)
return RemoveAll();
}
nsCAutoString expression;
// Using nsString here because it is going to be very long
nsCString expression;
if (aMatch) {
expression.Assign(NS_LITERAL_CSTRING("DELETE FROM webappsstore "
"WHERE owner IN (?"));
expression.AppendLiteral("DELETE FROM webappsstore2 WHERE scope IN (");
} else {
expression.Assign(NS_LITERAL_CSTRING("DELETE FROM webappsstore "
"WHERE owner NOT IN (?"));
expression.AppendLiteral("DELETE FROM webappsstore2 WHERE scope NOT IN (");
}
for (PRUint32 i = 1; i < aOwners.Length(); i++) {
expression.Append(", ?");
for (PRUint32 i = 0; i < aOwners.Length(); i++) {
if (i)
expression.AppendLiteral(" UNION ");
expression.AppendLiteral(
"SELECT DISTINCT scope FROM webappsstore2 WHERE scope GLOB ?");
}
expression.Append(")");
expression.AppendLiteral(");");
nsCOMPtr<mozIStorageStatement> statement;
@ -472,11 +554,21 @@ nsDOMStorageDB::RemoveOwners(const nsTArray<nsString> &aOwners, PRBool aMatch)
NS_ENSURE_SUCCESS(rv, rv);
for (PRUint32 i = 0; i < aOwners.Length(); i++) {
rv = statement->BindStringParameter(i, aOwners[i]);
nsCAutoString quotaKey;
rv = nsDOMStorageDB::CreateDomainScopeDBKey(NS_ConvertUTF16toUTF8(aOwners[i]), quotaKey);
if (!aIncludeSubDomains)
quotaKey.AppendLiteral(":");
quotaKey.AppendLiteral("*");
rv = statement->BindUTF8StringParameter(i, quotaKey);
NS_ENSURE_SUCCESS(rv, rv);
}
return statement->Execute();
rv = statement->Execute();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
@ -487,16 +579,40 @@ nsDOMStorageDB::RemoveAll()
}
nsresult
nsDOMStorageDB::GetUsage(const nsAString &aOwner, PRInt32 *aUsage)
nsDOMStorageDB::GetUsage(nsDOMStorage* aStorage, PRInt32 *aUsage)
{
if (aOwner == mCachedOwner) {
return GetUsageInternal(aStorage->GetQuotaDomainDBKey(), aUsage);
}
nsresult
nsDOMStorageDB::GetUsage(const nsACString& aDomain,
PRBool aIncludeSubDomains, PRInt32 *aUsage)
{
nsresult rv;
nsCAutoString quotadomainDBKey;
rv = nsDOMStorageDB::CreateQuotaDomainDBKey(aDomain,
aIncludeSubDomains,
quotadomainDBKey);
NS_ENSURE_SUCCESS(rv, rv);
return GetUsageInternal(quotadomainDBKey, aUsage);
}
nsresult
nsDOMStorageDB::GetUsageInternal(const nsACString& aQuotaDomainDBKey,
PRInt32 *aUsage)
{
if (aQuotaDomainDBKey == mCachedOwner) {
*aUsage = mCachedUsage;
return NS_OK;
}
mozStorageStatementScoper scope(mGetUsageStatement);
nsresult rv = mGetUsageStatement->BindStringParameter(0, aOwner);
nsresult rv;
rv = mGetUsageStatement->BindUTF8StringParameter(0, aQuotaDomainDBKey);
NS_ENSURE_SUCCESS(rv, rv);
PRBool exists;
@ -511,10 +627,96 @@ nsDOMStorageDB::GetUsage(const nsAString &aOwner, PRInt32 *aUsage)
rv = mGetUsageStatement->GetInt32(0, aUsage);
NS_ENSURE_SUCCESS(rv, rv);
if (!aOwner.IsEmpty()) {
mCachedOwner = aOwner;
if (!aQuotaDomainDBKey.IsEmpty()) {
mCachedOwner = aQuotaDomainDBKey;
mCachedUsage = *aUsage;
}
return NS_OK;
}
nsresult
nsDOMStorageDB::CreateOriginScopeDBKey(nsIURI* aUri, nsACString& aKey)
{
nsresult rv;
rv = CreateDomainScopeDBKey(aUri, aKey);
if (NS_FAILED(rv))
return rv;
nsCAutoString scheme;
rv = aUri->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
aKey.AppendLiteral(":");
aKey.Append(scheme);
PRInt32 port = NS_GetRealPort(aUri);
if (port != -1) {
aKey.AppendLiteral(":");
aKey.Append(nsPrintfCString(32, "%d", port));
}
return NS_OK;
}
nsresult
nsDOMStorageDB::CreateDomainScopeDBKey(nsIURI* aUri, nsACString& aKey)
{
nsresult rv;
nsCAutoString host;
rv = aUri->GetAsciiHost(host);
NS_ENSURE_SUCCESS(rv, rv);
rv = CreateDomainScopeDBKey(host, aKey);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
nsDOMStorageDB::CreateDomainScopeDBKey(const nsACString& aAsciiDomain, nsACString& aKey)
{
if (aAsciiDomain.IsEmpty())
return NS_ERROR_NOT_AVAILABLE;
ReverseString(aAsciiDomain, aKey);
aKey.AppendLiteral(".");
return NS_OK;
}
nsresult
nsDOMStorageDB::CreateQuotaDomainDBKey(const nsACString& aAsciiDomain,
PRBool aIncludeSubDomains, nsACString& aKey)
{
nsresult rv;
nsCOMPtr<nsIEffectiveTLDService> eTLDService(do_GetService(
NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> uri;
rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("http://") + aAsciiDomain);
NS_ENSURE_SUCCESS(rv, rv);
nsCAutoString eTLDplusOne;
rv = eTLDService->GetBaseDomain(uri, 0, eTLDplusOne);
if (NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS == rv) {
// XXX bug 357323 - what to do for localhost/file exactly?
eTLDplusOne = aAsciiDomain;
rv = NS_OK;
}
NS_ENSURE_SUCCESS(rv, rv);
nsCAutoString subdomainsDBKey;
nsDOMStorageDB::CreateDomainScopeDBKey(eTLDplusOne, subdomainsDBKey);
if (!aIncludeSubDomains)
subdomainsDBKey.AppendLiteral(":");
subdomainsDBKey.AppendLiteral("*");
aKey.Assign(subdomainsDBKey);
return NS_OK;
}

View File

@ -47,6 +47,39 @@
class nsDOMStorage;
class nsSessionStorageEntry;
/**
* For the purposes of quota checking, we want to be able to efficiently
* reference data items that belong to a host or its subhosts. We do this by
* using a reversed domain name as the key for an item. For example, a
* storage for foo.bar.com would use a key of 'moc.rab.oof.".
*
* Additionally, globalStorage and localStorage items must be distinguished.
* globalStorage items are scoped to the host, and localStorage are items are
* scoped to the scheme/host/port. To scope localStorage data, its port and
* scheme are appended to its key. http://foo.bar.com is stored as
* moc.rab.foo.:http:80.
*
* So the following queries can be used, for http://foo.bar.com:
*
* All data owned by globalStorage["foo.bar.com"] -> SELECT * WHERE Domain =
* "moc.rab.foo.:"
*
* All data owned by localStorage -> SELECT * WHERE Domain =
* "moc.rab.foo.:http:80"
*
* All data owned by foo.bar.com, in any storage ->
* SELECT * WHERE Domain GLOB "moc.rab.foo.:*"
*
* All data owned by foo.bar.com or any subdomain, in any storage ->
* SELECT * WHERE Domain GLOB "moc.rab.foo.*".
*
* This key is called the "scope DB key" throughout the code. So the scope DB
* key for localStorage at http://foo.bar.com is "moc.rab.foo.:http:80".
*
* When calculating quotas, we want to lump together everything in an ETLD+1.
* So we use a "quota key" during lookups to calculate the quota. So the
* quota key for localStorage at http://foo.bar.com is "moc.rab.". */
class nsDOMStorageDB
{
public:
@ -60,8 +93,7 @@ public:
* Retrieve a list of all the keys associated with a particular domain.
*/
nsresult
GetAllKeys(const nsAString& aDomain,
nsDOMStorage* aStorage,
GetAllKeys(nsDOMStorage* aStorage,
nsTHashtable<nsSessionStorageEntry>* aKeys);
/**
@ -70,21 +102,19 @@ public:
* @throws NS_ERROR_DOM_NOT_FOUND_ERR if key not found
*/
nsresult
GetKeyValue(const nsAString& aDomain,
GetKeyValue(nsDOMStorage* aStorage,
const nsAString& aKey,
nsAString& aValue,
PRBool* aSecure,
nsAString& aOwner);
PRBool* aSecure);
/**
* Set the value and secure flag for a key in storage.
*/
nsresult
SetKey(const nsAString& aDomain,
SetKey(nsDOMStorage* aStorage,
const nsAString& aKey,
const nsAString& aValue,
PRBool aSecure,
const nsAString& aOwner,
PRInt32 aQuota,
PRInt32* aNewUsage);
@ -93,7 +123,7 @@ public:
* not found.
*/
nsresult
SetSecure(const nsAString& aDomain,
SetSecure(nsDOMStorage* aStorage,
const nsAString& aKey,
const PRBool aSecure);
@ -101,23 +131,28 @@ public:
* Removes a key from storage.
*/
nsresult
RemoveKey(const nsAString& aDomain,
RemoveKey(nsDOMStorage* aStorage,
const nsAString& aKey,
const nsAString& aOwner,
PRInt32 aKeyUsage);
/**
* Remove all keys belonging to this storage.
*/
nsresult ClearStorage(nsDOMStorage* aStorage);
/**
* Removes all keys added by a given domain.
*/
nsresult
RemoveOwner(const nsAString& aOwner);
RemoveOwner(const nsACString& aOwner, PRBool aIncludeSubDomains);
/**
* Removes keys owned by domains that either match or don't match the
* list.
*/
nsresult
RemoveOwners(const nsTArray<nsString>& aOwners, PRBool aMatch);
RemoveOwners(const nsTArray<nsString>& aOwners,
PRBool aIncludeSubDomains, PRBool aMatch);
/**
* Removes all keys from storage. Used when clearing storage.
@ -125,7 +160,39 @@ public:
nsresult
RemoveAll();
nsresult GetUsage(const nsAString &aOwner, PRInt32 *aUsage);
/**
* Returns usage for a storage using its GetQuotaDomainDBKey() as a key.
*/
nsresult
GetUsage(nsDOMStorage* aStorage, PRInt32 *aUsage);
/**
* Returns usage of the domain and optionaly by any subdomain.
*/
nsresult
GetUsage(const nsACString& aDomain, PRBool aIncludeSubDomains, PRInt32 *aUsage);
/**
* Turns "http://foo.bar.com:80" to "moc.rab.oof.:http:80",
* i.e. reverses the host, appends a dot, appends the schema
* and a port number.
*/
static nsresult CreateOriginScopeDBKey(nsIURI* aUri, nsACString& aKey);
/**
* Turns "http://foo.bar.com" to "moc.rab.oof.",
* i.e. reverses the host and appends a dot.
*/
static nsresult CreateDomainScopeDBKey(nsIURI* aUri, nsACString& aKey);
static nsresult CreateDomainScopeDBKey(const nsACString& aAsciiDomain, nsACString& aKey);
/**
* Turns "foo.bar.com" to "moc.rab.",
* i.e. extracts eTLD+1 from the host, reverses the result
* and appends a dot.
*/
static nsresult CreateQuotaDomainDBKey(const nsACString& aAsciiDomain,
PRBool aIncludeSubDomains, nsACString& aKey);
protected:
@ -138,11 +205,15 @@ protected:
nsCOMPtr<mozIStorageStatement> mSetSecureStatement;
nsCOMPtr<mozIStorageStatement> mRemoveKeyStatement;
nsCOMPtr<mozIStorageStatement> mRemoveOwnerStatement;
nsCOMPtr<mozIStorageStatement> mRemoveStorageStatement;
nsCOMPtr<mozIStorageStatement> mRemoveAllStatement;
nsCOMPtr<mozIStorageStatement> mGetUsageStatement;
nsAutoString mCachedOwner;
nsCString mCachedOwner;
PRInt32 mCachedUsage;
nsresult
GetUsageInternal(const nsACString& aQuotaDomainDBKey, PRInt32 *aUsage);
};
#endif /* nsDOMStorageDB_h___ */

View File

@ -53,6 +53,7 @@ DIRS += \
general \
whatwg \
geolocation \
localstorage \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,80 @@
#
# ***** 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/localstorage
include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk
_TEST_FILES = \
frameChromeSlave.html \
frameMasterEqual.html \
frameMasterNotEqual.html \
frameSlaveEqual.html \
frameSlaveNotEqual.html \
frameReplace.html \
frameQuota.html \
frameOrder.html \
interOriginFrame.js \
interOriginTest.js \
interOriginTest2.js \
test_localStorageBase.html \
test_localStorageOriginsEquals.html \
test_localStorageOriginsDiff.html \
test_localStorageOriginsPortDiffs.html \
test_localStorageOriginsDomainDiffs.html \
test_localStorageOriginsSchemaDiffs.html \
test_localStorageReplace.html \
test_localStorageQuota.html \
test_localStorageKeyOrder.html \
test_removeOwnersAPI.html \
$(NULL)
_CHROME_FILES = \
test_localStorageFromChrome.xhtml \
$(NULL)
libs:: $(_TEST_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
libs:: $(_CHROME_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)

View File

@ -0,0 +1,9 @@
<html>
<head>
<body>
<span id="data"></span>
<script>
var span = document.getElementById("data");
span.innerHTML = localStorage.chromekey
</script>
</body>

View File

@ -0,0 +1,56 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>frame for localStorage test</title>
<script type="text/javascript" src="interOriginFrame.js"></script>
<script type="text/javascript">
var currentStep = 1;
function doStep()
{
switch (currentStep)
{
case 1:
localStorage.setItem("X", "1");
is(localStorage.getItem("X"), "1", "X is 1 in the master");
break;
case 3:
is(localStorage.getItem("X"), "2", "X set to 2 in the master");
localStorage.removeItem("X");
is(localStorage.getItem("X"), null, "X was removed from the master");
break;
case 5:
is(localStorage.getItem("Y"), "3", "Y is 3 in the master");
localStorage.setItem("Z", "4");
is(localStorage.getItem("Z"), "4", "Z is 4 in the master");
localStorage.clear();
is(localStorage.length, 0, "Master is empty");
break;
case 7:
is(localStorage.length, 0, "Master is empty");
break;
case 9:
return finishTest();
}
// Increase by two to distinguish each test step order
// in both master doStep and slave doStep functions.
++currentStep;
++currentStep;
return true;
}
</script>
</head>
<body onload="postMsg('frame loaded');">
</body>
</html>

View File

@ -0,0 +1,47 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>frame for localStorage test</title>
<script type="text/javascript" src="interOriginFrame.js"></script>
<script type="text/javascript">
var currentStep = 1;
function doStep()
{
switch (currentStep)
{
case 1:
localStorage.setItem("X", "1");
is(localStorage.getItem("X"), "1", "X is 1 in the master");
break;
case 3:
is(localStorage.getItem("X"), "1", "X remains 1 in the master");
localStorage.removeItem("X");
is(localStorage.getItem("X"), null, "X was removed from the master");
break;
case 5:
is(localStorage.getItem("Y"), null, "Y null in the master");
break;
case 7:
return finishTest();
}
// Increase by two to distinguish each test step order
// in both master doStep and slave doStep functions.
++currentStep;
++currentStep;
return true;
}
</script>
</head>
<body onload="postMsg('frame loaded');">
</body>
</html>

View File

@ -0,0 +1,27 @@
<html>
<head>
</head>
<script type="text/javascript">
function doTest()
{
var query = location.search.substring(1);
query = unescape(query);
var keyNames = eval(query);
parent.is(localStorage.a, "10", "a = 10");
parent.is(localStorage.b, "20", "b = 20");
parent.is(localStorage.c, "30", "c = 30");
parent.is(localStorage.d, "40", "d = 40");
parent.is(localStorage.e, "50", "e = 50");
parent.is(localStorage.length, 5, "length = 5");
for (var i = 0; i < localStorage.length; ++i)
parent.is(keyNames[i], localStorage.key(i), "key "+keyNames[i]+" on same index");
parent.SimpleTest.finish();
localStorage.clear();
}
</script>
<body onload="doTest();">
</body>
</html>

View File

@ -0,0 +1,105 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>slave for sessionStorage test</title>
<script type="text/javascript" src="interOriginFrame.js"></script>
<script type="text/javascript">
const DOM_QUOTA_REACHED = 2152924150;
function checkException(func, exc)
{
var exceptionThrew = false;
try {
func();
}
catch (ex) {
exceptionThrew = true;
is(ex.result, exc, "Expected "+exc+" exception");
}
ok(exceptionThrew, "Exception "+exc+" threw at "+location);
}
function doStep()
{
var query = location.search.substring(1);
var queries = query.split("&");
var operation = queries[0];
var keyName = queries[1];
var result = queries[2];
switch (result)
{
case "success":
switch (operation)
{
case "add":
// Store 500 bytes long string must succeed
localStorage.setItem(keyName, "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");
is(localStorage.getItem(keyName), "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", "500 bytes key "+keyName+" stored");
break;
case "remove":
localStorage.removeItem(keyName);
is(localStorage.getItem(keyName), null, "Key "+keyName+" removed");
break;
case "checkclean":
is(localStorage.getItem(keyName), null, "Key "+keyName+" not present");
break;
case "checknotclean":
is(localStorage.getItem(keyName), "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", "Key "+keyName+" is present");
break;
}
break;
case "failure":
switch (operation)
{
case "add":
// Attempt to store 500 bytes long string that doens't
// fit the quota, have to throw DOM_QUOTA_REACHED exception
checkException(function() {
localStorage.setItem(keyName, "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");
}, DOM_QUOTA_REACHED);
is(localStorage.getItem(keyName), null, "500 bytes key "+keyName+" is NOT stored");
break;
case "add2":
// Attempt to change a key value to reach the DOM quota and
// check it fails and the old key value is still present.
checkException(function() {
localStorage.setItem(keyName, "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");
}, DOM_QUOTA_REACHED);
is(localStorage.getItem(keyName), "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", "Key "+keyName+" left unchanged");
break;
}
break;
case "":
switch (operation)
{
case "clear":
localStorage.clear();
break;
}
break;
}
// Just inform the master we are finished now
postMsg("done");
return false;
}
</script>
</head>
<body onload="postMsg('frame loaded');">
</body>
</html>

View File

@ -0,0 +1,72 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>localStorage replace frame</title>
<script type="text/javascript">
var shell;
function ok(a, message)
{
if (!a)
shell.postMessage("FAILURE: " + message, "http://localhost:8888");
else
shell.postMessage(message, "http://localhost:8888");
}
function is(a, b, message)
{
if (a != b)
shell.postMessage("FAILURE: " + message + ", expected "+b+" got "+a, "http://localhost:8888");
else
shell.postMessage(message + ", expected "+b+" got "+a, "http://localhost:8888");
}
function doTest()
{
var query = location.search.substring(1);
var queries = query.split("&");
var action = queries[0];
shell = queries[1];
switch (shell)
{
case "frame":
shell = parent;
break;
case "window":
shell = opener;
break;
}
switch (action)
{
case "init":
localStorage.setItem("A", "1");
localStorage.setItem("B", "2");
localStorage.setItem("C", "3");
is(localStorage.getItem("A"), "1", "'A' is '1'");
is(localStorage.getItem("B"), "2", "'A' is '2'");
is(localStorage.getItem("C"), "3", "'A' is '3'");
break;
case "check":
is(localStorage.getItem("A"), null, "'A' is null");
is(localStorage.getItem("B"), null, "'A' is null");
is(localStorage.getItem("C"), null, "'A' is null");
break;
case "clean":
localStorage.clear();
break;
}
shell.postMessage(action + "_done", "http://localhost:8888");
}
</script>
</head>
<body onload="doTest();">
</body>
</html>

View File

@ -0,0 +1,51 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>frame for localStorage test</title>
<script type="text/javascript" src="interOriginFrame.js"></script>
<script type="text/javascript">
var currentStep = 2;
function doStep()
{
switch (currentStep)
{
case 2:
is(localStorage.getItem("X"), "1", "X is 1 in the slave");
localStorage.setItem("X", "2");
is(localStorage.getItem("X"), "2", "X set to 2 in the slave");
break;
case 4:
is(localStorage.getItem("X"), null, "X was removed from the slave");
localStorage.setItem("Y", "3");
is(localStorage.getItem("Y"), "3", "Y set to 3 in the slave");
break;
case 6:
is(localStorage.length, 0, "Slave is empty");
is(localStorage.getItem("X"), null, "X is null in the slave");
is(localStorage.getItem("Y"), null, "Y is null in the slave");
is(localStorage.getItem("Z"), null, "Z is null in the slave");
break;
case 8:
return finishTest();
}
// Increase by two to distinguish each test step order
// in both master doStep and slave doStep functions.
++currentStep;
++currentStep;
return true;
}
</script>
</head>
<body onload="postMsg('frame loaded');">
</body>
</html>

View File

@ -0,0 +1,44 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>frame for localStorage test</title>
<script type="text/javascript" src="interOriginFrame.js"></script>
<script type="text/javascript">
var currentStep = 2;
function doStep()
{
switch (currentStep)
{
case 2:
is(localStorage.getItem("X"), null, "X not set in the slave");
localStorage.setItem("X", "2");
is(localStorage.getItem("X"), "2", "X set to 2 in the slave");
break;
case 4:
is(localStorage.getItem("X"), "2", "X still set to 2 in the slave");
localStorage.setItem("Y", "3");
is(localStorage.getItem("Y"), "3", "Y set to 4 (MUST FAIL!) in the slave");
break;
case 6:
return finishTest();
}
// Increase by two to distinguish each test step order
// in both master doStep and slave doStep functions.
++currentStep;
++currentStep;
return true;
}
</script>
</head>
<body onload="postMsg('frame loaded');">
</body>
</html>

View File

@ -0,0 +1,55 @@
function postMsg(message)
{
parent.postMessage(message, "http://localhost:8888");
}
window.addEventListener("message", onMessageReceived, false);
function onMessageReceived(event)
{
if (event.data == "step") {
var performed = false;
try {
performed = doStep();
}
catch (ex) {
postMsg("FAILURE: exception threw at "+ location +":\n" + ex);
finishTest();
}
if (performed)
postMsg("perf");
return;
}
postMsg("Invalid message");
}
function ok(a, message)
{
if (!a)
postMsg("FAILURE: " + message);
else
postMsg(message);
}
function is(a, b, message)
{
if (a != b)
postMsg("FAILURE: " + message + ", expected "+b+" got "+a);
else
postMsg(message + ", expected "+b+" got "+a);
}
function todo(a, b, message)
{
postMsg("TODO: " + message + ", expected "+b+" got "+a);
}
function finishTest()
{
localStorage.clear();
postMsg("done");
return false;
}

View File

@ -0,0 +1,42 @@
var slaveLoadsPending = 1;
var slaveOrigin = "";
var slave = null;
var failureRegExp = new RegExp("^FAILURE");
const slavePath = "/tests/dom/tests/mochitest/localstorage/";
window.addEventListener("message", onMessageReceived, false);
function onMessageReceived(event)
{
switch (event.data)
{
// Indication of the frame onload event
case "frame loaded":
if (--slaveLoadsPending)
break;
// Just fall through...
// Indication of successfully finished step of a test
case "perf":
if (event.data == "perf")
doStep();
slave.postMessage("step", slaveOrigin);
break;
// Indication of all test parts finish (from any of the frames)
case "done":
localStorage.clear();
slaveLoadsPending = 1;
doNextTest();
break;
// Any other message indicates error or succes message of a test
default:
SimpleTest.ok(!event.data.match(failureRegExp), event.data);
break;
}
}

View File

@ -0,0 +1,53 @@
var frameLoadsPending = 2;
var callMasterFrame = true;
var testDone = false;
var masterFrameOrigin = "";
var slaveFrameOrigin = "";
var failureRegExp = new RegExp("^FAILURE");
var todoRegExp = new RegExp("^TODO");
const framePath = "/tests/dom/tests/mochitest/localstorage/";
window.addEventListener("message", onMessageReceived, false);
function onMessageReceived(event)
{
switch (event.data)
{
// Indication of the frame onload event
case "frame loaded":
if (--frameLoadsPending)
break;
// Just fall through...
// Indication of successfully finished step of a test
case "perf":
if (callMasterFrame)
masterFrame.postMessage("step", masterFrameOrigin);
else
slaveFrame.postMessage("step", slaveFrameOrigin);
callMasterFrame = !callMasterFrame;
break;
// Indication of all test parts finish (from any of the frames)
case "done":
if (testDone)
break;
testDone = true;
SimpleTest.finish();
break;
// Any other message indicates error, succes or todo message of a test
default:
if (event.data.match(todoRegExp))
SimpleTest.todo(false, event.data);
else
SimpleTest.ok(!event.data.match(failureRegExp), event.data);
break;
}
}

View File

@ -0,0 +1,187 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>localStorage basic 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">
var INDEX_SIZE_ERR = 1;
function checkException(func, exc)
{
var exceptionThrew = false;
try {
func();
}
catch (ex) {
exceptionThrew = true;
is(ex.code, exc, "Expected "+exc+" exception");
}
ok(exceptionThrew, "Exception "+exc+" threw");
}
function startTest()
{
// Initially check the localStorage is empty
is(localStorage.length, 0, "The storage is empty [1]");
checkException(function() {localStorage.key(0);}, INDEX_SIZE_ERR);
checkException(function() {localStorage.key(-1);}, INDEX_SIZE_ERR);
checkException(function() {localStorage.key(1);}, INDEX_SIZE_ERR);
is(localStorage.getItem("nonexisting"), null, "Nonexisting item is null (getItem())");
is(localStorage["nonexisting"], null, "Nonexisting item is null (array access)");
is(localStorage.nonexisting, null, "Nonexisting item is null (property access)");
localStorage.removeItem("nonexisting"); // Just check there is no exception
is(typeof localStorage.getItem("nonexisting"), "object", "getItem('nonexisting') is object");
is(typeof localStorage["nonexisting"], "object", "['nonexisting'] is object");
is(typeof localStorage.nonexisting, "object", "nonexisting is object");
is(typeof localStorage.getItem("nonexisting2"), "object", "getItem('nonexisting2') is object");
is(typeof localStorage["nonexisting2"], "object", "['nonexisting2'] is object");
is(typeof localStorage.nonexisting2, "object", "nonexisting2 is object");
// add an empty-value key
localStorage.setItem("empty", "");
is(localStorage.getItem("empty"), "", "Empty value (getItem())");
is(localStorage["empty"], "", "Empty value (array access)");
is(localStorage.empty, "", "Empty value (property access)");
is(typeof localStorage.getItem("empty"), "string", "getItem('empty') is string");
is(typeof localStorage["empty"], "string", "['empty'] is string");
is(typeof localStorage.empty, "string", "empty is string");
localStorage.removeItem("empty");
is(localStorage.length, 0, "The storage has no keys");
is(localStorage.getItem("empty"), null, "empty item is null (getItem())");
is(localStorage["empty"], null, "empty item is null (array access)");
is(localStorage.empty, null, "empty item is null (property access)");
is(typeof localStorage.getItem("empty"), "object", "getItem('empty') is object");
is(typeof localStorage["empty"], "object", "['empty'] is object");
is(typeof localStorage.empty, "object", "empty is object");
// add one key, check it is there
localStorage.setItem("key1", "value1");
is(localStorage.length, 1, "The storage has one key-value pair");
is(localStorage.key(0), "key1");
checkException(function() {localStorage.key(-1);}, INDEX_SIZE_ERR);
checkException(function() {localStorage.key(1);}, INDEX_SIZE_ERR);
// check all access method give the correct result
// and are of the correct type
is(localStorage.getItem("key1"), "value1", "getItem('key1') == value1");
is(localStorage["key1"], "value1", "['key1'] == value1");
is(localStorage.key1, "value1", "key1 == value1");
is(typeof localStorage.getItem("key1"), "string", "getItem('key1') is string");
is(typeof localStorage["key1"], "string", "['key1'] is string");
is(typeof localStorage.key1, "string", "key1 is string");
// remove the previously added key and check the storage is empty
localStorage.removeItem("key1");
is(localStorage.length, 0, "The storage is empty [2]");
checkException(function() {localStorage.key(0);}, INDEX_SIZE_ERR);
is(localStorage.getItem("key1"), null, "\'key1\' removed");
is(typeof localStorage.getItem("key1"), "object", "getItem('key1') is object");
is(typeof localStorage["key1"], "object", "['key1'] is object");
is(typeof localStorage.key1, "object", "key1 is object");
// add one key, check it is there
localStorage.setItem("key1", "value1");
is(localStorage.length, 1, "The storage has one key-value pair");
is(localStorage.key(0), "key1");
is(localStorage.getItem("key1"), "value1");
// add a second key
localStorage.setItem("key2", "value2");
is(localStorage.length, 2, "The storage has two key-value pairs");
is(localStorage.key(1), "key1"); // This test might not be accurate because order is not preserved
is(localStorage.key(0), "key2");
is(localStorage.getItem("key1"), "value1");
is(localStorage.getItem("key2"), "value2");
// change the second key
localStorage.setItem("key2", "value2-2");
is(localStorage.length, 2, "The storage has two key-value pairs");
is(localStorage.key(1), "key1"); // After key value changes the order must be preserved
is(localStorage.key(0), "key2");
checkException(function() {localStorage.key(-1);}, INDEX_SIZE_ERR);
checkException(function() {localStorage.key(2);}, INDEX_SIZE_ERR);
is(localStorage.getItem("key1"), "value1");
is(localStorage.getItem("key2"), "value2-2");
// change the first key
localStorage.setItem("key1", "value1-2");
is(localStorage.length, 2, "The storage has two key-value pairs");
is(localStorage.key(1), "key1"); // After key value changes the order must be preserved
is(localStorage.key(0), "key2");
checkException(function() {localStorage.key(-1);}, INDEX_SIZE_ERR);
checkException(function() {localStorage.key(2);}, INDEX_SIZE_ERR);
is(localStorage.getItem("key1"), "value1-2");
is(localStorage.getItem("key2"), "value2-2");
// remove the second key
localStorage.removeItem("key2");
is(localStorage.length, 1, "The storage has one key-value pair");
is(localStorage.key(0), "key1");
checkException(function() {localStorage.key(-1);}, INDEX_SIZE_ERR);
checkException(function() {localStorage.key(1);}, INDEX_SIZE_ERR);
is(localStorage.getItem("key1"), "value1-2");
// JS property test
localStorage.testA = "valueA";
is(localStorage.testA, "valueA");
is(localStorage["testA"], "valueA");
is(localStorage.getItem("testA"), "valueA");
localStorage.testA = "valueA2";
is(localStorage.testA, "valueA2");
is(localStorage["testA"], "valueA2");
is(localStorage.getItem("testA"), "valueA2");
localStorage["testB"] = "valueB";
is(localStorage.testB, "valueB");
is(localStorage["testB"], "valueB");
is(localStorage.getItem("testB"), "valueB");
localStorage["testB"] = "valueB2";
is(localStorage.testB, "valueB2");
is(localStorage["testB"], "valueB2");
is(localStorage.getItem("testB"), "valueB2");
localStorage.setItem("testC", "valueC");
is(localStorage.testC, "valueC");
is(localStorage["testC"], "valueC");
is(localStorage.getItem("testC"), "valueC");
localStorage.setItem("testC", "valueC2");
is(localStorage.testC, "valueC2");
is(localStorage["testC"], "valueC2");
is(localStorage.getItem("testC"), "valueC2");
// Clear the storage
localStorage.clear();
is(localStorage.length, 0, "The storage is empty [3]");
checkException(function() {localStorage.key(0);}, INDEX_SIZE_ERR); // this is unspecified!
checkException(function() {localStorage.key(-1);}, INDEX_SIZE_ERR);
checkException(function() {localStorage.key(1);}, INDEX_SIZE_ERR);
is(localStorage.getItem("nonexisting"), null, "Nonexisting item is null");
is(localStorage.getItem("key1"), null, "key1 removed");
is(localStorage.getItem("key2"), null, "key2 removed");
localStorage.removeItem("nonexisting"); // Just check there is no exception
localStorage.removeItem("key1"); // Just check there is no exception
localStorage.removeItem("key2"); // Just check there is no exception
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body onload="startTest();">
</body>
</html>

View File

@ -0,0 +1,46 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>localStorage basic test</title>
<script type="text/javascript" src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="text/javascript">
function startTest()
{
var url = "http://example.com/tests/dom/tests/mochitest/localstorage/frameChromeSlave.html";
var ios = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var ssm = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
.getService(Components.interfaces.nsIScriptSecurityManager);
var dsm = Components.classes["@mozilla.org/dom/storagemanager;1"]
.getService(Components.interfaces.nsIDOMStorageManager);
var uri = ios.newURI(url, "", null);
var principal = ssm.getCodebasePrincipal(uri);
var storage = dsm.getLocalStorageForPrincipal(principal);
storage.wrappedJSObject.setItem("chromekey", "chromevalue");
var aframe = document.getElementById("aframe");
aframe.onload = function()
{
is(storage.wrappedJSObject.getItem("chromekey"), "chromevalue");
is(aframe.contentDocument.getElementById("data").innerHTML, "chromevalue");
SimpleTest.finish();
}
aframe.src = "http://example.com/tests/dom/tests/mochitest/localstorage/frameChromeSlave.html";
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body onload="startTest();">
<iframe src="" id="aframe"></iframe>
</body>
</html>

View File

@ -0,0 +1,76 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>localStorage key order 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" />
<!--
Check we preserve order of keys in localStorage when
keys are just modified. When a new key is added or
a key is removed order is again unspecified.
-->
<script type="text/javascript">
var INDEX_SIZE_ERR = 1;
function startTest()
{
try
{
var keyNames = new Array;
localStorage.a = "1";
localStorage.b = "2";
localStorage.c = "3";
localStorage.d = "4";
localStorage.e = "5";
is(localStorage.a, "1", "a = 1");
is(localStorage.b, "2", "b = 2");
is(localStorage.c, "3", "c = 3");
is(localStorage.d, "4", "d = 4");
is(localStorage.e, "5", "e = 5");
is(localStorage.length, 5, "length = 5");
for (var i = 0; i < localStorage.length; ++i)
keyNames[i] = localStorage.key(i);
localStorage.a = "10";
localStorage.b = "20";
localStorage.c = "30";
localStorage.d = "40";
localStorage.e = "50";
is(localStorage.a, "10", "a = 10");
is(localStorage.b, "20", "b = 20");
is(localStorage.c, "30", "c = 30");
is(localStorage.d, "40", "d = 40");
is(localStorage.e, "50", "e = 50");
is(localStorage.length, 5, "length = 5");
for (var i = 0; i < localStorage.length; ++i)
is(keyNames[i], localStorage.key(i), "key "+keyNames[i]+" on same index");
keyNamesStringify = "[\"" + keyNames.join("\",\"") + "\"]";
frame.location = "http://localhost:8888/tests/dom/tests/mochitest/localstorage/frameOrder.html?" +
keyNamesStringify;
}
catch (ex)
{
localStorage.clear();
throw ex;
}
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body onload="startTest();">
<iframe src="" name="frame"></frame>
</body>
</html>

View File

@ -0,0 +1,44 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>localStorage different origins</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="interOriginTest2.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<!--
This test loads two frames from different
origins and checks that entries of localStorage
objects don't leak each between other.
The subsystem is based on postMessage and addEventListener
to send messages among different origins. The subsystem waits
for both frames be loaded and then alternately calls each frames'
doStep() function that on each call proceeds with a single step
of the test on its side. This way the subsystem alternate between
both frames until both sequences completely finish.
-->
<script type="text/javascript">
function startTest()
{
masterFrameOrigin = "http://example.org:80";
slaveFrameOrigin = "http://example.com:80";
masterFrame.location = masterFrameOrigin + framePath + "frameMasterNotEqual.html";
slaveFrame.location = slaveFrameOrigin + framePath + "frameSlaveNotEqual.html";
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body onload="startTest();">
<iframe src="" name="masterFrame"></iframe>
<iframe src="" name="slaveFrame"></iframe>
</body>
</html>

View File

@ -0,0 +1,44 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>localStorage different domains</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="interOriginTest2.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<!--
This test loads two frames from different
origins and checks that entries of localStorage
objects don't leak each between other.
The subsystem is based on postMessage and addEventListener
to send messages among different origins. The subsystem waits
for both frames be loaded and then alternately calls each frames'
doStep() function that on each call proceeds with a single step
of the test on its side. This way the subsystem alternate between
both frames until both sequences completely finish.
-->
<script type="text/javascript">
function startTest()
{
masterFrameOrigin = "http://example.org:80";
slaveFrameOrigin = "http://test1.example.org:80";
masterFrame.location = masterFrameOrigin + framePath + "frameMasterNotEqual.html";
slaveFrame.location = slaveFrameOrigin + framePath + "frameSlaveNotEqual.html";
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body onload="startTest();">
<iframe src="" name="masterFrame"></iframe>
<iframe src="" name="slaveFrame"></iframe>
</body>
</html>

View File

@ -0,0 +1,45 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>localStorage equal origins</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="interOriginTest2.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<!--
This test loads two frames from equal
origins and checks that entries of localStorage
objects are kept up-to-date and synchronized
between both frames.
The subsystem is based on postMessage and addEventListener
to send messages among different origins. The subsystem waits
for both frames be loaded and then alternately calls each frames'
doStep() function that on each call proceeds with a single step
of the test on its side. This way the subsystem alternate between
both frames until both sequences completely finish.
-->
<script type="text/javascript">
function startTest()
{
masterFrameOrigin = "http://example.org:80";
slaveFrameOrigin = "http://example.org:80";
masterFrame.location = masterFrameOrigin + framePath + "frameMasterEqual.html";
slaveFrame.location = slaveFrameOrigin + framePath + "frameSlaveEqual.html";
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body onload="startTest();">
<iframe src="" name="masterFrame"></iframe>
<iframe src="" name="slaveFrame"></iframe>
</body>
</html>

View File

@ -0,0 +1,44 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>localStorage different port numbers</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="interOriginTest2.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<!--
This test loads two frames from different
origins and checks that entries of localStorage
objects don't leak each between other.
The subsystem is based on postMessage and addEventListener
to send messages among different origins. The subsystem waits
for both frames be loaded and then alternately calls each frames'
doStep() function that on each call proceeds with a single step
of the test on its side. This way the subsystem alternate between
both frames until both sequences completely finish.
-->
<script type="text/javascript">
function startTest()
{
masterFrameOrigin = "http://example.org:80";
slaveFrameOrigin = "http://example.org:8000";
masterFrame.location = masterFrameOrigin + framePath + "frameMasterNotEqual.html";
slaveFrame.location = slaveFrameOrigin + framePath + "frameSlaveNotEqual.html";
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body onload="startTest();">
<iframe src="" name="masterFrame"></iframe>
<iframe src="" name="slaveFrame"></iframe>
</body>
</html>

View File

@ -0,0 +1,44 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>localStorage different domains</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="interOriginTest2.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<!--
This test loads two frames from different
origins and checks that entries of localStorage
objects don't leak each between other.
The subsystem is based on postMessage and addEventListener
to send messages among different origins. The subsystem waits
for both frames be loaded and then alternately calls each frames'
doStep() function that on each call proceeds with a single step
of the test on its side. This way the subsystem alternate between
both frames until both sequences completely finish.
-->
<script type="text/javascript">
function startTest()
{
masterFrameOrigin = "http://example.com";
slaveFrameOrigin = "https://example.com";
masterFrame.location = masterFrameOrigin + framePath + "frameMasterNotEqual.html";
slaveFrame.location = slaveFrameOrigin + framePath + "frameSlaveNotEqual.html";
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body onload="startTest();">
<iframe src="" name="masterFrame"></iframe>
<iframe src="" name="slaveFrame"></iframe>
</body>
</html>

View File

@ -0,0 +1,127 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>localStorage and DOM quota test</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="interOriginTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript">
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var currentTest = 1;
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
var quota;
function doNextTest()
{
slave = frame;
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
switch (currentTest)
{
// Initialy setup the quota to testing value of 1024B and
// set a 500 bytes key with name length 1 (allocate 501 bytes)
case 1:
try {
quota = prefs.getIntPref("dom.storage.default_quota");
} catch (ex) {
quota = 5*1024;
}
prefs.setIntPref("dom.storage.default_quota", 1);
slaveOrigin = "http://example.com";
slave.location = slaveOrigin + slavePath + "frameQuota.html?add&A&success";
break;
// In subdomain now set another key with length 500 bytes, i.e.
// allocate 501 bytes
case 2:
slaveOrigin = "http://test1.example.com";
slave.location = slaveOrigin + slavePath + "frameQuota.html?add&B&success";
break;
// Try to set the same key value again to check we don't fail
// even 1002 bytes has already been exhausted from the quota
// We just change the value of an existing key.
case 3:
slaveOrigin = "http://test1.example.com";
slave.location = slaveOrigin + slavePath + "frameQuota.html?add&B&success";
break;
// Try to set the same key to a larger value that would lead to
// quota reach and check that the value is still the old one
case 4:
slaveOrigin = "http://test1.example.com";
slave.location = slaveOrigin + slavePath + "frameQuota.html?add2&B&failure";
break;
// In a different subdomain try to set a new 500 bytes key
// and check we fail because we are over the quota
case 5:
slaveOrigin = "https://test2.example.com";
slave.location = slaveOrigin + slavePath + "frameQuota.html?add&C&failure";
break;
// Remove from the second subdomain the second key, it must not fail
// This should release the allocated space of the quota assigned to
// example.com.
case 6:
slaveOrigin = "http://test1.example.com";
slave.location = slaveOrigin + slavePath + "frameQuota.html?remove&B&success";
break;
// Now try again to set 500 bytes key, it must succeed.
case 7:
slaveOrigin = "https://test2.example.com";
slave.location = slaveOrigin + slavePath + "frameQuota.html?add&C&success";
break;
case 8:
// Do a clean up...
// TODO Bug 455070, use just ?clear what invokes call
// of clear() in the target frame. W/o clear method we must
// call clear implemented as removeItem for each item in
// the localStorage.
slaveOrigin = "http://example.com";
slave.location = slaveOrigin + slavePath + "frameQuota.html?clear&A&";
break;
case 9:
// Do a clean up...
slaveOrigin = "http://test1.example.com";
slave.location = slaveOrigin + slavePath + "frameQuota.html?clear&B&";
break;
case 10:
// Do a clean up...
slaveOrigin = "https://test2.example.com";
slave.location = slaveOrigin + slavePath + "frameQuota.html?clear&C&";
break;
case 11:
prefs.setIntPref("dom.storage.default_quota", quota);
SimpleTest.finish();
}
++currentTest;
}
function doStep()
{
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body onload="doNextTest();">
<iframe src="" name="frame"></iframe>
</body>
</html>

View File

@ -0,0 +1,78 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>localStorage replace test</title>
<!--
This test checks that localStorage object doesn't leak
in a window that changes its location. We do this by switching
frame location inside of this window and then by changing location
of a top level window.
-->
<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">
var shell;
var shellType;
var failureRegExp = new RegExp("^FAILURE");
window.addEventListener("message", onMessageReceived, false);
function onMessageReceived(event)
{
switch (event.data)
{
case "init_done":
// This is frame with different origin in the same browsing context
// as the first frame adding data to localStorage of the first origin.
shell.location = "http://example.com:80/tests/dom/tests/mochitest/localstorage/frameReplace.html?check&" + shellType;
break;
case "check_done":
// Clean the localStorage of the first origin.
shell.location = "http://example.org:80/tests/dom/tests/mochitest/localstorage/frameReplace.html?clean&" + shellType;
break;
case "clean_done":
switch (shellType)
{
case "frame":
// We finished testing in a frame
// proceed with test in a separate window
shellType = "window";
shell = window.open("http://example.org:80/tests/dom/tests/mochitest/localstorage/frameReplace.html?init&" + shellType);
break;
case "window":
shell.close();
window.setTimeout(function() {SimpleTest.finish();}, 0);
break;
}
break;
default:
SimpleTest.ok(!event.data.match(failureRegExp), event.data);
break;
}
}
function startTest()
{
shellType = "frame";
shell = frame;
shell.location = "http://example.org:80/tests/dom/tests/mochitest/localstorage/frameReplace.html?init&" + shellType;
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body onload="startTest();">
<iframe src="" name="frame"></iframe>
</body>
</html>

View File

@ -0,0 +1,135 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>localStorage and DOM quota test</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="interOriginTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript">
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var currentTest = 1;
var currentStep = 1;
function addOfflineApp(url)
{
var permissionManager = Components.classes["@mozilla.org/permissionmanager;1"]
.getService(Components.interfaces.nsIPermissionManager);
var uri = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService)
.newURI(url, null, null);
permissionManager.add(uri, "offline-app",
Components.interfaces.nsIPermissionManager.ALLOW_ACTION);
}
function removeOfflineApp(url)
{
var permissionManager = Components.classes["@mozilla.org/permissionmanager;1"]
.getService(Components.interfaces.nsIPermissionManager);
var uri = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService)
.newURI(url, null, null);
permissionManager.remove(uri.host, "offline-app");
}
function doNextTest()
{
slave = frame;
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
currentStep = 1;
switch (currentTest)
{
// Add something to storage of example.com
case 1:
slaveOrigin = "http://example.com";
slave.location = slaveOrigin + slavePath + "frameQuota.html?add&A&success";
break;
// Add something to storage of test1.example.com, secure schema
case 2:
slaveOrigin = "https://test1.example.com";
slave.location = slaveOrigin + slavePath + "frameQuota.html?add&B&success";
break;
// Add something to storage of http://sub1.xn--hxajbheg2az3al.xn--jxalpdlp, secure schema
case 3:
slaveOrigin = "http://sub1.xn--hxajbheg2az3al.xn--jxalpdlp";
slave.location = slaveOrigin + slavePath + "frameQuota.html?add&C&success";
break;
// Call RemoveOwners API through storage manager.
// Classify the sites above as offline-app using
// the permission manager to let the storage manager
// know about them.
case 4:
addOfflineApp("http://example.com");
addOfflineApp("http://sub1.xn--hxajbheg2az3al.xn--jxalpdlp");
var manager = Components.classes["@mozilla.org/dom/storagemanager;1"]
.getService(Components.interfaces.nsIDOMStorageManager);
try {
manager.clearOfflineApps();
}
catch (ex) {
ok(false, "Exception not thrown during clearOfflineApps()");
}
removeOfflineApp("http://example.com");
removeOfflineApp("http://sub1.xn--hxajbheg2az3al.xn--jxalpdlp");
// Now check that those two sites' data disappeared
slaveOrigin = "http://example.com";
slave.location = slaveOrigin + slavePath + "frameQuota.html?checkclean&A&success";
break;
case 5:
slaveOrigin = "http://sub1.xn--hxajbheg2az3al.xn--jxalpdlp";
slave.location = slaveOrigin + slavePath + "frameQuota.html?checkclean&C&success";
break;
case 6:
slaveOrigin = "https://test1.example.com";
slave.location = slaveOrigin + slavePath + "frameQuota.html?checknotclean&B&success";
break;
case 7:
addOfflineApp("https://test1.example.com");
var manager = Components.classes["@mozilla.org/dom/storagemanager;1"]
.getService(Components.interfaces.nsIDOMStorageManager);
try {
manager.clearOfflineApps();
}
catch (ex) {
ok(false, "Exception not thrown during clearOfflineApps()");
}
removeOfflineApp("https://test1.example.com");
// Now check that those site's data disappeared
slaveOrigin = "https://test1.example.com";
slave.location = slaveOrigin + slavePath + "frameQuota.html?checkclean&B&success";
break;
case 8:
SimpleTest.finish();
}
++currentTest;
}
function doStep()
{
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body onload="doNextTest();">
<iframe src="" name="frame"></iframe>
</body>
</html>

View File

@ -943,7 +943,7 @@ nsWindowWatcher::OpenWindowJSInternal(nsIDOMWindow *aParent,
if (piStorage){
storage = piStorage->Clone();
newDocShell->AddSessionStorage(
NS_ConvertUTF16toUTF8(piStorage->Domain()),
piStorage->Domain(),
storage);
}
}

View File

@ -460,11 +460,12 @@ members = [
# dom/public/idl/storage
'nsIDOMToString.toString',
'nsIDOMStorage.setItem',
'nsIDOMStorage.length',
'nsIDOMStorage.getItem',
'nsIDOMStorage.key',
'nsIDOMStorage.removeItem',
'nsIDOMStorage2.setItem',
'nsIDOMStorage2.length',
'nsIDOMStorage2.getItem',
'nsIDOMStorage2.key',
'nsIDOMStorage2.removeItem',
'nsIDOMStorage2.clear',
'nsIDOMStorageItem.value',
'nsIDOMStorageWindow.sessionStorage',
'nsIDOMStorageWindow.globalStorage',