Bug 694138: Support an array of strings as keyPath on objectStores and indexes. r=janv/bent
authorJonas Sicking <jonas@sicking.cc>
Tue, 20 Dec 2011 02:59:07 -0800
changeset 85023 0ad9c268e932154e015ac102b95fa008cfb77dd3
parent 85022 214af12ee22d089f9fae7fc9e8734de721d4e4af
child 85092 32d762c16927bab0e90e96e40e40d99bb9949fe2
push id114
push userffxbld
push dateFri, 09 Mar 2012 01:01:18 +0000
treeherdermozilla-release@c081ebf13261 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjanv, bent
bugs694138
milestone11.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 694138: Support an array of strings as keyPath on objectStores and indexes. r=janv/bent
dom/indexedDB/DatabaseInfo.cpp
dom/indexedDB/DatabaseInfo.h
dom/indexedDB/IDBDatabase.cpp
dom/indexedDB/IDBFactory.cpp
dom/indexedDB/IDBIndex.cpp
dom/indexedDB/IDBIndex.h
dom/indexedDB/IDBObjectStore.cpp
dom/indexedDB/IDBObjectStore.h
dom/indexedDB/Key.cpp
dom/indexedDB/Key.h
dom/indexedDB/OpenDatabaseHelper.cpp
dom/indexedDB/nsIIDBIndex.idl
dom/indexedDB/nsIIDBObjectStore.idl
dom/indexedDB/test/helpers.js
dom/indexedDB/test/test_complex_keyPaths.html
dom/indexedDB/test/test_create_index.html
dom/indexedDB/test/test_create_objectStore.html
dom/indexedDB/test/test_keys.html
--- a/dom/indexedDB/DatabaseInfo.cpp
+++ b/dom/indexedDB/DatabaseInfo.cpp
@@ -112,16 +112,17 @@ IndexInfo::IndexInfo()
 {
   MOZ_COUNT_CTOR(IndexInfo);
 }
 
 IndexInfo::IndexInfo(const IndexInfo& aOther)
 : id(aOther.id),
   name(aOther.name),
   keyPath(aOther.keyPath),
