From 9a3a451bee33cb5f6cad1064adc89632b5cff876 Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Mon, 21 Nov 2011 09:56:24 -0500 Subject: [PATCH] Bug 692627: Support complex keyPaths in IndexedDB. r=bent,jorendorff --- dom/indexedDB/IDBDatabase.cpp | 4 + dom/indexedDB/IDBObjectStore.cpp | 137 +++++++++----- dom/indexedDB/IDBObjectStore.h | 3 + dom/indexedDB/test/Makefile.in | 1 + dom/indexedDB/test/test_complex_keyPaths.html | 175 ++++++++++++++++++ js/src/jsapi.cpp | 13 ++ js/src/jsapi.h | 6 + 7 files changed, 295 insertions(+), 44 deletions(-) create mode 100644 dom/indexedDB/test/test_complex_keyPaths.html diff --git a/dom/indexedDB/IDBDatabase.cpp b/dom/indexedDB/IDBDatabase.cpp index c38197d0c5d..f2ff89040e7 100644 --- a/dom/indexedDB/IDBDatabase.cpp +++ b/dom/indexedDB/IDBDatabase.cpp @@ -512,6 +512,10 @@ IDBDatabase::CreateObjectStore(const nsAString& aName, autoIncrement = !!boolVal; } + if (!IDBObjectStore::IsValidKeyPath(aCx, keyPath)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + nsAutoPtr newInfo(new ObjectStoreInfo()); newInfo->name = aName; diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index 8c238befed2..620d77b9e82 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -43,6 +43,7 @@ #include "jsclone.h" #include "mozilla/storage.h" +#include "nsCharSeparatedTokenizer.h" #include "nsContentUtils.h" #include "nsDOMClassInfo.h" #include "nsEventDispatcher.h" @@ -411,24 +412,51 @@ private: }; inline -nsresult -GetKeyFromObject(JSContext* aCx, - JSObject* aObj, - const nsString& aKeyPath, - Key& aKey) +bool +IgnoreWhitespace(PRUnichar c) { - NS_PRECONDITION(aCx && aObj, "Null pointers!"); - NS_ASSERTION(!aKeyPath.IsVoid(), "This will explode!"); + return false; +} - const jschar* keyPathChars = reinterpret_cast(aKeyPath.get()); - const size_t keyPathLen = aKeyPath.Length(); +typedef nsCharSeparatedTokenizerTemplate KeyPathTokenizer; - jsval key; - JSBool ok = JS_GetUCProperty(aCx, aObj, keyPathChars, keyPathLen, &key); - NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); +inline +nsresult +GetKeyFromValue(JSContext* aCx, + jsval aVal, + const nsAString& aKeyPath, + Key& aKey) +{ + NS_ASSERTION(aCx, "Null pointer!"); + NS_ASSERTION(!JSVAL_IS_PRIMITIVE(aVal), "Why are we here!?"); + NS_ASSERTION(IDBObjectStore::IsValidKeyPath(aCx, aKeyPath), + "This will explode!"); - nsresult rv = aKey.SetFromJSVal(aCx, key); - NS_ENSURE_SUCCESS(rv, rv); + KeyPathTokenizer tokenizer(aKeyPath, '.'); + + jsval intermediate = aVal; + while (tokenizer.hasMoreTokens()) { + nsString token(tokenizer.nextToken()); + + if (!token.Length()) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + const jschar* keyPathChars = token.get(); + const size_t keyPathLen = token.Length(); + + if (JSVAL_IS_PRIMITIVE(intermediate)) { + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + JSBool ok = JS_GetUCProperty(aCx, JSVAL_TO_OBJECT(intermediate), + keyPathChars, keyPathLen, &intermediate); + NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + } + + if (NS_FAILED(aKey.SetFromJSVal(aCx, intermediate))) { + aKey.Unset(); + } return NS_OK; } @@ -502,6 +530,46 @@ IDBObjectStore::Create(IDBTransaction* aTransaction, return objectStore.forget(); } +// static +bool +IDBObjectStore::IsValidKeyPath(JSContext* aCx, + const nsAString& aKeyPath) +{ + NS_ASSERTION(!aKeyPath.IsVoid(), "What?"); + + KeyPathTokenizer tokenizer(aKeyPath, '.'); + + while (tokenizer.hasMoreTokens()) { + nsString token(tokenizer.nextToken()); + + if (!token.Length()) { + return false; + } + + jsval stringVal; + if (!xpc_qsStringToJsval(aCx, token, &stringVal)) { + return false; + } + + NS_ASSERTION(JSVAL_IS_STRING(stringVal), "This should never happen"); + JSString* str = JSVAL_TO_STRING(stringVal); + + JSBool isIdentifier = JS_FALSE; + if (!JS_IsIdentifier(aCx, str, &isIdentifier) || !isIdentifier) { + return false; + } + } + + // If the very last character was a '.', the tokenizer won't give us an empty + // token, but the keyPath is still invalid. + if (!aKeyPath.IsEmpty() && + aKeyPath.CharAt(aKeyPath.Length() - 1) == '.') { + return false; + } + + return true; +} + // static nsresult IDBObjectStore::GetKeyPathValueFromStructuredData(const PRUint8* aData, @@ -530,22 +598,8 @@ IDBObjectStore::GetKeyPathValueFromStructuredData(const PRUint8* aData, return NS_OK; } - JSObject* obj = JSVAL_TO_OBJECT(clone); - - const jschar* keyPathChars = - reinterpret_cast(aKeyPath.BeginReading()); - const size_t keyPathLen = aKeyPath.Length(); - - jsval keyVal; - JSBool ok = JS_GetUCProperty(aCx, obj, keyPathChars, keyPathLen, &keyVal); - NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - - nsresult rv = aValue.SetFromJSVal(aCx, keyVal); - if (NS_FAILED(rv)) { - // If the object doesn't have a value that we can use for our index then we - // leave it unset. - aValue.Unset(); - } + nsresult rv = GetKeyFromValue(aCx, clone, aKeyPath, aValue); + NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } @@ -566,23 +620,14 @@ IDBObjectStore::GetIndexUpdateInfo(ObjectStoreInfo* aObjectStoreInfo, return NS_ERROR_OUT_OF_MEMORY; } - cloneObj = JSVAL_TO_OBJECT(aObject); - for (PRUint32 indexesIndex = 0; indexesIndex < count; indexesIndex++) { const IndexInfo& indexInfo = aObjectStoreInfo->indexes[indexesIndex]; - const jschar* keyPathChars = - reinterpret_cast(indexInfo.keyPath.BeginReading()); - const size_t keyPathLen = indexInfo.keyPath.Length(); - - jsval keyPathValue; - JSBool ok = JS_GetUCProperty(aCx, cloneObj, keyPathChars, keyPathLen, - &keyPathValue); - NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); - Key value; - nsresult rv = value.SetFromJSVal(aCx, keyPathValue); - if (NS_FAILED(rv) || value.IsUnset()) { + nsresult rv = GetKeyFromValue(aCx, aObject, indexInfo.keyPath, value); + NS_ENSURE_SUCCESS(rv, rv); + + if (value.IsUnset()) { // Not a value we can do anything with, ignore it. continue; } @@ -850,7 +895,7 @@ IDBObjectStore::GetAddInfo(JSContext* aCx, return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; } - rv = GetKeyFromObject(aCx, JSVAL_TO_OBJECT(aValue), mKeyPath, aKey); + rv = GetKeyFromValue(aCx, aValue, mKeyPath, aKey); NS_ENSURE_SUCCESS(rv, rv); } @@ -1269,6 +1314,10 @@ IDBObjectStore::CreateIndex(const nsAString& aName, return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR; } + if (!IsValidKeyPath(aCx, aKeyPath)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + IDBTransaction* transaction = AsyncConnectionHelper::GetCurrentTransaction(); if (!transaction || diff --git a/dom/indexedDB/IDBObjectStore.h b/dom/indexedDB/IDBObjectStore.h index 15f4f82d8fc..61145a61473 100644 --- a/dom/indexedDB/IDBObjectStore.h +++ b/dom/indexedDB/IDBObjectStore.h @@ -72,6 +72,9 @@ public: Create(IDBTransaction* aTransaction, const ObjectStoreInfo* aInfo); + static bool + IsValidKeyPath(JSContext* aCx, const nsAString& aKeyPath); + static nsresult GetKeyPathValueFromStructuredData(const PRUint8* aData, PRUint32 aDataLength, diff --git a/dom/indexedDB/test/Makefile.in b/dom/indexedDB/test/Makefile.in index 3e383c1972d..cefad24c06b 100644 --- a/dom/indexedDB/test/Makefile.in +++ b/dom/indexedDB/test/Makefile.in @@ -59,6 +59,7 @@ TEST_FILES = \ test_bfcache.html \ test_clear.html \ test_cmp.html \ + test_complex_keyPaths.html \ test_count.html \ test_create_index.html \ test_create_index_with_integer_keys.html \ diff --git a/dom/indexedDB/test/test_complex_keyPaths.html b/dom/indexedDB/test/test_complex_keyPaths.html new file mode 100644 index 00000000000..bdca769c0ba --- /dev/null +++ b/dom/indexedDB/test/test_complex_keyPaths.html @@ -0,0 +1,175 @@ + + + + Indexed Database Property Test + + + + + + + + + + + diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 8a1c7dd594c..1775d9b48d3 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -6484,6 +6484,19 @@ JS_IndexToId(JSContext *cx, uint32 index, jsid *id) return IndexToId(cx, index, id); } +JS_PUBLIC_API(JSBool) +JS_IsIdentifier(JSContext *cx, JSString *str, JSBool *isIdentifier) +{ + assertSameCompartment(cx, str); + + JSLinearString* linearStr = str->ensureLinear(cx); + if (!linearStr) + return false; + + *isIdentifier = js::IsIdentifier(linearStr); + return true; +} + #ifdef JS_THREADSAFE static PRStatus CallOnce(void *func) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 9c6aeff6710..4c0ab2311e4 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -5020,6 +5020,12 @@ JS_ScheduleGC(JSContext *cx, uint32 count, JSBool compartment); extern JS_PUBLIC_API(JSBool) JS_IndexToId(JSContext *cx, uint32 index, jsid *id); +/* + * Test if the given string is a valid ECMAScript identifier + */ +extern JS_PUBLIC_API(JSBool) +JS_IsIdentifier(JSContext *cx, JSString *str, JSBool *isIdentifier); + JS_END_EXTERN_C #endif /* jsapi_h___ */