NS_IMETHODIMP
IDBDatabase::CreateObjectStore(const nsAString& aName,
                               const jsval& aOptions,
                               JSContext* aCx,
                               nsIIDBObjectStore** _retval)
{
  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");

  IDBTransaction* transaction = AsyncConnectionHelper::GetCurrentTransaction();

  if (!transaction ||
      transaction->Mode() != nsIIDBTransaction::VERSION_CHANGE) {
    return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
  }

  DatabaseInfo* databaseInfo = transaction->DBInfo();

  mozilla::dom::IDBObjectStoreParameters params;
  nsString keyPath;
  keyPath.SetIsVoid(true);
  nsTArray<nsString> keyPathArray;

  if (!JSVAL_IS_VOID(aOptions) && !JSVAL_IS_NULL(aOptions)) {
    nsresult rv = params.Init(aCx, &aOptions);
    NS_ENSURE_SUCCESS(rv, rv);

    // Get keyPath
    jsval val = params.keyPath;
    if (!JSVAL_IS_VOID(val) && !JSVAL_IS_NULL(val)) {
      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!");
      }
      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;
      }
    }
  }

  if (databaseInfo->ContainsStoreName(aName)) {
    return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
  }

  if (params.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 = params.autoIncrement ? 1 : 0;
  newInfo->comittedAutoIncrementId = newInfo->nextAutoIncrementId;

  if (!databaseInfo->PutObjectStore(newInfo)) {
    NS_WARNING("Put failed!");
    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
  }

  // Don't leave this in the hash if we fail below!
  AutoRemoveObjectStore autoRemove(databaseInfo, aName);

  nsRefPtr<IDBObjectStore> objectStore =
    transaction->GetOrCreateObjectStore(aName, newInfo);
  NS_ENSURE_TRUE(objectStore, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);

  nsRefPtr<CreateObjectStoreHelper> helper =
    new CreateObjectStoreHelper(transaction, objectStore);

  nsresult rv = helper->DispatchToTransactionPool();
  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);

  autoRemove.forget();

  objectStore.forget(_retval);
  return NS_OK;
}