+  keyPathArray(aOther.keyPathArray),
   unique(aOther.unique),
   multiEntry(aOther.multiEntry)
 {
   MOZ_COUNT_CTOR(IndexInfo);
 }
 
 IndexInfo::~IndexInfo()
 {
--- a/dom/indexedDB/DatabaseInfo.h
+++ b/dom/indexedDB/DatabaseInfo.h
@@ -116,16 +116,17 @@ struct IndexInfo
 #else
   IndexInfo()
   : id(LL_MININT), unique(false), multiEntry(false) { }
 #endif
 
   PRInt64 id;
   nsString name;
   nsString keyPath;
+  nsTArray<nsString> keyPathArray;
   bool unique;
   bool multiEntry;
 };
 
 struct ObjectStoreInfo
 {
 #ifdef NS_BUILD_REFCNT_LOGGING
   ObjectStoreInfo();
@@ -143,16 +144,17 @@ private:
   ~ObjectStoreInfo() {}
 #endif
 public:
 
   // Constant members, can be gotten on any thread
   nsString name;
   PRInt64 id;
   nsString keyPath;
+  nsTArray<nsString> keyPathArray;
 
   // Main-thread only members. This must *not* be touced on the database thread
   nsTArray<IndexInfo> indexes;
 
   // Database-thread members. After the ObjectStoreInfo has been initialized,
   // these can *only* be touced on the database thread.
   PRInt64 nextAutoIncrementId;
   PRInt64 comittedAutoIncrementId;
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -383,45 +383,85 @@ IDBDatabase::CreateObjectStore(const nsA
       transaction->Mode() != nsIIDBTransaction::VERSION_CHANGE) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
   DatabaseInfo* databaseInfo = transaction->DBInfo();
 
   nsString keyPath;
   keyPath.SetIsVoid(true);
+  nsTArray<nsString> keyPathArray;
   bool autoIncrement = false;
 
   if (!JSVAL_IS_VOID(aOptions) && !JSVAL_IS_NULL(aOptions)) {
     if (JSVAL_IS_PRIMITIVE(aOptions)) {
       // XXX This isn't the right error
       return NS_ERROR_DOM_TYPE_ERR;
     }
 
     NS_ASSERTION(JSVAL_IS_OBJECT(aOptions), "Huh?!");
     JSObject* options = JSVAL_TO_OBJECT(aOptions);
 
+    // Get keyPath
     jsval val;
     if (!JS_GetPropertyById(aCx, options, nsDOMClassInfo::sKeyPath_id, &val)) {
       NS_WARNING("JS_GetPropertyById failed!");
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
 
     if (!JSVAL_IS_VOID(val) && !JSVAL_IS_NULL(val)) {
-      JSString* str = JS_ValueToString(aCx, val);
-      if (!str) {
-        NS_WARNING("JS_ValueToString failed!");
-        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+      if (!JSVAL_IS_PRIMITIVE(val) &&
+          JS_IsArrayObject(aCx, JSVAL_TO_OBJECT(val))) {
+    
+        JSObject* obj = JSVAL_TO_OBJECT(val);
+    
+        jsuint length;
+        if (!JS_GetArrayLength(aCx, obj, &length)) {
+          return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+        }
+    
+        if (!length) {
+          return NS_ERROR_DOM_SYNTAX_ERR;
+        }
+    
+        keyPathArray.SetCapacity(length);
+    
+        for (jsuint index = 0; index < length; index++) {
+          jsval val;
+          JSString* jsstr;
+          nsDependentJSString str;
+          if (!JS_GetElement(aCx, obj, index, &val) ||
+              !(jsstr = JS_ValueToString(aCx, val)) ||
+              !str.init(aCx, jsstr)) {
+            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+          }
+    
+          if (!IDBObjectStore::IsValidKeyPath(aCx, str)) {
+            return NS_ERROR_DOM_SYNTAX_ERR;
+          }
+    
+          keyPathArray.AppendElement(str);
+        }
+    
+        NS_ASSERTION(!keyPathArray.IsEmpty(), "This shouldn't have happened!");
       }
-      nsDependentJSString dependentKeyPath;
-      if (!dependentKeyPath.init(aCx, str)) {
-        NS_WARNING("Initializing keyPath failed!");
-        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+      else {
+        JSString* jsstr;
+        nsDependentJSString str;
+        if (!(jsstr = JS_ValueToString(aCx, val)) ||
+            !str.init(aCx, jsstr)) {
+          return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+        }
+    
+        if (!IDBObjectStore::IsValidKeyPath(aCx, str)) {
+          return NS_ERROR_DOM_SYNTAX_ERR;
+        }
+    
+        keyPath = str;
       }
-      keyPath = dependentKeyPath;
     }
 
     if (!JS_GetPropertyById(aCx, options, nsDOMClassInfo::sAutoIncrement_id,
                             &val)) {
       NS_WARNING("JS_GetPropertyById failed!");
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
 
@@ -432,30 +472,27 @@ IDBDatabase::CreateObjectStore(const nsA
     }
     autoIncrement = !!boolVal;
   }
 
   if (databaseInfo->ContainsStoreName(aName)) {
     return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
   }
 
-  if (!keyPath.IsVoid()) {
-    if (keyPath.IsEmpty() && autoIncrement) {
-      return NS_ERROR_DOM_INVALID_ACCESS_ERR;
-    }
-    if (!IDBObjectStore::IsValidKeyPath(aCx, keyPath)) {
-      return NS_ERROR_DOM_SYNTAX_ERR;
-    }
+  if (autoIncrement &&
+      ((!keyPath.IsVoid() && keyPath.IsEmpty()) || !keyPathArray.IsEmpty())) {
+    return NS_ERROR_DOM_INVALID_ACCESS_ERR;
   }
 
   nsRefPtr<ObjectStoreInfo> newInfo(new ObjectStoreInfo());
 
   newInfo->name = aName;
   newInfo->id = databaseInfo->nextObjectStoreId++;
   newInfo->keyPath = keyPath;
+  newInfo->keyPathArray = keyPathArray;
   newInfo->nextAutoIncrementId = autoIncrement ? 1 : 0;
   newInfo->comittedAutoIncrementId = newInfo->nextAutoIncrementId;
 
   if (!databaseInfo->PutObjectStore(newInfo)) {
     NS_WARNING("Put failed!");
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
@@ -741,21 +778,40 @@ CreateObjectStoreHelper::DoDatabaseWork(
 
   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("auto_increment"),
                              mObjectStore->IsAutoIncrement() ? 1 : 0);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mObjectStore->Name());
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
-  rv = mObjectStore->HasKeyPath() ?
-    stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
-                           mObjectStore->KeyPath()) :
-    stmt->BindNullByName(NS_LITERAL_CSTRING("key_path"));
-  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  if (mObjectStore->UsesKeyPathArray()) {
+    // We use a comma in the beginning to indicate that it's an array of
+    // key paths. This is to be able to tell a string-keypath from an
+    // array-keypath which contains only one item.
+    // It also makes serializing easier :-)
+    nsAutoString keyPath;
+    const nsTArray<nsString>& keyPaths = mObjectStore->KeyPathArray();
+    for (PRUint32 i = 0; i < keyPaths.Length(); ++i) {
+      keyPath.Append(NS_LITERAL_STRING(",") + keyPaths[i]);
+    }
+    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
+                                keyPath);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  }
+  else if (mObjectStore->HasKeyPath()) {
+    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
+                                mObjectStore->KeyPath());
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  }
+  else {
+    rv = stmt->BindNullByName(NS_LITERAL_CSTRING("key_path"));
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  }
+
 
   rv = stmt->Execute();
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   return NS_OK;
 }
 
 nsresult
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -44,16 +44,17 @@
 #include "nsILocalFile.h"
 #include "nsIScriptContext.h"
 
 #include "mozilla/storage.h"
 #include "mozilla/dom/ContentChild.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIScriptSecurityManager.h"
+#include "nsCharSeparatedTokenizer.h"
 #include "nsContentUtils.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDOMClassInfoID.h"
 #include "nsIPrincipal.h"
 #include "nsHashKeys.h"
 #include "nsPIDOMWindow.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
@@ -197,16 +198,23 @@ IDBFactory::GetDirectoryForOrigin(const 
 
   rv = directory->Append(originSanitized);
   NS_ENSURE_SUCCESS(rv, rv);
 
   directory.forget(aDirectory);
   return NS_OK;
 }
 
+inline
+bool
+IgnoreWhitespace(PRUnichar c)
+{
+  return false;
+}
+
 // static
 nsresult
 IDBFactory::LoadDatabaseInformation(mozIStorageConnection* aConnection,
                                     nsIAtom* aDatabaseId,
                                     PRUint64* aVersion,
                                     ObjectStoreInfoArray& aObjectStores)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
@@ -240,18 +248,37 @@ IDBFactory::LoadDatabaseInformation(mozI
     nsresult rv = stmt->GetTypeOfIndex(2, &columnType);
     NS_ENSURE_SUCCESS(rv, rv);
     if (columnType == mozIStorageStatement::VALUE_TYPE_NULL) {
       info->keyPath.SetIsVoid(true);
     }
     else {
       NS_ASSERTION(columnType == mozIStorageStatement::VALUE_TYPE_TEXT,
                    "Should be a string");
-      rv = stmt->GetString(2, info->keyPath);
+      nsString keyPath;
+      rv = stmt->GetString(2, keyPath);
       NS_ENSURE_SUCCESS(rv, rv);
+
+      if (!keyPath.IsEmpty() && keyPath.First() == ',') {
+        // We use a comma in the beginning to indicate that it's an array of
+        // key paths. This is to be able to tell a string-keypath from an
+        // array-keypath which contains only one item.
+        nsCharSeparatedTokenizerTemplate<IgnoreWhitespace>
+          tokenizer(keyPath, ',');
+        tokenizer.nextToken();
+        while (tokenizer.hasMoreTokens()) {
+          info->keyPathArray.AppendElement(tokenizer.nextToken());
+        }
+        NS_ASSERTION(!info->keyPathArray.IsEmpty(),
+                     "Should have at least one keypath");
+      }
+      else {
+        info->keyPath = keyPath;
+      }
+
     }
 
     info->nextAutoIncrementId = stmt->AsInt64(3);
     info->comittedAutoIncrementId = info->nextAutoIncrementId;
 
     ObjectStoreInfoMap* mapEntry = infoMap.AppendElement();
     NS_ENSURE_TRUE(mapEntry, NS_ERROR_OUT_OF_MEMORY);
 
@@ -286,18 +313,35 @@ IDBFactory::LoadDatabaseInformation(mozI
     IndexInfo* indexInfo = objectStoreInfo->indexes.AppendElement();
     NS_ENSURE_TRUE(indexInfo, NS_ERROR_OUT_OF_MEMORY);
 
     indexInfo->id = stmt->AsInt64(1);
 
     rv = stmt->GetString(2, indexInfo->name);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = stmt->GetString(3, indexInfo->keyPath);
+    nsString keyPath;
+    rv = stmt->GetString(3, keyPath);
     NS_ENSURE_SUCCESS(rv, rv);
+    if (!keyPath.IsEmpty() && keyPath.First() == ',') {
+      // We use a comma in the beginning to indicate that it's an array of
+      // key paths. This is to be able to tell a string-keypath from an
+      // array-keypath which contains only one item.
+      nsCharSeparatedTokenizerTemplate<IgnoreWhitespace>
+        tokenizer(keyPath, ',');
+      tokenizer.nextToken();
+      while (tokenizer.hasMoreTokens()) {
+        indexInfo->keyPathArray.AppendElement(tokenizer.nextToken());
+      }
+      NS_ASSERTION(!indexInfo->keyPathArray.IsEmpty(),
+                   "Should have at least one keypath");
+    }
+    else {
+      indexInfo->keyPath = keyPath;
+    }
 
     indexInfo->unique = !!stmt->AsInt32(4);
     indexInfo->multiEntry = !!stmt->AsInt32(5);
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Load version information.
   rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
--- a/dom/indexedDB/IDBIndex.cpp
+++ b/dom/indexedDB/IDBIndex.cpp
@@ -43,16 +43,18 @@
 
 #include "nsIIDBKeyRange.h"
 #include "nsIJSContextStack.h"
 
 #include "nsDOMClassInfoID.h"
 #include "nsEventDispatcher.h"
 #include "nsThreadUtils.h"
 #include "mozilla/storage.h"
+#include "xpcprivate.h"
+#include "XPCQuickStubs.h"
 
 #include "AsyncConnectionHelper.h"
 #include "IDBCursor.h"
 #include "IDBEvents.h"
 #include "IDBKeyRange.h"
 #include "IDBObjectStore.h"
 #include "IDBTransaction.h"
 #include "DatabaseInfo.h"
@@ -314,16 +316,17 @@ IDBIndex::Create(IDBObjectStore* aObject
 
   index->mScriptContext = database->ScriptContext();
   index->mOwner = database->Owner();
 
   index->mObjectStore = aObjectStore;
   index->mId = aIndexInfo->id;
   index->mName = aIndexInfo->name;
   index->mKeyPath = aIndexInfo->keyPath;
+  index->mKeyPathArray = aIndexInfo->keyPathArray;
   index->mUnique = aIndexInfo->unique;
   index->mMultiEntry = aIndexInfo->multiEntry;
 
   return index.forget();
 }
 
 IDBIndex::IDBIndex()
 : mId(LL_MININT),
@@ -375,21 +378,48 @@ NS_IMETHODIMP
 IDBIndex::GetStoreName(nsAString& aStoreName)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   return mObjectStore->GetName(aStoreName);
 }
 
 NS_IMETHODIMP
-IDBIndex::GetKeyPath(nsAString& aKeyPath)
+IDBIndex::GetKeyPath(JSContext* aCx,
+                     jsval* aVal)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  aKeyPath.Assign(mKeyPath);
+  if (UsesKeyPathArray()) {
+    JSObject* array = JS_NewArrayObject(aCx, mKeyPathArray.Length(), nsnull);
+    if (!array) {
+      NS_WARNING("Failed to make array!");
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    }
+
+    for (PRUint32 i = 0; i < mKeyPathArray.Length(); ++i) {
+      jsval val;
+      nsString tmp(mKeyPathArray[i]);
+      if (!xpc_qsStringToJsval(aCx, tmp, &val)) {
+        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+      }
+
+      if (!JS_SetElement(aCx, array, i, &val)) {
+        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+      }
+    }
+
+    *aVal = OBJECT_TO_JSVAL(array);
+  }
+  else {
+    nsString tmp(mKeyPath);
+    if (!xpc_qsStringToJsval(aCx, tmp, aVal)) {
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    }
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBIndex::GetUnique(bool* aUnique)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
--- a/dom/indexedDB/IDBIndex.h
+++ b/dom/indexedDB/IDBIndex.h
@@ -92,27 +92,38 @@ public:
     return mMultiEntry;
   }
 
   const nsString& KeyPath() const
   {
     return mKeyPath;
   }
 
+  bool UsesKeyPathArray() const
+  {
+    return !mKeyPathArray.IsEmpty();
+  }
+  
+  const nsTArray<nsString>& KeyPathArray() const
+  {
+    return mKeyPathArray;
+  }
+
 private:
   IDBIndex();
   ~IDBIndex();
 
   nsRefPtr<IDBObjectStore> mObjectStore;
 
   nsCOMPtr<nsIScriptContext> mScriptContext;
   nsCOMPtr<nsPIDOMWindow> mOwner;
 
   PRInt64 mId;
   nsString mName;
   nsString mKeyPath;
+  nsTArray<nsString> mKeyPathArray;
   bool mUnique;
   bool mMultiEntry;
 };
 
 END_INDEXEDDB_NAMESPACE
 
 #endif // mozilla_dom_indexeddb_idbindex_h__
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -471,16 +471,42 @@ GetKeyFromValue(JSContext* aCx,
   if (NS_FAILED(aKey.SetFromJSVal(aCx, key))) {
     aKey.Unset();
   }
 
   return NS_OK;
 }
 
 inline
+nsresult
+GetKeyFromValue(JSContext* aCx,
+                jsval aVal,
+                const nsTArray<nsString>& aKeyPathArray,
+                Key& aKey)
+{
+  NS_ASSERTION(!aKeyPathArray.IsEmpty(),
+               "Should not use empty keyPath array");
+  for (PRUint32 i = 0; i < aKeyPathArray.Length(); ++i) {
+    jsval key;
+    nsresult rv = GetJSValFromKeyPath(aCx, aVal, aKeyPathArray[i], key);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (NS_FAILED(aKey.AppendArrayItem(aCx, i == 0, key))) {
+      NS_ASSERTION(aKey.IsUnset(), "Encoding error should unset");
+      return NS_OK;
+    }
+  }
+
+  aKey.FinishArray();
+
+  return NS_OK;
+}
+
+
+inline
 already_AddRefed<IDBRequest>
 GenerateRequest(IDBObjectStore* aObjectStore)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   IDBDatabase* database = aObjectStore->Transaction()->Database();
   return IDBRequest::Create(aObjectStore, database->ScriptContext(),
                             database->Owner(), aObjectStore->Transaction());
 }
@@ -508,16 +534,17 @@ IDBObjectStore::Create(IDBTransaction* a
 
   objectStore->mScriptContext = aTransaction->Database()->ScriptContext();
   objectStore->mOwner = aTransaction->Database()->Owner();
 
   objectStore->mTransaction = aTransaction;
   objectStore->mName = aStoreInfo->name;
   objectStore->mId = aStoreInfo->id;
   objectStore->mKeyPath = aStoreInfo->keyPath;
+  objectStore->mKeyPathArray = aStoreInfo->keyPathArray;
   objectStore->mAutoIncrement = !!aStoreInfo->nextAutoIncrementId;
   objectStore->mDatabaseId = aDatabaseId;
   objectStore->mInfo = aStoreInfo;
 
   return objectStore.forget();
 }
 
 // static
@@ -559,24 +586,41 @@ IDBObjectStore::IsValidKeyPath(JSContext
 
   return true;
 }
 
 // static
 nsresult
 IDBObjectStore::AppendIndexUpdateInfo(PRInt64 aIndexID,
                                       const nsAString& aKeyPath,
+                                      const nsTArray<nsString>& aKeyPathArray,
                                       bool aUnique,
                                       bool aMultiEntry,
                                       JSContext* aCx,
-                                      jsval aObject,
+                                      jsval aVal,
                                       nsTArray<IndexUpdateInfo>& aUpdateInfoArray)
 {
+  nsresult rv;
+  if (!aKeyPathArray.IsEmpty()) {
+    Key arrayKey;
+    rv = GetKeyFromValue(aCx, aVal, aKeyPathArray, arrayKey);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (!arrayKey.IsUnset()) {
+      IndexUpdateInfo* updateInfo = aUpdateInfoArray.AppendElement();
+      updateInfo->indexId = aIndexID;
+      updateInfo->indexUnique = aUnique;
+      updateInfo->value = arrayKey;
+    }
+
+    return NS_OK;
+  }
+
   jsval key;
-  nsresult rv = GetJSValFromKeyPath(aCx, aObject, aKeyPath, key);
+  rv = GetJSValFromKeyPath(aCx, aVal, aKeyPath, key);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (aMultiEntry && !JSVAL_IS_PRIMITIVE(key) &&
       JS_IsArrayObject(aCx, JSVAL_TO_OBJECT(key))) {
     JSObject* array = JSVAL_TO_OBJECT(key);
     jsuint arrayLength;
     if (!JS_GetArrayLength(aCx, array, &arrayLength)) {
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
@@ -1123,35 +1167,41 @@ IDBObjectStore::GetAddInfo(JSContext* aC
   if (!HasKeyPath()) {
     // Out-of-line keys must be passed in.
     rv = aKey.SetFromJSVal(aCx, aKeyVal);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   else if (!mAutoIncrement) {
     // Inline keys live on the object. Make sure that the value passed in is an
     // object.
-    rv = GetKeyFromValue(aCx, aValue, mKeyPath, aKey);
+    if (UsesKeyPathArray()) {
+      rv = GetKeyFromValue(aCx, aValue, mKeyPathArray, aKey);
+    }
+    else {
+      rv = GetKeyFromValue(aCx, aValue, mKeyPath, aKey);
+    }
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Return DATA_ERR if no key was specified this isn't an autoIncrement
   // objectStore.
   if (aKey.IsUnset() && !mAutoIncrement) {
     return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
   }
 
   // Figure out indexes and the index values to update here.
   PRUint32 count = mInfo->indexes.Length();
   aUpdateInfoArray.SetCapacity(count); // Pretty good estimate
   for (PRUint32 indexesIndex = 0; indexesIndex < count; indexesIndex++) {
     const IndexInfo& indexInfo = mInfo->indexes[indexesIndex];
 
     rv = AppendIndexUpdateInfo(indexInfo.id, indexInfo.keyPath,
-                               indexInfo.unique, indexInfo.multiEntry,
-                               aCx, aValue, aUpdateInfoArray);
+                               indexInfo.keyPathArray, indexInfo.unique,
+                               indexInfo.multiEntry, aCx, aValue,
+                               aUpdateInfoArray);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   nsString targetObjectPropName;
   JSObject* targetObject = nsnull;
 
   rv = NS_OK;
   if (mAutoIncrement && HasKeyPath()) {
@@ -1359,21 +1409,48 @@ IDBObjectStore::GetName(nsAString& aName
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   aName.Assign(mName);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-IDBObjectStore::GetKeyPath(nsAString& aKeyPath)
+IDBObjectStore::GetKeyPath(JSContext* aCx,
+                           jsval* aVal)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  aKeyPath.Assign(mKeyPath);
+  if (UsesKeyPathArray()) {
+    JSObject* array = JS_NewArrayObject(aCx, mKeyPathArray.Length(), nsnull);
+    if (!array) {
+      NS_WARNING("Failed to make array!");
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    }
+
+    for (PRUint32 i = 0; i < mKeyPathArray.Length(); ++i) {
+      jsval val;
+      nsString tmp(mKeyPathArray[i]);
+      if (!xpc_qsStringToJsval(aCx, tmp, &val)) {
+        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+      }
+
+      if (!JS_SetElement(aCx, array, i, &val)) {
+        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+      }
+    }
+
+    *aVal = OBJECT_TO_JSVAL(array);
+  }
+  else {
+    nsString tmp(mKeyPath);
+    if (!xpc_qsStringToJsval(aCx, tmp, aVal)) {
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    }
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBObjectStore::GetTransaction(nsIIDBTransaction** aTransaction)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
@@ -1608,27 +1685,79 @@ IDBObjectStore::OpenCursor(const jsval& 
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   request.forget(_retval);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBObjectStore::CreateIndex(const nsAString& aName,
-                            const nsAString& aKeyPath,
+                            const jsval& aKeyPath,
                             const jsval& aOptions,
                             JSContext* aCx,
                             nsIIDBIndex** _retval)
 {
   NS_PRECONDITION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!IsValidKeyPath(aCx, aKeyPath)) {
-    return NS_ERROR_DOM_SYNTAX_ERR;
+  // Get KeyPath
+  nsString keyPath;
+  nsTArray<nsString> keyPathArray;
+
+  // See if this is a JS array.
+  if (!JSVAL_IS_PRIMITIVE(aKeyPath) &&
+      JS_IsArrayObject(aCx, JSVAL_TO_OBJECT(aKeyPath))) {
+
+    JSObject* obj = JSVAL_TO_OBJECT(aKeyPath);
+
+    jsuint length;
+    if (!JS_GetArrayLength(aCx, obj, &length)) {
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    }
+
+    if (!length) {
+      return NS_ERROR_DOM_SYNTAX_ERR;
+    }
+
+    keyPathArray.SetCapacity(length);
+
+    for (jsuint index = 0; index < length; index++) {
+      jsval val;
+      JSString* jsstr;
+      nsDependentJSString str;
+      if (!JS_GetElement(aCx, obj, index, &val) ||
+          !(jsstr = JS_ValueToString(aCx, val)) ||
+          !str.init(aCx, jsstr)) {
+        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+      }
+
+      if (!IsValidKeyPath(aCx, str)) {
+        return NS_ERROR_DOM_SYNTAX_ERR;
+      }
+
+      keyPathArray.AppendElement(str);
+    }
+
+    NS_ASSERTION(!keyPathArray.IsEmpty(), "This shouldn't have happened!");
   }
-
+  else {
+    JSString* jsstr;
+    nsDependentJSString str;
+    if (!(jsstr = JS_ValueToString(aCx, aKeyPath)) ||
+        !str.init(aCx, jsstr)) {
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    }
+
+    if (!IsValidKeyPath(aCx, str)) {
+      return NS_ERROR_DOM_SYNTAX_ERR;
+    }
+
+    keyPath = str;
+  }
+
+  // Check name and current mode
   IDBTransaction* transaction = AsyncConnectionHelper::GetCurrentTransaction();
 
   if (!transaction ||
       transaction != mTransaction ||
       mTransaction->Mode() != nsIIDBTransaction::VERSION_CHANGE) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
@@ -1680,22 +1809,27 @@ IDBObjectStore::CreateIndex(const nsAStr
 
     if (!JS_ValueToBoolean(aCx, val, &boolVal)) {
       NS_WARNING("JS_ValueToBoolean failed!");
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
     multiEntry = !!boolVal;
   }
 
+  if (multiEntry && !keyPathArray.IsEmpty()) {
+    return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+  }
+
   DatabaseInfo* databaseInfo = mTransaction->DBInfo();
 
   IndexInfo* indexInfo = mInfo->indexes.AppendElement();
   indexInfo->id = databaseInfo->nextIndexId++;
   indexInfo->name = aName;
-  indexInfo->keyPath = aKeyPath;
+  indexInfo->keyPath = keyPath;
+  indexInfo->keyPathArray.SwapElements(keyPathArray);
   indexInfo->unique = unique;
   indexInfo->multiEntry = multiEntry;
 
   // Don't leave this in the list if we fail below!
   AutoRemoveIndex autoRemove(mInfo, aName);
 
 #ifdef DEBUG
   for (PRUint32 index = 0; index < mCreatedIndexes.Length(); index++) {
@@ -1907,17 +2041,16 @@ AddHelper::DoDatabaseWork(mozIStorageCon
       autoIncrementNum = mObjectStore->Info()->nextAutoIncrementId;
       if (autoIncrementNum > (1LL << 53)) {
         return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
       }
       mKey.SetFromInteger(autoIncrementNum);
     }
     else if (mKey.IsFloat() &&
              mKey.ToFloat() >= mObjectStore->Info()->nextAutoIncrementId) {
-      // XXX Once we support floats, we should use floor(mKey.ToFloat()) here
       autoIncrementNum = floor(mKey.ToFloat());
     }
 
     if (keyUnset && !keyPath.IsEmpty()) {
       // Special case where someone put an object into an autoIncrement'ing
       // objectStore with no key in its keyPath set. We needed to figure out
       // which row id we would get above before we could set that properly.
 
@@ -2418,19 +2551,35 @@ CreateIndexHelper::DoDatabaseWork(mozISt
 
   nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"),
                                       mIndex->Id());
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mIndex->Name());
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
-  rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
-                              mIndex->KeyPath());
-  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  if (mIndex->UsesKeyPathArray()) {
+    // We use a comma in the beginning to indicate that it's an array of
+    // key paths. This is to be able to tell a string-keypath from an
+    // array-keypath which contains only one item.
+    // It also makes serializing easier :-)
+    nsAutoString keyPath;
+    const nsTArray<nsString>& keyPaths = mIndex->KeyPathArray();
+    for (PRUint32 i = 0; i < keyPaths.Length(); ++i) {
+      keyPath.Append(NS_LITERAL_STRING(",") + keyPaths[i]);
+    }
+    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
+                                keyPath);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  }
+  else {
+    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
+                                mIndex->KeyPath());
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  }
 
   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("unique"),
                              mIndex->IsUnique() ? 1 : 0);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("multientry"),
                              mIndex->IsMultiEntry() ? 1 : 0);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
@@ -2516,16 +2665,17 @@ CreateIndexHelper::InsertDataFromObjectS
     if (!buffer.read(cx, &clone, &callbacks, &cloneReadInfo)) {
       NS_WARNING("Failed to deserialize structured clone data!");
       return NS_ERROR_DOM_DATA_CLONE_ERR;
     }
 
     nsTArray<IndexUpdateInfo> updateInfo;
     rv = IDBObjectStore::AppendIndexUpdateInfo(mIndex->Id(),
                                                mIndex->KeyPath(),
+                                               mIndex->KeyPathArray(),
                                                mIndex->IsUnique(),
                                                mIndex->IsMultiEntry(),
                                                tlsEntry->Context(),
                                                clone, updateInfo);
     NS_ENSURE_SUCCESS(rv, rv);
 
     PRInt64 objectDataID = stmt->AsInt64(0);
 
--- a/dom/indexedDB/IDBObjectStore.h
+++ b/dom/indexedDB/IDBObjectStore.h
@@ -76,16 +76,17 @@ public:
          nsIAtom* aDatabaseId);
 
   static bool
   IsValidKeyPath(JSContext* aCx, const nsAString& aKeyPath);
 
   static nsresult
   AppendIndexUpdateInfo(PRInt64 aIndexID,
                         const nsAString& aKeyPath,
+                        const nsTArray<nsString>& aKeyPathArray,
                         bool aUnique,
                         bool aMultiEntry,
                         JSContext* aCx,
                         jsval aObject,
                         nsTArray<IndexUpdateInfo>& aUpdateInfoArray);
 
   static nsresult
   UpdateIndexes(IDBTransaction* aTransaction,
@@ -154,17 +155,27 @@ public:
 
   const nsString& KeyPath() const
   {
     return mKeyPath;
   }
 
   const bool HasKeyPath() const
   {
-    return !mKeyPath.IsVoid();
+    return !mKeyPath.IsVoid() || !mKeyPathArray.IsEmpty();
+  }
+
+  bool UsesKeyPathArray() const
+  {
+    return !mKeyPathArray.IsEmpty();
+  }
+  
+  const nsTArray<nsString>& KeyPathArray() const
+  {
+    return mKeyPathArray;
   }
 
   IDBTransaction* Transaction()
   {
     return mTransaction;
   }
 
   ObjectStoreInfo* Info()
@@ -194,16 +205,17 @@ private:
   nsRefPtr<IDBTransaction> mTransaction;
 
   nsCOMPtr<nsIScriptContext> mScriptContext;
   nsCOMPtr<nsPIDOMWindow> mOwner;
 
   PRInt64 mId;
   nsString mName;
   nsString mKeyPath;
+  nsTArray<nsString> mKeyPathArray;
   bool mAutoIncrement;
   nsCOMPtr<nsIAtom> mDatabaseId;
   nsRefPtr<ObjectStoreInfo> mInfo;
   PRUint32 mStructuredCloneVersion;
 
   nsTArray<nsRefPtr<IDBIndex> > mCreatedIndexes;
 };
 
--- a/dom/indexedDB/Key.cpp
+++ b/dom/indexedDB/Key.cpp
@@ -15,17 +15,18 @@
  *
  * The Original Code is Indexed Database.
  *
  * The Initial Developer of the Original Code is The Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
- *   Ben Turner <bent.mozilla@gmail.com>
+ *   Jan Varga <Jan.Varga@gmail.com>
+ *   Jonas Sicking <jonas@sicking.cc>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
--- a/dom/indexedDB/Key.h
+++ b/dom/indexedDB/Key.h
@@ -186,16 +186,38 @@ public:
     NS_ENSURE_SUCCESS(rv, rv);
 
     NS_ASSERTION(pos >= BufferEnd(),
                  "Didn't consume whole buffer");
 
     return NS_OK;
   }
 
+  nsresult AppendArrayItem(JSContext* aCx,
+                           bool aFirst,
+                           const jsval aVal)
+  {
+    if (aFirst) {
+      Unset();
+    }
+
+    nsresult rv = EncodeJSVal(aCx, aVal, aFirst ? eMaxType : 0);
+    if (NS_FAILED(rv)) {
+      Unset();
+      return rv;
+    }
+
+    return NS_OK;
+  }
+
+  void FinishArray()
+  {
+    TrimBuffer();
+  }
+
   const nsCString& GetBuffer() const
   {
     return mBuffer;
   }
 
   nsresult BindToStatement(mozIStorageStatement* aStatement,
                            const nsACString& aParamName) const
   {
--- a/dom/indexedDB/OpenDatabaseHelper.cpp
+++ b/dom/indexedDB/OpenDatabaseHelper.cpp
@@ -211,18 +211,18 @@ CreateTables(mozIStorageConnection* aDBC
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Table `object_data`
   rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "CREATE TABLE object_data ("
       "id INTEGER PRIMARY KEY, "
       "object_store_id INTEGER NOT NULL, "
       "key_value BLOB DEFAULT NULL, "
+      "file_ids TEXT, "
       "data BLOB NOT NULL, "
-      "file_ids TEXT, "
       "UNIQUE (object_store_id, key_value), "
       "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
         "CASCADE"
     ");"
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Table `index`
@@ -1163,28 +1163,28 @@ UpgradeSchemaFrom11_0To12_0(mozIStorageC
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "CREATE TABLE object_data ("
       "id INTEGER PRIMARY KEY, "
       "object_store_id INTEGER NOT NULL, "
       "key_value BLOB DEFAULT NULL, "
+      "file_ids TEXT, "
       "data BLOB NOT NULL, "
-      "file_ids TEXT, "
       "UNIQUE (object_store_id, key_value), "
       "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
         "CASCADE"
     ");"
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "INSERT INTO object_data "
-      "SELECT id, object_store_id, key_value, data, file_ids "
+      "SELECT id, object_store_id, key_value, file_ids, data "
       "FROM temp_upgrade;"
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "DROP TABLE temp_upgrade;"
   ));
   NS_ENSURE_SUCCESS(rv, rv);
@@ -1254,17 +1254,17 @@ UpgradeSchemaFrom11_0To12_0(mozIStorageC
         "CASCADE, "
       "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
         "CASCADE"
     ");"
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "INSERT OR IGNORE INTO index_data "
+    "INSERT INTO index_data "
       "SELECT index_id, value, object_data_key, object_data_id "
       "FROM temp_upgrade;"
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "DROP TABLE temp_upgrade;"
   ));
--- a/dom/indexedDB/nsIIDBIndex.idl
+++ b/dom/indexedDB/nsIIDBIndex.idl
@@ -42,24 +42,25 @@
 interface nsIIDBObjectStore;
 interface nsIIDBRequest;
 
 /**
  * IDBIndex interface.  See
  * http://dev.w3.org/2006/webapi/WebSimpleDB/#idl-def-IDBIndex for more
  * information.
  */
-[scriptable, builtinclass, uuid(fcb9a158-833e-4aa9-ab19-ab90cbb50afc)]
+[scriptable, builtinclass, uuid(233ec586-7b34-4263-b27e-a4991b757597)]
 interface nsIIDBIndex : nsISupports
 {
   readonly attribute DOMString name;
 
   readonly attribute DOMString storeName;
 
-  readonly attribute DOMString keyPath;
+  [implicit_jscontext]
+  readonly attribute jsval keyPath;
 
   readonly attribute boolean unique;
 
   readonly attribute boolean multiEntry;
 
   readonly attribute nsIIDBObjectStore objectStore;
 
   [implicit_jscontext]
--- a/dom/indexedDB/nsIIDBObjectStore.idl
+++ b/dom/indexedDB/nsIIDBObjectStore.idl
@@ -45,22 +45,23 @@ interface nsIIDBRequest;
 interface nsIIDBTransaction;
 interface nsIDOMDOMStringList;
 
 /**
  * nsIIDBObjectStore interface.  See
  * http://dev.w3.org/2006/webapi/WebSimpleDB/#idl-def-nsIIDBObjectStore
  * for more information.
  */
-[scriptable, builtinclass, uuid(e93c5ca4-89da-4eb4-b839-271ba4f65a27)]
+[scriptable, builtinclass, uuid(e0d308ea-b804-4962-918a-28ec0aa4e42b)]
 interface nsIIDBObjectStore : nsISupports
 {
   readonly attribute DOMString name;
 
-  readonly attribute DOMString keyPath;
+  [implicit_jscontext]
+  readonly attribute jsval keyPath;
 
   readonly attribute nsIDOMDOMStringList indexNames;
 
   readonly attribute nsIIDBTransaction transaction;
 
   readonly attribute boolean autoIncrement;
 
   // Success fires IDBTransactionEvent, result == value for key
@@ -106,17 +107,17 @@ interface nsIIDBObjectStore : nsISupport
    * Optional arguments:
    *   - unique (boolean):
    *       Specifies whether values in the index must be unique. Defaults to
    *       false.
    */
   [implicit_jscontext]
   nsIIDBIndex
   createIndex([Null(Stringify)] in DOMString name,
-              [Null(Stringify)] in DOMString keyPath,
+              in jsval keyPath,
               [optional /* none */] in jsval options);
 
   // Returns object immediately
   nsIIDBIndex
   index([Null(Stringify)] in DOMString name);
 
   void
   deleteIndex([Null(Stringify)] in DOMString name);
--- a/dom/indexedDB/test/helpers.js
+++ b/dom/indexedDB/test/helpers.js
@@ -80,16 +80,46 @@ ExpectError.prototype = {
     is(event.type, "error", "Got an error event");
     is(this._code, event.target.errorCode, "Expected error was thrown.");
     event.preventDefault();
     event.stopPropagation();
     grabEventAndContinueHandler(event);
   }
 };
 
+function compareKeys(k1, k2) {
+  let t = typeof k1;
+  if (t != typeof k2)
+    return false;
+
+  if (t !== "object")
+    return k1 === k2;
+
+  if (k1 instanceof Date) {
+    return (k2 instanceof Date) &&
+      k1.getTime() === k2.getTime();
+  }
+
+  if (k1 instanceof Array) {
+    if (!(k2 instanceof Array) ||
+        k1.length != k2.length)
+      return false;
+    
+    for (let i = 0; i < k1.length; ++i) {
+      if (!compareKeys(k1[i], k2[i]))
+        return false;
+    }
+    
+    return true;
+  }
+
+  return false;
+}
+
+
 function addPermission(type, allow, url)
 {
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 
   let uri;
   if (url) {
     uri = Components.classes["@mozilla.org/network/io-service;1"]
                     .getService(Components.interfaces.nsIIOService)
--- a/dom/indexedDB/test/test_complex_keyPaths.html
+++ b/dom/indexedDB/test/test_complex_keyPaths.html
@@ -43,46 +43,69 @@
         { keyPath: "foo.",    exception: true },
         { keyPath: "fo o",    exception: true },
         { keyPath: "foo ",    exception: true },
         { keyPath: "foo[bar]",exception: true },
         { keyPath: "$('id').stuff", exception: true },
         { keyPath: "foo.2.bar", exception: true },
         { keyPath: "foo. .bar", exception: true },
         { keyPath: ".bar",    exception: true },
+
+        { keyPath: ["foo", "bar"],        value: { foo: 1, bar: 2 },              key: [1, 2] },
+        { keyPath: ["foo"],               value: { foo: 1, bar: 2 },              key: [1] },
+        { keyPath: ["foo", "bar", "bar"], value: { foo: 1, bar: "x" },            key: [1, "x", "x"] },
+        { keyPath: ["x", "y"],            value: { x: [],  y: "x" },              key: [[], "x"] },
+        { keyPath: ["x", "y"],            value: { x: [[1]],  y: "x" },           key: [[[1]], "x"] },
+        { keyPath: ["x", "y"],            value: { x: [[1]],  y: new Date(1) },   key: [[[1]], new Date(1)] },
+        { keyPath: ["x", "y"],            value: { x: [[1]],  y: [new Date(3)] }, key: [[[1]], [new Date(3)]] },
+        { keyPath: ["x", "y.bar"],        value: { x: "hi", y: { bar: "x"} },     key: ["hi", "x"] },
+        { keyPath: ["x.y", "y.bar"],      value: { x: { y: "hello" }, y: { bar: "nurse"} }, key: ["hello", "nurse"] },
+        { keyPath: ["", ""],              value: 5,                               key: [5, 5] },
+        { keyPath: ["x", "y"],            value: { x: 1 } },
+        { keyPath: ["x", "y"],            value: { y: 1 } },
+        { keyPath: ["x", "y"],            value: { x: 1, y: undefined } },
+        { keyPath: ["x", "y"],            value: { x: null, y: 1 } },
+        { keyPath: ["x", "y.bar"],        value: { x: null, y: { bar: "x"} } },
+        { keyPath: ["x", "y"],            value: { x: 1, y: false } },
+        { keyPath: ["x", "y", "z"],       value: { x: 1, y: false, z: "a" } },
+        { keyPath: [".x", "y", "z"],      exception: true },
+        { keyPath: ["x", "y ", "z"],      exception: true },
       ];
 
       let openRequest = mozIndexedDB.open(name, 1);
       openRequest.onerror = errorHandler;
       openRequest.onupgradeneeded = grabEventAndContinueHandler;
       openRequest.onsuccess = unexpectedSuccessHandler;
       let event = yield;
       let db = event.target.result;
 
       let stores = {};
 
       // Test creating object stores and inserting data
       for (let i = 0; i < keyPaths.length; i++) {
         let info = keyPaths[i];
+
         let test = " for objectStore test " + JSON.stringify(info);
-        if (!stores[info.keyPath]) {
+        let indexName = JSON.stringify(info.keyPath);
+        if (!stores[indexName]) {
           try {
-            let objectStore = db.createObjectStore(info.keyPath, { keyPath: info.keyPath });
+            let objectStore = db.createObjectStore(indexName, { keyPath: info.keyPath });
             ok(!("exception" in info), "shouldn't throw" + test);
-            is(objectStore.keyPath, info.keyPath, "correct keyPath property" + test);
-            stores[info.keyPath] = objectStore;
+            is(JSON.stringify(objectStore.keyPath), JSON.stringify(info.keyPath),
+               "correct keyPath property" + test);
+            stores[indexName] = objectStore;
           } catch (e) {
             ok("exception" in info, "should throw" + test);
             ok(e instanceof DOMException, "Got a DOM Exception" + test);
             is(e.code, DOMException.SYNTAX_ERR, "expect a syntax error" + test);
             continue;
           }
         }
 
-        let store = stores[info.keyPath];
+        let store = stores[indexName];
 
         try {
           request = store.add(info.value);
           ok("key" in info, "successfully created request to insert value" + test);
         } catch (e) {
           ok(!("key" in info), "threw when attempted to insert" + test);
           ok(e instanceof IDBDatabaseException, "Got a IDBDatabaseException" + test);
           is(e.code, IDBDatabaseException.DATA_ERR, "expect a DATA_ERR error" + test);
@@ -90,43 +113,50 @@
         }
 
         request.onerror = errorHandler;
         request.onsuccess = grabEventAndContinueHandler;
 
         let e = yield;
         is(e.type, "success", "inserted successfully" + test);
         is(e.target, request, "expected target" + test);
-        is(request.result, info.key, "found correct key" + test);
+        ok(compareKeys(request.result, info.key), "found correct key" + test);
+        is(mozIndexedDB.cmp(request.result, info.key), 0, "returned key compares correctly" + test);
+
+        store.get(info.key).onsuccess = grabEventAndContinueHandler;
+        e = yield;
+        isnot(e.target.result, undefined, "Did find entry");
 
         store.clear().onsuccess = grabEventAndContinueHandler;
         yield;
       }
 
       // Attempt to create indexes and insert data
       let store = db.createObjectStore("indexStore");
       let indexes = {};
       for (let i = 0; i < keyPaths.length; i++) {
         let test = " for index test " + JSON.stringify(info);
         let info = keyPaths[i];
-        if (!indexes[info.keyPath]) {
+        let indexName = JSON.stringify(info.keyPath);
+        if (!indexes[indexName]) {
           try {
-            let index = store.createIndex(info.keyPath, info.keyPath);
+            let index = store.createIndex(indexName, info.keyPath);
             ok(!("exception" in info), "shouldn't throw" + test);
-            is(index.keyPath, info.keyPath, "index has correct keyPath property" + test);
-            indexes[info.keyPath] = index;
+            is(JSON.stringify(index.keyPath), JSON.stringify(info.keyPath),
+               "index has correct keyPath property" + test);
+            indexes[indexName] = index;
           } catch (e) {
             ok("exception" in info, "should throw" + test);
             ok(e instanceof DOMException, "Got a DOM Exception" + test);
             is(e.code, DOMException.SYNTAX_ERR, "expect a syntax error" + test);
             continue;
           }
         }
         
-        let index = indexes[info.keyPath];
+        let index = indexes[indexName];
 
         request = store.add(info.value, 1);
         if ("key" in info) {
           index.getKey(info.key).onsuccess = grabEventAndContinueHandler;
           e = yield;
           is(e.target.result, 1, "found value when reading" + test);
         }
         else {
--- a/dom/indexedDB/test/test_create_index.html
+++ b/dom/indexedDB/test/test_create_index.html
@@ -46,16 +46,32 @@
           request = objectStore.createIndex("Hola");
           ok(false, "createIndex with no keyPath should throw");
         }
         catch(e) {
           ok(true, "createIndex with no keyPath should throw");
         }
 
         try {
+          request = objectStore.createIndex("Hola", ["foo"], { multiEntry: true });
+          ok(false, "createIndex with array keyPath and multiEntry should throw");
+        }
+        catch(e) {
+          ok(true, "createIndex with array keyPath and multiEntry should throw");
+        }
+
+        try {
+          request = objectStore.createIndex("Hola", []);
+          ok(false, "createIndex with empty array keyPath should throw");
+        }
+        catch(e) {
+          ok(true, "createIndex with empty array keyPath should throw");
+        }
+
+        try {
           request = objectStore.createIndex("foo", "bar", 10);
           ok(false, "createIndex with bad options should throw");
         }
         catch(e) {
           ok(true, "createIndex with bad options threw");
         }
 
         ok(objectStore.createIndex("foo", "bar", { foo: "" }),
--- a/dom/indexedDB/test/test_create_objectStore.html
+++ b/dom/indexedDB/test/test_create_objectStore.html
@@ -47,16 +47,24 @@
       try {
         db.createObjectStore("foo", "bar");
         ok(false, "createObjectStore with bad options should throw");
       }
       catch(e) {
         ok(true, "createObjectStore with bad options");
       }
 
+      try {
+        db.createObjectStore("foo", { keyPath: ["foo"], autoIncrement: true });
+        ok(false, "createObjectStore with keyPath-array and autoIncrement should throw");
+      }
+      catch(e) {
+        ok(true, "createObjectStore with keyPath-array and autoIncrement should throw");
+      }
+
       ok(db.createObjectStore("foo", { foo: "" }),
          "createObjectStore with unknown options should not throw");
       db.deleteObjectStore("foo");
 
       for (let index in objectStoreInfo) {
         index = parseInt(index);
         const info = objectStoreInfo[index];
 
--- a/dom/indexedDB/test/test_keys.html
+++ b/dom/indexedDB/test/test_keys.html
@@ -6,45 +6,16 @@
 <head>
   <title>Indexed Database Property Test</title>
 
   <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;version=1.7">
 
-    function compareKeys(k1, k2) {
-      let t = typeof k1;
-      if (t != typeof k2)
-        return false;
-    
-      if (t !== "object")
-        return k1 === k2;
-    
-      if (k1 instanceof Date) {
-        return (k2 instanceof Date) &&
-          k1.getTime() === k2.getTime();
-      }
-    
-      if (k1 instanceof Array) {
-        if (!(k2 instanceof Array) ||
-            k1.length != k2.length)
-          return false;
-        
-        for (let i = 0; i < k1.length; ++i) {
-          if (!compareKeys(k1[i], k2[i]))
-            return false;
-        }
-        
-        return true;
-      }
-    
-      return false;
-    }
-
     function testSteps()
     {
       const dbname = window.location.pathname;
       const RW = IDBTransaction.READ_WRITE
       let c1 = 1;
       let c2 = 1;
 
       let openRequest = mozIndexedDB.open(dbname, 1);