Merge mozilla-central and mozilla-inbound
authorEd Morley <bmo@edmorley.co.uk>
Tue, 08 Nov 2011 08:21:25 +0000
changeset 80636 d4479192bda58bb73a9632728fd7f1a458677815
parent 80635 a62abefaab90fe1348e82b2ee71c2e8b1c93acab (current diff)
parent 80626 c170948e646eb24f0ed60cc91c4ae61ec3520af8 (diff)
child 80637 f44967f832f21e87d12487a8bcd7ac39c2c8e706
push id506
push userclegnitto@mozilla.com
push dateWed, 09 Nov 2011 02:03:18 +0000
treeherdermozilla-aurora@63587fc7bb93 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone10.0a1
Merge mozilla-central and mozilla-inbound
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -5437,20 +5437,25 @@ DebugWrapperTraceCallback(PRUint32 langI
   callback->NoteScriptChild(langID, p);
 }
 
 // static
 void
 nsContentUtils::CheckCCWrapperTraversal(nsISupports* aScriptObjectHolder,
                                         nsWrapperCache* aCache)
 {
+  JSObject* wrapper = aCache->GetWrapper();
+  if (!wrapper) {
+    return;
+  }
+
   nsXPCOMCycleCollectionParticipant* participant;
   CallQueryInterface(aScriptObjectHolder, &participant);
 
-  DebugWrapperTraversalCallback callback(aCache->GetWrapper());
+  DebugWrapperTraversalCallback callback(wrapper);
 
   participant->Traverse(aScriptObjectHolder, callback);
   NS_ASSERTION(callback.mFound,
                "Cycle collection participant didn't traverse to preserved "
                "wrapper! This will probably crash.");
 
   callback.mFound = false;
   participant->Trace(aScriptObjectHolder, DebugWrapperTraceCallback, &callback);
@@ -5902,19 +5907,21 @@ nsContentUtils::ReleaseWrapper(nsISuppor
 }
 
 // static
 void
 nsContentUtils::TraceWrapper(nsWrapperCache* aCache, TraceCallback aCallback,
                              void *aClosure)
 {
   if (aCache->PreservingWrapper()) {
-    aCallback(nsIProgrammingLanguage::JAVASCRIPT,
-              aCache->GetWrapperPreserveColor(),
-              "Preserved wrapper", aClosure);
+    JSObject *wrapper = aCache->GetWrapperPreserveColor();
+    if (wrapper) {
+      aCallback(nsIProgrammingLanguage::JAVASCRIPT, wrapper,
+                "Preserved wrapper", aClosure);
+    }
   }
   else {
     JSObject *expando = aCache->GetExpandoObjectPreserveColor();
     if (expando) {
       aCallback(nsIProgrammingLanguage::JAVASCRIPT, expando, "Expando object",
                 aClosure);
     }
   }
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -448,16 +448,24 @@ nsXMLHttpRequest::~nsXMLHttpRequest()
                 XML_HTTP_REQUEST_SENT |
                 XML_HTTP_REQUEST_LOADING)) {
     Abort();
   }
 
   NS_ABORT_IF_FALSE(!(mState & XML_HTTP_REQUEST_SYNCLOOPING), "we rather crash than hang");
   mState &= ~XML_HTTP_REQUEST_SYNCLOOPING;
 
+  // This can happen if the XHR was only used by C++ (and so never created a JS
+  // wrapper) that also made an ArrayBuffer.
+  if (PreservingWrapper()) {
+    nsContentUtils::ReleaseWrapper(
+      static_cast<nsIDOMEventTarget*>(
+        static_cast<nsDOMEventTargetHelper*>(this)), this);
+  }
+
   nsLayoutStatics::Release();
 }
 
 void
 nsXMLHttpRequest::RootResultArrayBuffer()
 {
   nsContentUtils::PreserveWrapper(
     static_cast<nsIDOMEventTarget*>(
--- a/content/xslt/src/xpath/txExprParser.cpp
+++ b/content/xslt/src/xpath/txExprParser.cpp
@@ -108,17 +108,16 @@ txExprParser::createAVT(const nsSubstrin
             }
 
             if (start == iter && literalString.IsEmpty()) {
                 // Restart the loop since we didn't create an expression
                 continue;
             }
             newExpr = new txLiteralExpr(literalString +
                                         Substring(start, iter));
-            NS_ENSURE_TRUE(newExpr, NS_ERROR_OUT_OF_MEMORY);
         }
         else {
             // Parse expressions, iter is already past the initial '{' when
             // we get here.
             while (iter != end) {
                 if (*iter == '}') {
                     rv = createExprInternal(Substring(start, iter),
                                             start - avtStart, aContext,
@@ -166,17 +165,16 @@ txExprParser::createAVT(const nsSubstrin
 
     if (inExpr) {
         aContext->SetErrorOffset(iter - avtStart);
         return NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE;
     }
 
     if (!expr) {
         expr = new txLiteralExpr(EmptyString());
-        NS_ENSURE_TRUE(expr, NS_ERROR_OUT_OF_MEMORY);
     }
 
     *aResult = expr.forget();
 
     return NS_OK;
 }
 
 nsresult
@@ -308,36 +306,40 @@ txExprParser::createExpr(txExprLexer& le
 
     nsAutoPtr<Expr> expr;
 
     txStack exprs;
     txStack ops;
 
     while (!done) {
 
-        MBool unary = MB_FALSE;
+        PRUint16 negations = 0;
         while (lexer.peek()->mType == Token::SUBTRACTION_OP) {
-            unary = !unary;
+            negations++;
             lexer.nextToken();
         }
 
         rv = createUnionExpr(lexer, aContext, getter_Transfers(expr));
         if (NS_FAILED(rv)) {
             break;
         }
 
-        if (unary) {
-            Expr* unaryExpr = new UnaryExpr(expr);
-            if (!unaryExpr) {
-                rv = NS_ERROR_OUT_OF_MEMORY;
-                break;
+        if (negations > 0) {
+            if (negations % 2 == 0) {
+                FunctionCall* fcExpr = new txCoreFunctionCall(txCoreFunctionCall::NUMBER);
+                
+                rv = fcExpr->addParam(expr);
+                if (NS_FAILED(rv))
+                    return rv;
+                expr.forget();
+                expr = fcExpr;
             }
-            
-            expr.forget();
-            expr = unaryExpr;
+            else {
+                expr = new UnaryExpr(expr.forget());
+            }
         }
 
         Token* tok = lexer.nextToken();
         short tokPrecedence = precedence(tok);
         if (tokPrecedence != 0) {
             while (!exprs.isEmpty() && tokPrecedence
                    <= precedence(static_cast<Token*>(ops.peek()))) {
                 // can't use expr as argument due to order of evaluation
@@ -396,46 +398,42 @@ txExprParser::createFilterOrStep(txExprL
             {
                 nsCOMPtr<nsIAtom> prefix, lName;
                 PRInt32 nspace;
                 nsresult rv = resolveQName(tok->Value(), getter_AddRefs(prefix),
                                            aContext, getter_AddRefs(lName),
                                            nspace);
                 NS_ENSURE_SUCCESS(rv, rv);
                 expr = new VariableRefExpr(prefix, lName, nspace);
-                NS_ENSURE_TRUE(expr, NS_ERROR_OUT_OF_MEMORY);
             }
             break;
         case Token::L_PAREN:
             rv = createExpr(lexer, aContext, getter_Transfers(expr));
             NS_ENSURE_SUCCESS(rv, rv);
 
             if (lexer.nextToken()->mType != Token::R_PAREN) {
                 lexer.pushBack();
                 return NS_ERROR_XPATH_PAREN_EXPECTED;
             }
             break;
         case Token::LITERAL :
             expr = new txLiteralExpr(tok->Value());
-            NS_ENSURE_TRUE(expr, NS_ERROR_OUT_OF_MEMORY);
             break;
         case Token::NUMBER:
         {
             expr = new txLiteralExpr(Double::toDouble(tok->Value()));
-            NS_ENSURE_TRUE(expr, NS_ERROR_OUT_OF_MEMORY);
             break;
         }
         default:
             lexer.pushBack();
             return createLocationStep(lexer, aContext, aResult);
     }
 
     if (lexer.peek()->mType == Token::L_BRACKET) {
         nsAutoPtr<FilterExpr> filterExpr(new FilterExpr(expr));
-        NS_ENSURE_TRUE(filterExpr, NS_ERROR_OUT_OF_MEMORY);
 
         expr.forget();
 
         //-- handle predicates
         rv = parsePredicates(filterExpr, lexer, aContext);
         NS_ENSURE_SUCCESS(rv, rv);
         expr = filterExpr.forget();
     }
@@ -463,33 +461,31 @@ txExprParser::createFunctionCall(txExprL
                                getter_AddRefs(lName), namespaceID);
     NS_ENSURE_SUCCESS(rv, rv);
 
     txCoreFunctionCall::eType type;
     if (namespaceID == kNameSpaceID_None &&
         txCoreFunctionCall::getTypeFromAtom(lName, type)) {
         // It is a known built-in function.
         fnCall = new txCoreFunctionCall(type);
-        NS_ENSURE_TRUE(fnCall, NS_ERROR_OUT_OF_MEMORY);
     }
 
     // check extension functions and xslt
     if (!fnCall) {
         rv = aContext->resolveFunctionCall(lName, namespaceID,
                                            getter_Transfers(fnCall));
 
         if (rv == NS_ERROR_NOT_IMPLEMENTED) {
             // this should just happen for unparsed-entity-uri()
             NS_ASSERTION(!fnCall, "Now is it implemented or not?");
             rv = parseParameters(0, lexer, aContext);
             NS_ENSURE_SUCCESS(rv, rv);
 
             *aResult = new txLiteralExpr(tok->Value() +
                                          NS_LITERAL_STRING(" not implemented."));
-            NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
 
             return NS_OK;
         }
 
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     //-- handle parametes
@@ -567,24 +563,22 @@ txExprParser::createLocationStep(txExprL
             lexer.nextToken();
             axisIdentifier = LocationStep::ATTRIBUTE_AXIS;
             break;
         case Token::PARENT_NODE :
             //-- eat token
             lexer.nextToken();
             axisIdentifier = LocationStep::PARENT_AXIS;
             nodeTest = new txNodeTypeTest(txNodeTypeTest::NODE_TYPE);
-            NS_ENSURE_TRUE(nodeTest, NS_ERROR_OUT_OF_MEMORY);
             break;
         case Token::SELF_NODE :
             //-- eat token
             lexer.nextToken();
             axisIdentifier = LocationStep::SELF_AXIS;
             nodeTest = new txNodeTypeTest(txNodeTypeTest::NODE_TYPE);
-            NS_ENSURE_TRUE(nodeTest, NS_ERROR_OUT_OF_MEMORY);
             break;
         default:
             break;
     }
 
     //-- get NodeTest unless an AbbreviatedStep was found
     nsresult rv = NS_OK;
     if (!nodeTest) {
@@ -599,27 +593,25 @@ txExprParser::createLocationStep(txExprL
                               nspace, true);
             NS_ENSURE_SUCCESS(rv, rv);
 
             nodeTest =
               new txNameTest(prefix, lName, nspace,
                              axisIdentifier == LocationStep::ATTRIBUTE_AXIS ?
                              static_cast<PRUint16>(txXPathNodeType::ATTRIBUTE_NODE) :
                              static_cast<PRUint16>(txXPathNodeType::ELEMENT_NODE));
-            NS_ENSURE_TRUE(nodeTest, NS_ERROR_OUT_OF_MEMORY);
         }
         else {
             lexer.pushBack();
             rv = createNodeTypeTest(lexer, getter_Transfers(nodeTest));
             NS_ENSURE_SUCCESS(rv, rv);
         }
     }
     
     nsAutoPtr<LocationStep> lstep(new LocationStep(nodeTest, axisIdentifier));
-    NS_ENSURE_TRUE(lstep, NS_ERROR_OUT_OF_MEMORY);
 
     nodeTest.forget();
 
     //-- handle predicates
     rv = parsePredicates(lstep, lexer, aContext);
     NS_ENSURE_SUCCESS(rv, rv);
 
     *aExpr = lstep.forget();
@@ -685,17 +677,16 @@ txExprParser::createPathExpr(txExprLexer
 
     Token* tok = lexer.peek();
 
     // is this a root expression?
     if (tok->mType == Token::PARENT_OP) {
         lexer.nextToken();
         if (!isLocationStepToken(lexer.peek())) {
             *aResult = new RootExpr();
-            NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
             return NS_OK;
         }
         lexer.pushBack();
     }
 
     // parse first step (possibly a FilterExpr)
     nsresult rv = NS_OK;
     if (tok->mType != Token::PARENT_OP &&
@@ -708,26 +699,24 @@ txExprParser::createPathExpr(txExprLexer
         if (tok->mType != Token::PARENT_OP &&
             tok->mType != Token::ANCESTOR_OP) {
             *aResult = expr.forget();
             return NS_OK;
         }
     }
     else {
         expr = new RootExpr();
-        NS_ENSURE_TRUE(expr, NS_ERROR_OUT_OF_MEMORY);
 
 #ifdef TX_TO_STRING
         static_cast<RootExpr*>(expr.get())->setSerialize(false);
 #endif
     }
     
     // We have a PathExpr containing several steps
     nsAutoPtr<PathExpr> pathExpr(new PathExpr());
-    NS_ENSURE_TRUE(pathExpr, NS_ERROR_OUT_OF_MEMORY);
 
     rv = pathExpr->addExpr(expr, PathExpr::RELATIVE_OP);
     NS_ENSURE_SUCCESS(rv, rv);
 
     expr.forget();
 
     // this is ugly
     while (1) {
@@ -773,17 +762,16 @@ txExprParser::createUnionExpr(txExprLexe
     NS_ENSURE_SUCCESS(rv, rv);
     
     if (lexer.peek()->mType != Token::UNION_OP) {
         *aResult = expr.forget();
         return NS_OK;
     }
 
     nsAutoPtr<UnionExpr> unionExpr(new UnionExpr());
-    NS_ENSURE_TRUE(unionExpr, NS_ERROR_OUT_OF_MEMORY);
 
     rv = unionExpr->addExpr(expr);
     NS_ENSURE_SUCCESS(rv, rv);
 
     expr.forget();
 
     while (lexer.peek()->mType == Token::UNION_OP) {
         lexer.nextToken(); //-- eat token
--- a/content/xslt/tests/mochitest/Makefile.in
+++ b/content/xslt/tests/mochitest/Makefile.in
@@ -50,14 +50,15 @@ include $(topsrcdir)/config/rules.mk
 		test_bug468208.html \
 		test_bug453441.html \
 		test_bug511487.html \
 		test_bug551412.html \
 		test_bug551654.html \
 		test_bug566629.html \
 		test_bug566629.xhtml \
 		test_bug603159.html \
+		test_bug616774.html \
 		test_bug667315.html \
 		test_exslt_regex.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/content/xslt/tests/mochitest/test_bug616774.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=616774-->
+<head>
+  <title>Test for Bug 616774</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=616774">Mozilla Bug 616774</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  42
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 616774 **/
+is(document.evaluate('- "8"', document, null, XPathResult.ANY_TYPE, null).numberValue, -8, "Negated string literal should evaluate to itself negated");
+is(document.evaluate('- - "999"', document, null, XPathResult.ANY_TYPE, null).numberValue, 999, "String literal should evaluate to itself");
+is(document.evaluate('- - id("content")', document, null, XPathResult.ANY_TYPE, null).numberValue, 42, "DOM element should evaluate to itself coerced to a number");
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/indexedDB/AsyncConnectionHelper.cpp
+++ b/dom/indexedDB/AsyncConnectionHelper.cpp
@@ -219,20 +219,19 @@ AsyncConnectionHelper::~AsyncConnectionH
 NS_IMPL_THREADSAFE_ISUPPORTS2(AsyncConnectionHelper, nsIRunnable,
                                                      mozIStorageProgressHandler)
 
 NS_IMETHODIMP
 AsyncConnectionHelper::Run()
 {
   if (NS_IsMainThread()) {
     if (mTransaction &&
-        mTransaction->IsAborted() &&
-        NS_SUCCEEDED(mResultCode)) {
-      // Don't fire success events if the transaction has since been aborted.
-      // Instead convert to an error event.
+        mTransaction->IsAborted()) {
+      // Always fire a "error" event with ABORT_ERR if the transaction was
+      // aborted, even if the request succeeded or failed with another error.
       mResultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
     }
 
     IDBTransaction* oldTransaction = gCurrentTransaction;
     gCurrentTransaction = mTransaction;
 
     if (mRequest) {
       nsresult rv = mRequest->NotifyHelperCompleted(this);
--- a/dom/indexedDB/CheckPermissionsHelper.cpp
+++ b/dom/indexedDB/CheckPermissionsHelper.cpp
@@ -136,17 +136,18 @@ CheckPermissionsHelper::Run()
         do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
       NS_ENSURE_STATE(permissionManager);
   
       rv = permissionManager->Add(uri, PERMISSION_INDEXEDDB, permission,
                                   nsIPermissionManager::EXPIRE_NEVER, 0);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
-  else if (permission == nsIPermissionManager::UNKNOWN_ACTION) {
+  else if (permission == nsIPermissionManager::UNKNOWN_ACTION &&
+           mPromptAllowed) {
     nsCOMPtr<nsIObserverService> obs = GetObserverService();
     rv = obs->NotifyObservers(static_cast<nsIRunnable*>(this),
                               TOPIC_PERMISSIONS_PROMPT, nsnull);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
   }
 
@@ -191,16 +192,17 @@ CheckPermissionsHelper::GetInterface(con
 
 NS_IMETHODIMP
 CheckPermissionsHelper::Observe(nsISupports* aSubject,
                                 const char* aTopic,
                                 const PRUnichar* aData)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!strcmp(aTopic, TOPIC_PERMISSIONS_RESPONSE), "Bad topic!");
+  NS_ASSERTION(mPromptAllowed, "How did we get here?");
 
   mHasPrompted = true;
 
   nsresult rv;
   mPromptResult = nsDependentString(aData).ToInteger(&rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
--- a/dom/indexedDB/CheckPermissionsHelper.h
+++ b/dom/indexedDB/CheckPermissionsHelper.h
@@ -59,31 +59,36 @@ class CheckPermissionsHelper : public ns
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIRUNNABLE
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSIOBSERVER
 
   CheckPermissionsHelper(OpenDatabaseHelper* aHelper,
                          nsIDOMWindow* aWindow,
-                         const nsACString& aASCIIOrigin)
+                         const nsACString& aASCIIOrigin,
+                         bool aForDeletion)
   : mHelper(aHelper),
     mWindow(aWindow),
     mASCIIOrigin(aASCIIOrigin),
+    // If we're trying to delete the database, we should never prompt the user.
+    // Anything that would prompt is translated to denied.
+    mPromptAllowed(!aForDeletion),
     mHasPrompted(false),
     mPromptResult(0)
   {
     NS_ASSERTION(aHelper, "Null pointer!");
     NS_ASSERTION(aWindow, "Null pointer!");
     NS_ASSERTION(!aASCIIOrigin.IsEmpty(), "Empty origin!");
   }
 
 private:
   nsRefPtr<OpenDatabaseHelper> mHelper;
   nsCOMPtr<nsIDOMWindow> mWindow;
   nsCString mASCIIOrigin;
+  bool mPromptAllowed;
   bool mHasPrompted;
   PRUint32 mPromptResult;
 };
 
 END_INDEXEDDB_NAMESPACE
 
 #endif // mozilla_dom_indexeddb_checkpermissionshelper_h__
--- a/dom/indexedDB/DatabaseInfo.cpp
+++ b/dom/indexedDB/DatabaseInfo.cpp
@@ -34,39 +34,24 @@
  * 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 "DatabaseInfo.h"
 
-#include "nsClassHashtable.h"
-#include "nsHashKeys.h"
+#include "nsDataHashtable.h"
 #include "nsThreadUtils.h"
 
 USING_INDEXEDDB_NAMESPACE
 
 namespace {
 
-typedef nsClassHashtable<nsStringHashKey, ObjectStoreInfo>
-        ObjectStoreInfoHash;
-
-struct DatabaseInfoHash
-{
-  DatabaseInfoHash(DatabaseInfo* aInfo) {
-    NS_ASSERTION(aInfo, "Null pointer!");
-    info = aInfo;
-  }
-
-  nsAutoPtr<DatabaseInfo> info;
-  nsAutoPtr<ObjectStoreInfoHash> objectStoreHash;
-};
-
-typedef nsClassHashtable<nsISupportsHashKey, DatabaseInfoHash>
+typedef nsDataHashtable<nsISupportsHashKey, DatabaseInfo*>
         DatabaseHash;
 
 DatabaseHash* gDatabaseHash = nsnull;
 
 PLDHashOperator
 EnumerateObjectStoreNames(const nsAString& aKey,
                           ObjectStoreInfo* aData,
                           void* aUserArg)
@@ -74,53 +59,88 @@ EnumerateObjectStoreNames(const nsAStrin
   nsTArray<nsString>* array = static_cast<nsTArray<nsString>*>(aUserArg);
   if (!array->AppendElement(aData->name)) {
     NS_ERROR("Out of memory?");
     return PL_DHASH_STOP;
   }
   return PL_DHASH_NEXT;
 }
 
+PLDHashOperator
+CloneObjectStoreInfo(const nsAString& aKey,
+                     ObjectStoreInfo* aData,
+                     void* aUserArg)
+{
+  ObjectStoreInfoHash* hash = static_cast<ObjectStoreInfoHash*>(aUserArg);
+
+  nsAutoPtr<ObjectStoreInfo> newInfo(new ObjectStoreInfo(*aData));
+
+  if (!hash->Put(aKey, newInfo)) {
+    NS_WARNING("Out of memory?");
+    return PL_DHASH_STOP;
+  }
+
+  newInfo.forget();
+  return PL_DHASH_NEXT;
 }
 
-#ifdef NS_BUILD_REFCNT_LOGGING
-DatabaseInfo::DatabaseInfo()
-: nextObjectStoreId(1),
-  nextIndexId(1),
-  runningVersionChange(false)
-{
-  MOZ_COUNT_CTOR(DatabaseInfo);
 }
 
 DatabaseInfo::~DatabaseInfo()
 {
-  MOZ_COUNT_DTOR(DatabaseInfo);
+  // Clones are never in the hash.
+  if (!cloned) {
+    DatabaseInfo::Remove(id);
+  }
 }
 
+#ifdef NS_BUILD_REFCNT_LOGGING
+
 IndexInfo::IndexInfo()
 : id(LL_MININT),
   unique(false),
   autoIncrement(false)
 {
   MOZ_COUNT_CTOR(IndexInfo);
 }
 
+IndexInfo::IndexInfo(const IndexInfo& aOther)
+: id(aOther.id),
+  name(aOther.name),
+  keyPath(aOther.keyPath),
+  unique(aOther.unique),
+  autoIncrement(aOther.autoIncrement)
+{
+  MOZ_COUNT_CTOR(IndexInfo);
+}
+
 IndexInfo::~IndexInfo()
 {
   MOZ_COUNT_DTOR(IndexInfo);
 }
 
 ObjectStoreInfo::ObjectStoreInfo()
 : id(0),
   autoIncrement(false),
   databaseId(0)
 {
   MOZ_COUNT_CTOR(ObjectStoreInfo);
 }
 
+ObjectStoreInfo::ObjectStoreInfo(ObjectStoreInfo& aOther)
+: name(aOther.name),
+  id(aOther.id),
+  keyPath(aOther.keyPath),
+  autoIncrement(aOther.autoIncrement),
+  databaseId(aOther.databaseId),
+  indexes(aOther.indexes)
+{
+  MOZ_COUNT_CTOR(ObjectStoreInfo);
+}
+
 ObjectStoreInfo::~ObjectStoreInfo()
 {
   MOZ_COUNT_DTOR(ObjectStoreInfo);
 }
 
 IndexUpdateInfo::IndexUpdateInfo()
 {
   MOZ_COUNT_CTOR(IndexUpdateInfo);
@@ -135,24 +155,20 @@ IndexUpdateInfo::~IndexUpdateInfo()
 // static
 bool
 DatabaseInfo::Get(nsIAtom* aId,
                   DatabaseInfo** aInfo)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aId, "Bad id!");
 
-  if (gDatabaseHash) {
-    DatabaseInfoHash* hash;
-    if (gDatabaseHash->Get(aId, &hash)) {
-      if (aInfo) {
-        *aInfo = hash->info;
-      }
-      return true;
-    }
+  if (gDatabaseHash &&
+      gDatabaseHash->Get(aId, aInfo)) {
+    NS_IF_ADDREF(*aInfo);
+    return true;
   }
   return false;
 }
 
 // static
 bool
 DatabaseInfo::Put(DatabaseInfo* aInfo)
 {
@@ -169,143 +185,131 @@ DatabaseInfo::Put(DatabaseInfo* aInfo)
     gDatabaseHash = databaseHash.forget();
   }
 
   if (gDatabaseHash->Get(aInfo->id, nsnull)) {
     NS_ERROR("Already know about this database!");
     return false;
   }
 
-  nsAutoPtr<DatabaseInfoHash> hash(new DatabaseInfoHash(aInfo));
-  if (!gDatabaseHash->Put(aInfo->id, hash)) {
+  if (!gDatabaseHash->Put(aInfo->id, aInfo)) {
     NS_ERROR("Put failed!");
     return false;
   }
 
-  hash.forget();
   return true;
 }
 
 // static
 void
 DatabaseInfo::Remove(nsIAtom* aId)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(Get(aId, nsnull), "Don't know anything about this one!");
+
+  DatabaseInfo* info = nsnull;
+
+  DebugOnly<bool> got = Get(aId, &info);
+  NS_ASSERTION(got && info, "Don't know anything about this one!");
 
   if (gDatabaseHash) {
     gDatabaseHash->Remove(aId);
 
     if (!gDatabaseHash->Count()) {
       delete gDatabaseHash;
       gDatabaseHash = nsnull;
     }
   }
 }
 
 bool
 DatabaseInfo::GetObjectStoreNames(nsTArray<nsString>& aNames)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(Get(id, nsnull), "Don't know anything about this one!");
-
-  if (!gDatabaseHash) {
-    return false;
-  }
-
-  DatabaseInfoHash* info;
-  if (!gDatabaseHash->Get(id, &info)) {
-    return false;
-  }
 
   aNames.Clear();
-  if (info->objectStoreHash) {
-    info->objectStoreHash->EnumerateRead(EnumerateObjectStoreNames, &aNames);
+  if (objectStoreHash) {
+    objectStoreHash->EnumerateRead(EnumerateObjectStoreNames, &aNames);
   }
   return true;
 }
 
 bool
 DatabaseInfo::ContainsStoreName(const nsAString& aName)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(Get(id, nsnull), "Don't know anything about this one!");
 
-  DatabaseInfoHash* hash;
-  ObjectStoreInfo* info;
-
-  return gDatabaseHash &&
-         gDatabaseHash->Get(id, &hash) &&
-         hash->objectStoreHash &&
-         hash->objectStoreHash->Get(aName, &info);
+  return objectStoreHash && objectStoreHash->Get(aName, nsnull);
 }
 
-// static
 bool
-ObjectStoreInfo::Get(nsIAtom* aDatabaseId,
-                     const nsAString& aName,
-                     ObjectStoreInfo** aInfo)
+DatabaseInfo::GetObjectStore(const nsAString& aName,
+                             ObjectStoreInfo** aInfo)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!aName.IsEmpty(), "Empty object store name!");
 
-  if (gDatabaseHash) {
-    DatabaseInfoHash* hash;
-    if (gDatabaseHash->Get(aDatabaseId, &hash)) {
-      if (hash->objectStoreHash) {
-        return !!hash->objectStoreHash->Get(aName, aInfo);
-      }
-    }
+  if (objectStoreHash) {
+    return objectStoreHash->Get(aName, aInfo);
   }
 
   return false;
 }
 
-// static
 bool
-ObjectStoreInfo::Put(ObjectStoreInfo* aInfo)
+DatabaseInfo::PutObjectStore(ObjectStoreInfo* aInfo)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aInfo, "Null pointer!");
 
-  if (!gDatabaseHash) {
-    NS_ERROR("No databases known!");
-    return false;
-  }
-
-  DatabaseInfoHash* hash;
-  if (!gDatabaseHash->Get(aInfo->databaseId, &hash)) {
-    NS_ERROR("Don't know about this database!");
-    return false;
-  }
-
-  if (!hash->objectStoreHash) {
-    nsAutoPtr<ObjectStoreInfoHash> objectStoreHash(new ObjectStoreInfoHash());
-    if (!objectStoreHash->Init()) {
+  if (!objectStoreHash) {
+    nsAutoPtr<ObjectStoreInfoHash> hash(new ObjectStoreInfoHash());
+    if (!hash->Init()) {
       NS_ERROR("Failed to initialize hashtable!");
       return false;
     }
-    hash->objectStoreHash = objectStoreHash.forget();
+    objectStoreHash = hash.forget();
   }
 
-  if (hash->objectStoreHash->Get(aInfo->name, nsnull)) {
+  if (objectStoreHash->Get(aInfo->name, nsnull)) {
     NS_ERROR("Already have an entry for this objectstore!");
     return false;
   }
 
-  return !!hash->objectStoreHash->Put(aInfo->name, aInfo);
+  return objectStoreHash->Put(aInfo->name, aInfo);
 }
 
-// static
 void
-ObjectStoreInfo::Remove(nsIAtom* aDatabaseId,
-                        const nsAString& aName)
+DatabaseInfo::RemoveObjectStore(const nsAString& aName)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(Get(aDatabaseId, aName, nsnull), "Don't know about this one!");
+  NS_ASSERTION(GetObjectStore(aName, nsnull), "Don't know about this one!");
 
-  if (gDatabaseHash) {
-    DatabaseInfoHash* hash;
-    if (gDatabaseHash->Get(aDatabaseId, &hash) && hash->objectStoreHash) {
-      hash->objectStoreHash->Remove(aName);
-    }
+  if (objectStoreHash) {
+    objectStoreHash->Remove(aName);
   }
 }
+
+already_AddRefed<DatabaseInfo>
+DatabaseInfo::Clone()
+{
+  NS_ASSERTION(!cloned, "Should never clone a clone!");
+
+  nsRefPtr<DatabaseInfo> dbInfo(new DatabaseInfo());
+
+  dbInfo->cloned = true;
+  dbInfo->name = name;
+  dbInfo->version = version;
+  dbInfo->id = id;
+  dbInfo->filePath = filePath;
+  dbInfo->nextObjectStoreId = nextObjectStoreId;
+  dbInfo->nextIndexId = nextIndexId;
+
+  if (objectStoreHash) {
+    dbInfo->objectStoreHash = new ObjectStoreInfoHash();
+    if (!dbInfo->objectStoreHash->Init()) {
+      return nsnull;
+    }
+
+    objectStoreHash->EnumerateRead(CloneObjectStoreInfo,
+                                   dbInfo->objectStoreHash);
+  }
+
+  return dbInfo.forget();
+}
--- a/dom/indexedDB/DatabaseInfo.h
+++ b/dom/indexedDB/DatabaseInfo.h
@@ -41,54 +41,80 @@
 #define mozilla_dom_indexeddb_databaseinfo_h__
 
 // Only meant to be included in IndexedDB source files, not exported.
 #include "IndexedDatabase.h"
 
 #include "Key.h"
 #include "IDBObjectStore.h"
 
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+
 BEGIN_INDEXEDDB_NAMESPACE
 
+struct ObjectStoreInfo;
+
+typedef nsClassHashtable<nsStringHashKey, ObjectStoreInfo>
+        ObjectStoreInfoHash;
+
+class IDBDatabase;
+class OpenDatabaseHelper;
+
 struct DatabaseInfo
 {
-#ifdef NS_BUILD_REFCNT_LOGGING
-  DatabaseInfo();
+  friend class IDBDatabase;
+  friend class OpenDatabaseHelper;
+
+private:
+  DatabaseInfo()
+  : nextObjectStoreId(1),
+    nextIndexId(1),
+    cloned(false)
+  { }
   ~DatabaseInfo();
-#else
-  DatabaseInfo()
-  : nextObjectStoreId(1), nextIndexId(1), runningVersionChange(false)
-  { }
-#endif
 
   static bool Get(nsIAtom* aId,
                   DatabaseInfo** aInfo);
 
   static bool Put(DatabaseInfo* aInfo);
 
   static void Remove(nsIAtom* aId);
 
+public:
   bool GetObjectStoreNames(nsTArray<nsString>& aNames);
   bool ContainsStoreName(const nsAString& aName);
 
+  bool GetObjectStore(const nsAString& aName,
+                      ObjectStoreInfo** aInfo);
+
+  bool PutObjectStore(ObjectStoreInfo* aInfo);
+
+  void RemoveObjectStore(const nsAString& aName);
+
+  already_AddRefed<DatabaseInfo> Clone();
+
   nsString name;
   PRUint64 version;
   nsIAtom* id;
   nsString filePath;
   PRInt64 nextObjectStoreId;
   PRInt64 nextIndexId;
-  bool runningVersionChange;
+  bool cloned;
 
-  nsAutoRefCnt referenceCount;
+  nsAutoPtr<ObjectStoreInfoHash> objectStoreHash;
+
+  NS_INLINE_DECL_REFCOUNTING(DatabaseInfo)
 };
 
 struct IndexInfo
 {
 #ifdef NS_BUILD_REFCNT_LOGGING
   IndexInfo();
+  IndexInfo(const IndexInfo& aOther);
   ~IndexInfo();
 #else
   IndexInfo()
   : id(LL_MININT), unique(false), autoIncrement(false) { }
 #endif
 
   PRInt64 id;
   nsString name;
@@ -96,31 +122,23 @@ struct IndexInfo
   bool unique;
   bool autoIncrement;
 };
 
 struct ObjectStoreInfo
 {
 #ifdef NS_BUILD_REFCNT_LOGGING
   ObjectStoreInfo();
+  ObjectStoreInfo(ObjectStoreInfo& aOther);
   ~ObjectStoreInfo();
 #else
   ObjectStoreInfo()
   : id(0), autoIncrement(false), databaseId(0) { }
 #endif
 
-  static bool Get(nsIAtom* aDatabaseId,
-                  const nsAString& aName,
-                  ObjectStoreInfo** aInfo);
-
-  static bool Put(ObjectStoreInfo* aInfo);
-
-  static void Remove(nsIAtom* aDatabaseId,
-                     const nsAString& aName);
-
   nsString name;
   PRInt64 id;
   nsString keyPath;
   bool autoIncrement;
   nsIAtom* databaseId;
   nsTArray<IndexInfo> indexes;
 };
 
--- a/dom/indexedDB/IDBCursor.cpp
+++ b/dom/indexedDB/IDBCursor.cpp
@@ -72,20 +72,23 @@ GenerateRequest(IDBCursor* aCursor)
 
 } // anonymous namespace
 
 BEGIN_INDEXEDDB_NAMESPACE
 
 class ContinueHelper : public AsyncConnectionHelper
 {
 public:
-  ContinueHelper(IDBCursor* aCursor)
+  ContinueHelper(IDBCursor* aCursor,
+                 PRInt32 aCount)
   : AsyncConnectionHelper(aCursor->mTransaction, aCursor->mRequest),
-    mCursor(aCursor)
-  { }
+    mCursor(aCursor), mCount(aCount)
+  {
+    NS_ASSERTION(aCount > 0, "Must have a count!");
+  }
 
   ~ContinueHelper()
   {
     IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
   }
 
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
@@ -101,50 +104,54 @@ protected:
   virtual nsresult
   BindArgumentsToStatement(mozIStorageStatement* aStatement) = 0;
 
   virtual nsresult
   GatherResultsFromStatement(mozIStorageStatement* aStatement) = 0;
 
 protected:
   nsRefPtr<IDBCursor> mCursor;
+  PRInt32 mCount;
   Key mKey;
   Key mObjectKey;
   JSAutoStructuredCloneBuffer mCloneBuffer;
 };
 
 class ContinueObjectStoreHelper : public ContinueHelper
 {
 public:
-  ContinueObjectStoreHelper(IDBCursor* aCursor)
-  : ContinueHelper(aCursor)
+  ContinueObjectStoreHelper(IDBCursor* aCursor,
+                            PRUint32 aCount)
+  : ContinueHelper(aCursor, aCount)
   { }
 
 private:
   nsresult BindArgumentsToStatement(mozIStorageStatement* aStatement);
   nsresult GatherResultsFromStatement(mozIStorageStatement* aStatement);
 };
 
 class ContinueIndexHelper : public ContinueHelper
 {
 public:
-  ContinueIndexHelper(IDBCursor* aCursor)
-  : ContinueHelper(aCursor)
+  ContinueIndexHelper(IDBCursor* aCursor,
+                      PRUint32 aCount)
+  : ContinueHelper(aCursor, aCount)
   { }
 
 private:
   nsresult BindArgumentsToStatement(mozIStorageStatement* aStatement);
   nsresult GatherResultsFromStatement(mozIStorageStatement* aStatement);
 };
 
 class ContinueIndexObjectHelper : public ContinueIndexHelper
 {
 public:
-  ContinueIndexObjectHelper(IDBCursor* aCursor)
-  : ContinueIndexHelper(aCursor)
+  ContinueIndexObjectHelper(IDBCursor* aCursor,
+                            PRUint32 aCount)
+  : ContinueIndexHelper(aCursor, aCount)
   { }
 
 private:
   nsresult GatherResultsFromStatement(mozIStorageStatement* aStatement);
 };
 
 END_INDEXEDDB_NAMESPACE
 
@@ -291,16 +298,70 @@ IDBCursor::~IDBCursor()
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (mRooted) {
     NS_DROP_JS_OBJECTS(this, IDBCursor);
   }
   IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer);
 }
 
+nsresult
+IDBCursor::ContinueInternal(const Key& aKey,
+                            PRInt32 aCount)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aCount > 0, "Must have a count!");
+
+  if (!mTransaction->IsOpen()) {
+    return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
+  }
+
+  if (!mHaveValue || mContinueCalled) {
+    return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
+  }
+
+  mContinueToKey = aKey;
+
+#ifdef DEBUG
+  {
+    PRUint16 readyState;
+    if (NS_FAILED(mRequest->GetReadyState(&readyState))) {
+      NS_ERROR("This should never fail!");
+    }
+    NS_ASSERTION(readyState == nsIIDBRequest::DONE, "Should be DONE!");
+  }
+#endif
+
+  mRequest->Reset();
+
+  nsRefPtr<ContinueHelper> helper;
+  switch (mType) {
+    case OBJECTSTORE:
+      helper = new ContinueObjectStoreHelper(this, aCount);
+      break;
+
+    case INDEXKEY:
+      helper = new ContinueIndexHelper(this, aCount);
+      break;
+
+    case INDEXOBJECT:
+      helper = new ContinueIndexObjectHelper(this, aCount);
+      break;
+
+    default:
+      NS_NOTREACHED("Unknown cursor type!");
+  }
+
+  nsresult rv = helper->DispatchToTransactionPool();
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  mContinueCalled = true;
+  return NS_OK;
+}
+
 NS_IMPL_CYCLE_COLLECTION_CLASS(IDBCursor)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IDBCursor)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mRequest,
                                                        nsIDOMEventTarget)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mTransaction,
                                                        nsIDOMEventTarget)
@@ -482,24 +543,16 @@ IDBCursor::GetValue(JSContext* aCx,
 }
 
 NS_IMETHODIMP
 IDBCursor::Continue(const jsval &aKey,
                     JSContext* aCx)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!mTransaction->IsOpen()) {
-    return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
-  }
-
-  if (!mHaveValue || mContinueCalled) {
-    return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
-  }
-
   Key key;
   nsresult rv = key.SetFromJSVal(aCx, aKey);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!key.IsUnset()) {
     switch (mDirection) {
       case nsIIDBCursor::NEXT:
       case nsIIDBCursor::NEXT_NO_DUPLICATE:
@@ -515,53 +568,17 @@ IDBCursor::Continue(const jsval &aKey,
         }
         break;
 
       default:
         NS_NOTREACHED("Unknown direction type!");
     }
   }
 
-  mContinueToKey = key;
-
-#ifdef DEBUG
-  {
-    PRUint16 readyState;
-    if (NS_FAILED(mRequest->GetReadyState(&readyState))) {
-      NS_ERROR("This should never fail!");
-    }
-    NS_ASSERTION(readyState == nsIIDBRequest::DONE, "Should be DONE!");
-  }
-#endif
-
-  mRequest->Reset();
-
-  nsRefPtr<ContinueHelper> helper;
-  switch (mType) {
-    case OBJECTSTORE:
-      helper = new ContinueObjectStoreHelper(this);
-      break;
-
-    case INDEXKEY:
-      helper = new ContinueIndexHelper(this);
-      break;
-
-    case INDEXOBJECT:
-      helper = new ContinueIndexObjectHelper(this);
-      break;
-
-    default:
-      NS_NOTREACHED("Unknown cursor type!");
-  }
-
-  rv = helper->DispatchToTransactionPool();
-  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
-
-  mContinueCalled = true;
-  return NS_OK;
+  return ContinueInternal(key, 1);
 }
 
 NS_IMETHODIMP
 IDBCursor::Update(const jsval& aValue,
                   JSContext* aCx,
                   nsIIDBRequest** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
@@ -648,43 +665,70 @@ IDBCursor::Delete(JSContext* aCx,
 
   jsval key;
   nsresult rv = objectKey.ToJSVal(aCx, &key);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return mObjectStore->Delete(key, aCx, _retval);
 }
 
+NS_IMETHODIMP
+IDBCursor::Advance(PRInt32 aCount)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  if (aCount < 1) {
+    return NS_ERROR_DOM_TYPE_ERR;
+  }
+
+  Key key;
+  return ContinueInternal(key, aCount);
+}
+
 nsresult
 ContinueHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   // We need to pick a query based on whether or not the cursor's mContinueToKey
   // is set. If it is unset then othing was passed to continue so we'll grab the
   // next item in the database that is greater than (less than, if we're running
   // a PREV cursor) the current key. If it is set then a key was passed to
   // continue so we'll grab the next item in the database that is greater than
   // (less than, if we're running a PREV cursor) or equal to the key that was
   // specified.
 
-  const nsCString& query = mCursor->mContinueToKey.IsUnset() ?
-                           mCursor->mContinueQuery :
-                           mCursor->mContinueToQuery;
+  nsCAutoString query;
+  if (mCursor->mContinueToKey.IsUnset()) {
+    query.Assign(mCursor->mContinueQuery);
+  }
+  else {
+    query.Assign(mCursor->mContinueToQuery);
+  }
   NS_ASSERTION(!query.IsEmpty(), "Bad query!");
 
+  query.AppendInt(mCount);
+
   nsCOMPtr<mozIStorageStatement> stmt = mTransaction->GetCachedStatement(query);
   NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   mozStorageStatementScoper scoper(stmt);
 
   nsresult rv = BindArgumentsToStatement(stmt);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
+  NS_ASSERTION(mCount > 0, "Not ok!");
+
   bool hasResult;
-  rv = stmt->ExecuteStep(&hasResult);
-  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  for (PRInt32 index = 0; index < mCount; index++) {
+    rv = stmt->ExecuteStep(&hasResult);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+    if (!hasResult) {
+      break;
+    }
+  }
 
   if (hasResult) {
     rv = GatherResultsFromStatement(stmt);
     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
   }
   else {
     mKey.Unset();
   }
--- a/dom/indexedDB/IDBCursor.h
+++ b/dom/indexedDB/IDBCursor.h
@@ -138,16 +138,20 @@ protected:
   CreateCommon(IDBRequest* aRequest,
                IDBTransaction* aTransaction,
                IDBObjectStore* aObjectStore,
                PRUint16 aDirection,
                const Key& aRangeKey,
                const nsACString& aContinueQuery,
                const nsACString& aContinueToQuery);
 
+  nsresult
+  ContinueInternal(const Key& aKey,
+                   PRInt32 aCount);
+
   nsRefPtr<IDBRequest> mRequest;
   nsRefPtr<IDBTransaction> mTransaction;
   nsRefPtr<IDBObjectStore> mObjectStore;
   nsRefPtr<IDBIndex> mIndex;
 
   nsCOMPtr<nsIScriptContext> mScriptContext;
   nsCOMPtr<nsPIDOMWindow> mOwner;
 
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -126,58 +126,61 @@ private:
   // In-params.
   PRInt64 mObjectStoreId;
 };
 
 NS_STACK_CLASS
 class AutoRemoveObjectStore
 {
 public:
-  AutoRemoveObjectStore(nsIAtom* aId, const nsAString& aName)
-  : mId(aId), mName(aName)
+  AutoRemoveObjectStore(IDBDatabase* aDatabase, const nsAString& aName)
+  : mDatabase(aDatabase), mName(aName)
   { }
 
   ~AutoRemoveObjectStore()
   {
-    if (mId) {
-      ObjectStoreInfo::Remove(mId, mName);
+    if (mDatabase) {
+      mDatabase->Info()->RemoveObjectStore(mName);
     }
   }
 
   void forget()
   {
-    mId = 0;
+    mDatabase = nsnull;
   }
 
 private:
-  nsCOMPtr<nsIAtom> mId;
+  IDBDatabase* mDatabase;
   nsString mName;
 };
 
 } // anonymous namespace
 
 // static
 already_AddRefed<IDBDatabase>
 IDBDatabase::Create(nsIScriptContext* aScriptContext,
                     nsPIDOMWindow* aOwner,
-                    DatabaseInfo* aDatabaseInfo,
+                    already_AddRefed<DatabaseInfo> aDatabaseInfo,
                     const nsACString& aASCIIOrigin)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(aDatabaseInfo, "Null pointer!");
   NS_ASSERTION(!aASCIIOrigin.IsEmpty(), "Empty origin!");
 
+  nsRefPtr<DatabaseInfo> databaseInfo(aDatabaseInfo);
+  NS_ASSERTION(databaseInfo, "Null pointer!");
+
   nsRefPtr<IDBDatabase> db(new IDBDatabase());
 
   db->mScriptContext = aScriptContext;
   db->mOwner = aOwner;
 
-  db->mDatabaseId = aDatabaseInfo->id;
-  db->mName = aDatabaseInfo->name;
-  db->mFilePath = aDatabaseInfo->filePath;
+  db->mDatabaseId = databaseInfo->id;
+  db->mName = databaseInfo->name;
+  db->mFilePath = databaseInfo->filePath;
+  databaseInfo.swap(db->mDatabaseInfo);
   db->mASCIIOrigin = aASCIIOrigin;
 
   IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
   NS_ASSERTION(mgr, "This should never be null!");
 
   if (!mgr->RegisterDatabase(db)) {
     // Either out of memory or shutting down.
     return nsnull;
@@ -185,51 +188,40 @@ IDBDatabase::Create(nsIScriptContext* aS
 
   return db.forget();
 }
 
 IDBDatabase::IDBDatabase()
 : mDatabaseId(0),
   mInvalidated(0),
   mRegistered(false),
-  mClosed(false)
+  mClosed(false),
+  mRunningVersionChange(false)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (!gDatabaseInstanceCount++) {
     NS_ASSERTION(!gPromptHelpersMutex, "Should be null!");
     gPromptHelpersMutex = new mozilla::Mutex("IDBDatabase gPromptHelpersMutex");
   }
 }
 
 IDBDatabase::~IDBDatabase()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (mRegistered) {
-    CloseInternal();
+    CloseInternal(true);
 
     IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
     if (mgr) {
       mgr->UnregisterDatabase(this);
     }
   }
 
-  if (mDatabaseId && !mInvalidated) {
-    DatabaseInfo* info;
-    if (!DatabaseInfo::Get(mDatabaseId, &info)) {
-      NS_ERROR("This should never fail!");
-    }
-
-    NS_ASSERTION(info->referenceCount, "Bad reference count!");
-    if (--info->referenceCount == 0) {
-      DatabaseInfo::Remove(mDatabaseId);
-    }
-  }
-
   if (mListenerManager) {
     mListenerManager->Disconnect();
   }
 
   if (!--gDatabaseInstanceCount) {
     NS_ASSERTION(gPromptHelpersMutex, "Should not be null!");
 
     delete gPromptHelpers;
@@ -304,41 +296,43 @@ IDBDatabase::Invalidate()
         if (helper->WindowSerial() == Owner()->GetSerial()) {
           helper->Cancel();
           break;
         }
       }
     }
   }
 
-  if (!PR_ATOMIC_SET(&mInvalidated, 1)) {
-    DatabaseInfo* info;
-    if (!DatabaseInfo::Get(mDatabaseId, &info)) {
-      NS_ERROR("This should never fail!");
-    }
-
-    NS_ASSERTION(info->referenceCount, "Bad reference count!");
-    if (--info->referenceCount == 0) {
-      DatabaseInfo::Remove(mDatabaseId);
-    }
-  }
+  mInvalidated = true;
 }
 
 bool
 IDBDatabase::IsInvalidated()
 {
   return !!mInvalidated;
 }
 
 void
-IDBDatabase::CloseInternal()
+IDBDatabase::CloseInternal(bool aIsDead)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (!mClosed) {
+    // If we're getting called from Unlink, avoid cloning the DatabaseInfo.
+    {
+      nsRefPtr<DatabaseInfo> previousInfo;
+      mDatabaseInfo.swap(previousInfo);
+
+      if (!aIsDead) {
+        nsRefPtr<DatabaseInfo> clonedInfo = previousInfo->Clone();
+
+        clonedInfo.swap(mDatabaseInfo);
+      }
+    }
+
     IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
     if (mgr) {
       mgr->OnDatabaseClosed(this);
     }
     mClosed = true;
   }
 }
 
@@ -347,46 +341,36 @@ IDBDatabase::IsClosed()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   return mClosed;
 }
 
 void
 IDBDatabase::EnterSetVersionTransaction()
 {
-  DatabaseInfo* dbInfo;
-  if (!DatabaseInfo::Get(mDatabaseId, &dbInfo)) {
-    NS_ERROR("This should never fail!");
-  }
-
-  NS_ASSERTION(!dbInfo->runningVersionChange, "How did that happen?");
-  dbInfo->runningVersionChange = true;
+  NS_ASSERTION(!mRunningVersionChange, "How did that happen?");
+  mRunningVersionChange = true;
 }
 
 void
 IDBDatabase::ExitSetVersionTransaction()
 {
-  DatabaseInfo* dbInfo;
-  if (!DatabaseInfo::Get(mDatabaseId, &dbInfo)) {
-    NS_ERROR("This should never fail!");
-  }
-
-  NS_ASSERTION(dbInfo->runningVersionChange, "How did that happen?");
-  dbInfo->runningVersionChange = false;
+  NS_ASSERTION(mRunningVersionChange, "How did that happen?");
+  mRunningVersionChange = false;
 }
 
 void
 IDBDatabase::OnUnlink()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!mOwner, "Should have been cleared already!");
 
   // We've been unlinked, at the very least we should be able to prevent further
   // transactions from starting and unblock any other SetVersion callers.
-  Close();
+  CloseInternal(true);
 
   // No reason for the IndexedDatabaseManager to track us any longer.
   IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
   if (mgr) {
     mgr->UnregisterDatabase(this);
 
     // Don't try to unregister again in the destructor.
     mRegistered = false;
@@ -427,33 +411,29 @@ IDBDatabase::GetName(nsAString& aName)
   aName.Assign(mName);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBDatabase::GetVersion(PRUint64* aVersion)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  DatabaseInfo* info;
-  if (!DatabaseInfo::Get(mDatabaseId, &info)) {
-    NS_ERROR("This should never fail!");
-  }
+
+  DatabaseInfo* info = Info();
   *aVersion = info->version;
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBDatabase::GetObjectStoreNames(nsIDOMDOMStringList** aObjectStores)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  DatabaseInfo* info;
-  if (!DatabaseInfo::Get(mDatabaseId, &info)) {
-    NS_ERROR("This should never fail!");
-  }
+  DatabaseInfo* info = Info();
 
   nsAutoTArray<nsString, 10> objectStoreNames;
   if (!info->GetObjectStoreNames(objectStoreNames)) {
     NS_WARNING("Couldn't get names!");
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   nsRefPtr<nsDOMStringList> list(new nsDOMStringList());
@@ -470,112 +450,91 @@ IDBDatabase::GetObjectStoreNames(nsIDOMD
 NS_IMETHODIMP
 IDBDatabase::CreateObjectStore(const nsAString& aName,
                                const jsval& aOptions,
                                JSContext* aCx,
                                nsIIDBObjectStore** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (aName.IsEmpty()) {
-    // XXX Update spec for a real error code here.
-    return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR;
-  }
-
   IDBTransaction* transaction = AsyncConnectionHelper::GetCurrentTransaction();
 
   if (!transaction ||
       transaction->Mode() != nsIIDBTransaction::VERSION_CHANGE) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
-  DatabaseInfo* databaseInfo;
-  if (!DatabaseInfo::Get(mDatabaseId, &databaseInfo)) {
-    NS_ERROR("This should never fail!");
-  }
+  DatabaseInfo* databaseInfo = Info();
 
   if (databaseInfo->ContainsStoreName(aName)) {
     return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
   }
 
   nsString keyPath;
   bool autoIncrement = false;
 
   if (!JSVAL_IS_VOID(aOptions) && !JSVAL_IS_NULL(aOptions)) {
     if (JSVAL_IS_PRIMITIVE(aOptions)) {
-      // XXX Update spec for a real code here
-      return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR;
+      // 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);
 
-    js::AutoIdArray ids(aCx, JS_Enumerate(aCx, options));
-    if (!ids) {
+    jsval val;
+    if (!JS_GetPropertyById(aCx, options, nsDOMClassInfo::sKeyPath_id, &val)) {
+      NS_WARNING("JS_GetPropertyById failed!");
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
 
-    for (size_t index = 0; index < ids.length(); index++) {
-      jsid id = ids[index];
-
-      if (id != nsDOMClassInfo::sKeyPath_id &&
-          id != nsDOMClassInfo::sAutoIncrement_id) {
-        // XXX Update spec for a real code here
-        return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_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;
       }
-
-      jsval val;
-      if (!JS_GetPropertyById(aCx, options, id, &val)) {
-        NS_WARNING("JS_GetPropertyById failed!");
+      nsDependentJSString dependentKeyPath;
+      if (!dependentKeyPath.init(aCx, str)) {
+        NS_WARNING("Initializing keyPath failed!");
         return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
       }
+      keyPath = dependentKeyPath;
+    }
 
-      if (id == nsDOMClassInfo::sKeyPath_id) {
-        JSString* str = JS_ValueToString(aCx, val);
-        if (!str) {
-          NS_WARNING("JS_ValueToString failed!");
-          return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-        }
-        nsDependentJSString dependentKeyPath;
-        if (!dependentKeyPath.init(aCx, str)) {
-          NS_WARNING("Initializing keyPath failed!");
-          return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-        }
-        keyPath = dependentKeyPath;
-      }
-      else if (id == nsDOMClassInfo::sAutoIncrement_id) {
-        JSBool boolVal;
-        if (!JS_ValueToBoolean(aCx, val, &boolVal)) {
-          NS_WARNING("JS_ValueToBoolean failed!");
-          return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-        }
-        autoIncrement = !!boolVal;
-      }
-      else {
-        NS_NOTREACHED("Shouldn't be able to get here!");
-      }
+    if (!JS_GetPropertyById(aCx, options, nsDOMClassInfo::sAutoIncrement_id,
+                            &val)) {
+      NS_WARNING("JS_GetPropertyById failed!");
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
+
+    JSBool boolVal;
+    if (!JS_ValueToBoolean(aCx, val, &boolVal)) {
+      NS_WARNING("JS_ValueToBoolean failed!");
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    }
+    autoIncrement = !!boolVal;
   }
 
   nsAutoPtr<ObjectStoreInfo> newInfo(new ObjectStoreInfo());
 
   newInfo->name = aName;
   newInfo->id = databaseInfo->nextObjectStoreId++;
   newInfo->keyPath = keyPath;
   newInfo->autoIncrement = autoIncrement;
   newInfo->databaseId = mDatabaseId;
 
-  if (!ObjectStoreInfo::Put(newInfo)) {
+  if (!Info()->PutObjectStore(newInfo)) {
     NS_WARNING("Put failed!");
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
   ObjectStoreInfo* objectStoreInfo = newInfo.forget();
 
   // Don't leave this in the hash if we fail below!
-  AutoRemoveObjectStore autoRemove(mDatabaseId, aName);
+  AutoRemoveObjectStore autoRemove(this, aName);
 
   nsRefPtr<IDBObjectStore> objectStore =
     transaction->GetOrCreateObjectStore(aName, objectStoreInfo);
   NS_ENSURE_TRUE(objectStore, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   nsRefPtr<CreateObjectStoreHelper> helper =
     new CreateObjectStoreHelper(transaction, objectStore);
 
@@ -595,27 +554,28 @@ IDBDatabase::DeleteObjectStore(const nsA
 
   IDBTransaction* transaction = AsyncConnectionHelper::GetCurrentTransaction();
 
   if (!transaction ||
       transaction->Mode() != nsIIDBTransaction::VERSION_CHANGE) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
+  DatabaseInfo* info = Info();
   ObjectStoreInfo* objectStoreInfo;
-  if (!ObjectStoreInfo::Get(mDatabaseId, aName, &objectStoreInfo)) {
+  if (!info->GetObjectStore(aName, &objectStoreInfo)) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR;
   }
 
   nsRefPtr<DeleteObjectStoreHelper> helper =
     new DeleteObjectStoreHelper(transaction, objectStoreInfo->id);
   nsresult rv = helper->DispatchToTransactionPool();
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
-  ObjectStoreInfo::Remove(mDatabaseId, aName);
+  info->RemoveObjectStore(aName);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBDatabase::Transaction(const jsval& aStoreNames,
                          PRUint16 aMode,
                          JSContext* aCx,
                          PRUint8 aOptionalArgCount,
@@ -626,22 +586,17 @@ IDBDatabase::Transaction(const jsval& aS
   if (IndexedDatabaseManager::IsShuttingDown()) {
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   if (mClosed) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
-  DatabaseInfo* info;
-  if (!DatabaseInfo::Get(mDatabaseId, &info)) {
-    NS_ERROR("This should never fail!");
-  }
-
-  if (info->runningVersionChange) {
+  if (mRunningVersionChange) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
   if (aOptionalArgCount) {
     if (aMode != nsIIDBTransaction::READ_WRITE &&
         aMode != nsIIDBTransaction::READ_ONLY) {
       return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR;
     }
@@ -737,16 +692,17 @@ IDBDatabase::Transaction(const jsval& aS
         !str.init(aCx, jsstr)) {
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
 
     storesToOpen.AppendElement(str);
   }
 
   // Now check to make sure the object store names we collected actually exist.
+  DatabaseInfo* info = Info();
   for (PRUint32 index = 0; index < storesToOpen.Length(); index++) {
     if (!info->ContainsStoreName(storesToOpen[index])) {
       return NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR;
     }
   }
 
   nsRefPtr<IDBTransaction> transaction =
     IDBTransaction::Create(this, storesToOpen, aMode,
@@ -757,17 +713,17 @@ IDBDatabase::Transaction(const jsval& aS
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBDatabase::Close()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  CloseInternal();
+  CloseInternal(false);
 
   NS_ASSERTION(mClosed, "Should have set the closed flag!");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBDatabase::SetOnerror(nsIDOMEventListener* aErrorListener)
 {
--- a/dom/indexedDB/IDBDatabase.h
+++ b/dom/indexedDB/IDBDatabase.h
@@ -72,27 +72,32 @@ public:
   NS_DECL_NSIIDBDATABASE
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IDBDatabase,
                                            nsDOMEventTargetHelper)
 
   static already_AddRefed<IDBDatabase>
   Create(nsIScriptContext* aScriptContext,
          nsPIDOMWindow* aOwner,
-         DatabaseInfo* aDatabaseInfo,
+         already_AddRefed<DatabaseInfo> aDatabaseInfo,
          const nsACString& aASCIIOrigin);
 
   // nsIDOMEventTarget
   virtual nsresult PostHandleEvent(nsEventChainPostVisitor& aVisitor);
 
-  nsIAtom* Id()
+  nsIAtom* Id() const
   {
     return mDatabaseId;
   }
 
+  DatabaseInfo* Info() const
+  {
+    return mDatabaseInfo;
+  }
+
   const nsString& Name()
   {
     return mName;
   }
 
   const nsString& FilePath()
   {
     return mFilePath;
@@ -125,38 +130,40 @@ public:
   }
 
   void Invalidate();
 
   // Whether or not the database has been invalidated. If it has then no further
   // transactions for this database will be allowed to run.
   bool IsInvalidated();
 
-  void CloseInternal();
+  void CloseInternal(bool aIsDead);
 
   // Whether or not the database has had Close called on it.
   bool IsClosed();
 
   void EnterSetVersionTransaction();
   void ExitSetVersionTransaction();
 
 private:
   IDBDatabase();
   ~IDBDatabase();
 
   void OnUnlink();
 
+  nsRefPtr<DatabaseInfo> mDatabaseInfo;
   nsCOMPtr<nsIAtom> mDatabaseId;
   nsString mName;
   nsString mFilePath;
   nsCString mASCIIOrigin;
 
   PRInt32 mInvalidated;
   bool mRegistered;
   bool mClosed;
+  bool mRunningVersionChange;
 
   // Only touched on the main thread.
   nsRefPtr<nsDOMEventListenerWrapper> mOnErrorListener;
   nsRefPtr<nsDOMEventListenerWrapper> mOnVersionChangeListener;
 };
 
 END_INDEXEDDB_NAMESPACE
 
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -341,26 +341,26 @@ IDBFactory::UpdateDatabaseMetadata(Datab
   nsAutoTArray<nsString, 10> existingNames;
   if (!aDatabaseInfo->GetObjectStoreNames(existingNames)) {
     NS_WARNING("Out of memory!");
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   // Remove all the old ones.
   for (PRUint32 index = 0; index < existingNames.Length(); index++) {
-    ObjectStoreInfo::Remove(aDatabaseInfo->id, existingNames[index]);
+    aDatabaseInfo->RemoveObjectStore(existingNames[index]);
   }
 
   aDatabaseInfo->version = aVersion;
 
   for (PRUint32 index = 0; index < objectStores.Length(); index++) {
     nsAutoPtr<ObjectStoreInfo>& info = objectStores[index];
     NS_ASSERTION(info->databaseId == aDatabaseInfo->id, "Huh?!");
 
-    if (!ObjectStoreInfo::Put(info)) {
+    if (!aDatabaseInfo->PutObjectStore(info)) {
       NS_WARNING("Out of memory!");
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     info.forget();
   }
 
   return NS_OK;
@@ -372,41 +372,32 @@ NS_IMPL_RELEASE(IDBFactory)
 NS_INTERFACE_MAP_BEGIN(IDBFactory)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRY(nsIIDBFactory)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(IDBFactory)
 NS_INTERFACE_MAP_END
 
 DOMCI_DATA(IDBFactory, IDBFactory)
 
-NS_IMETHODIMP
-IDBFactory::Open(const nsAString& aName,
-                 PRInt64 aVersion,
-                 JSContext* aCx,
-                 PRUint8 aOptionalArgCount,
-                 nsIIDBOpenDBRequest** _retval)
+nsresult
+IDBFactory::OpenCommon(const nsAString& aName,
+                       PRInt64 aVersion,
+                       bool aDeleting,
+                       nsIIDBOpenDBRequest** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (aVersion < 1 && aOptionalArgCount) {
-    return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR;
-  }
-
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     // Force ContentChild to cache the path from the parent, so that
     // we do not end up in a side thread that asks for the path (which
     // would make ContentChild try to send a message in a thread other
     // than the main one).
     ContentChild::GetSingleton()->GetIndexedDBPath();
   }
 
-  if (aName.IsEmpty()) {
-    return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR;
-  }
-
   nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
   NS_ENSURE_TRUE(window, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(window);
   NS_ENSURE_TRUE(sgo, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   nsIScriptContext* context = sgo->GetContext();
   NS_ENSURE_TRUE(context, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
@@ -430,25 +421,45 @@ IDBFactory::Open(const nsAString& aName,
     }
   }
 
   nsRefPtr<IDBOpenDBRequest> request =
     IDBOpenDBRequest::Create(context, window);
   NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   nsRefPtr<OpenDatabaseHelper> openHelper =
-    new OpenDatabaseHelper(request, aName, origin, aVersion);
+    new OpenDatabaseHelper(request, aName, origin, aVersion, aDeleting);
 
   rv = openHelper->Init();
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   nsRefPtr<CheckPermissionsHelper> permissionHelper =
-    new CheckPermissionsHelper(openHelper, window, origin);
+    new CheckPermissionsHelper(openHelper, window, origin, aDeleting);
 
   nsRefPtr<IndexedDatabaseManager> mgr = IndexedDatabaseManager::GetOrCreate();
   NS_ENSURE_TRUE(mgr, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   rv = mgr->WaitForOpenAllowed(origin, openHelper->Id(), permissionHelper);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   request.forget(_retval);
   return NS_OK;
 }
+
+NS_IMETHODIMP
+IDBFactory::Open(const nsAString& aName,
+                 PRInt64 aVersion,
+                 PRUint8 aArgc,
+                 nsIIDBOpenDBRequest** _retval)
+{
+  if (aVersion < 1 && aArgc) {
+    return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR;
+  }
+
+  return OpenCommon(aName, aVersion, false, _retval);
+}
+
+NS_IMETHODIMP
+IDBFactory::DeleteDatabase(const nsAString& aName,
+                           nsIIDBOpenDBRequest** _retval)
+{
+  return OpenCommon(aName, 0, true, _retval);
+}
--- a/dom/indexedDB/IDBFactory.h
+++ b/dom/indexedDB/IDBFactory.h
@@ -93,14 +93,20 @@ public:
   UpdateDatabaseMetadata(DatabaseInfo* aDatabaseInfo,
                          PRUint64 aVersion,
                          ObjectStoreInfoArray& aObjectStores);
 
 private:
   IDBFactory();
   ~IDBFactory() { }
 
+  nsresult
+  OpenCommon(const nsAString& aName,
+             PRInt64 aVersion,
+             bool aDeleting,
+             nsIIDBOpenDBRequest** _retval);
+
   nsCOMPtr<nsIWeakReference> mWindow;
 };
 
 END_INDEXEDDB_NAMESPACE
 
 #endif // mozilla_dom_indexeddb_idbfactory_h__
--- a/dom/indexedDB/IDBIndex.cpp
+++ b/dom/indexedDB/IDBIndex.cpp
@@ -1087,30 +1087,33 @@ OpenKeyCursorHelper::DoDatabaseWork(mozI
   if (mKeyRange) {
     mKeyRange->GetBindingClause(value, keyRangeClause);
   }
 
   nsCAutoString directionClause = NS_LITERAL_CSTRING(" ORDER BY ") + value;
   switch (mDirection) {
     case nsIIDBCursor::NEXT:
     case nsIIDBCursor::NEXT_NO_DUPLICATE:
-      directionClause.AppendLiteral(" ASC");
+      directionClause += NS_LITERAL_CSTRING(" ASC, ") + keyColumn +
+                         NS_LITERAL_CSTRING(" ASC");
       break;
 
     case nsIIDBCursor::PREV:
+      directionClause += NS_LITERAL_CSTRING(" DESC, ") + keyColumn +
+                         NS_LITERAL_CSTRING(" DESC");
+      break;
+
     case nsIIDBCursor::PREV_NO_DUPLICATE:
-      directionClause.AppendLiteral(" DESC");
+      directionClause += NS_LITERAL_CSTRING(" DESC, ") + keyColumn +
+                         NS_LITERAL_CSTRING(" ASC");
       break;
 
     default:
       NS_NOTREACHED("Unknown direction!");
   }
-  directionClause += NS_LITERAL_CSTRING(", ") + keyColumn +
-                     NS_LITERAL_CSTRING(" ASC");
-
   nsCString firstQuery = NS_LITERAL_CSTRING("SELECT value, ") + keyColumn +
                          NS_LITERAL_CSTRING(" FROM ") + table +
                          NS_LITERAL_CSTRING(" WHERE index_id = :") + id +
                          keyRangeClause + directionClause +
                          NS_LITERAL_CSTRING(" LIMIT 1");
 
   nsCOMPtr<mozIStorageStatement> stmt =
     mTransaction->GetCachedStatement(firstQuery);
@@ -1159,61 +1162,61 @@ OpenKeyCursorHelper::DoDatabaseWork(mozI
       }
       mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND ( ( value = :") +
                        currentKey + NS_LITERAL_CSTRING(" AND ") + keyColumn +
                        NS_LITERAL_CSTRING(" > :") + objectKey +
                        NS_LITERAL_CSTRING(" ) OR ( value > :") + currentKey +
                        NS_LITERAL_CSTRING(" ) )") + directionClause +
                        NS_LITERAL_CSTRING(" LIMIT 1");
       mContinueToQuery = queryStart + NS_LITERAL_CSTRING(" AND value >= :") +
-                         currentKey + NS_LITERAL_CSTRING(" LIMIT 1");
+                         currentKey + NS_LITERAL_CSTRING(" LIMIT ");
       break;
 
     case nsIIDBCursor::NEXT_NO_DUPLICATE:
       if (mKeyRange && !mKeyRange->Upper().IsUnset()) {
         AppendConditionClause(value, rangeKey, true, !mKeyRange->IsUpperOpen(),
                               queryStart);
         mRangeKey = mKeyRange->Upper();
       }
       mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND value > :") +
                        currentKey + directionClause +
                        NS_LITERAL_CSTRING(" LIMIT 1");
       mContinueToQuery = queryStart + NS_LITERAL_CSTRING(" AND value >= :") +
                          currentKey + directionClause +
-                         NS_LITERAL_CSTRING(" LIMIT 1");
+                         NS_LITERAL_CSTRING(" LIMIT ");
       break;
 
     case nsIIDBCursor::PREV:
       if (mKeyRange && !mKeyRange->Lower().IsUnset()) {
         AppendConditionClause(value, rangeKey, false, !mKeyRange->IsLowerOpen(),
                               queryStart);
         mRangeKey = mKeyRange->Lower();
       }
       mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND ( ( value = :") +
                        currentKey + NS_LITERAL_CSTRING(" AND ") + keyColumn +
                        NS_LITERAL_CSTRING(" < :") + objectKey +
                        NS_LITERAL_CSTRING(" ) OR ( value < :") + currentKey +
                        NS_LITERAL_CSTRING(" ) )") + directionClause +
                        NS_LITERAL_CSTRING(" LIMIT 1");
       mContinueToQuery = queryStart + NS_LITERAL_CSTRING(" AND value <= :") +
-                         currentKey + NS_LITERAL_CSTRING(" LIMIT 1");
+                         currentKey + NS_LITERAL_CSTRING(" LIMIT ");
       break;
 
     case nsIIDBCursor::PREV_NO_DUPLICATE:
       if (mKeyRange && !mKeyRange->Lower().IsUnset()) {
         AppendConditionClause(value, rangeKey, false, !mKeyRange->IsLowerOpen(),
                               queryStart);
         mRangeKey = mKeyRange->Lower();
       }
       mContinueQuery = queryStart + NS_LITERAL_CSTRING(" AND value < :") +
                        currentKey + directionClause +
                        NS_LITERAL_CSTRING(" LIMIT 1");
       mContinueToQuery = queryStart + NS_LITERAL_CSTRING(" AND value <= :") +
                          currentKey + directionClause +
-                         NS_LITERAL_CSTRING(" LIMIT 1");
+                         NS_LITERAL_CSTRING(" LIMIT ");
       break;
 
     default:
       NS_NOTREACHED("Unknown direction type!");
   }
 
   return NS_OK;
 }
@@ -1278,29 +1281,33 @@ OpenCursorHelper::DoDatabaseWork(mozISto
   if (mKeyRange) {
     mKeyRange->GetBindingClause(value, keyRangeClause);
   }
 
   nsCAutoString directionClause = NS_LITERAL_CSTRING(" ORDER BY ") + value;
   switch (mDirection) {
     case nsIIDBCursor::NEXT:
     case nsIIDBCursor::NEXT_NO_DUPLICATE:
-      directionClause.AppendLiteral(" ASC");
+      directionClause += NS_LITERAL_CSTRING(" ASC, ") + keyValue +
+                         NS_LITERAL_CSTRING(" ASC");
       break;
 
     case nsIIDBCursor::PREV:
+      directionClause += NS_LITERAL_CSTRING(" DESC, ") + keyValue +
+                         NS_LITERAL_CSTRING(" DESC");
+      break;
+
     case nsIIDBCursor::PREV_NO_DUPLICATE:
-      directionClause.AppendLiteral(" DESC");
+      directionClause += NS_LITERAL_CSTRING(" DESC, ") + keyValue +
+                         NS_LITERAL_CSTRING(" ASC");
       break;
 
     default:
       NS_NOTREACHED("Unknown direction!");
   }
-  directionClause += NS_LITERAL_CSTRING(", ") + keyValue +
-                     NS_LITERAL_CSTRING(" ASC");
 
   nsCString firstQuery = NS_LITERAL_CSTRING("SELECT ") + value +
                          NS_LITERAL_CSTRING(", ") + keyValue +
                          NS_LITERAL_CSTRING(", ") + data +
                          NS_LITERAL_CSTRING(" FROM ") + objectTable +
                          NS_LITERAL_CSTRING(" INNER JOIN ") + indexTable +
                          NS_LITERAL_CSTRING(" ON ") + indexTable +
                          NS_LITERAL_CSTRING(".") + objectDataIdColumn +
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -372,45 +372,45 @@ protected:
 private:
   PRUint64 mCount;
 };
 
 NS_STACK_CLASS
 class AutoRemoveIndex
 {
 public:
-  AutoRemoveIndex(nsIAtom* aDatabaseId,
+  AutoRemoveIndex(IDBDatabase* aDatabase,
                   const nsAString& aObjectStoreName,
                   const nsAString& aIndexName)
-  : mDatabaseId(aDatabaseId), mObjectStoreName(aObjectStoreName),
+  : mDatabase(aDatabase), mObjectStoreName(aObjectStoreName),
     mIndexName(aIndexName)
   { }
 
   ~AutoRemoveIndex()
   {
-    if (mDatabaseId) {
+    if (mDatabase) {
       ObjectStoreInfo* info;
-      if (ObjectStoreInfo::Get(mDatabaseId, mObjectStoreName, &info)) {
+      if (mDatabase->Info()->GetObjectStore(mObjectStoreName, &info)) {
         for (PRUint32 index = 0; index < info->indexes.Length(); index++) {
           if (info->indexes[index].name == mIndexName) {
             info->indexes.RemoveElementAt(index);
             break;
           }
         }
       }
     }
   }
 
   void forget()
   {
-    mDatabaseId = 0;
+    mDatabase = nsnull;
   }
 
 private:
-  nsCOMPtr<nsIAtom> mDatabaseId;
+  IDBDatabase* mDatabase;
   nsString mObjectStoreName;
   nsString mIndexName;
 };
 
 inline
 nsresult
 GetKeyFromObject(JSContext* aCx,
                  JSObject* aObj,
@@ -857,17 +857,17 @@ IDBObjectStore::GetAddInfo(JSContext* aC
   // 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.
   ObjectStoreInfo* info;
-  if (!ObjectStoreInfo::Get(mTransaction->Database()->Id(), mName, &info)) {
+  if (!mTransaction->Database()->Info()->GetObjectStore(mName, &info)) {
     NS_ERROR("This should never fail!");
   }
 
   rv = GetIndexUpdateInfo(info, aCx, aValue, aUpdateInfoArray);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   const jschar* keyPathChars =
     reinterpret_cast<const jschar*>(mKeyPath.get());
@@ -1032,17 +1032,17 @@ IDBObjectStore::GetTransaction(nsIIDBTra
 }
 
 NS_IMETHODIMP
 IDBObjectStore::GetIndexNames(nsIDOMDOMStringList** aIndexNames)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   ObjectStoreInfo* info;
-  if (!ObjectStoreInfo::Get(mTransaction->Database()->Id(), mName, &info)) {
+  if (!mTransaction->Database()->Info()->GetObjectStore(mName, &info)) {
     NS_ERROR("This should never fail!");
   }
 
   nsRefPtr<nsDOMStringList> list(new nsDOMStringList());
 
   PRUint32 count = info->indexes.Length();
   for (PRUint32 index = 0; index < count; index++) {
     NS_ENSURE_TRUE(list->Add(info->indexes[index].name),
@@ -1260,30 +1260,30 @@ NS_IMETHODIMP
 IDBObjectStore::CreateIndex(const nsAString& aName,
                             const nsAString& aKeyPath,
                             const jsval& aOptions,
                             JSContext* aCx,
                             nsIIDBIndex** _retval)
 {
   NS_PRECONDITION(NS_IsMainThread(), "Wrong thread!");
 
-  if (aName.IsEmpty() || aKeyPath.IsEmpty()) {
+  if (aKeyPath.IsEmpty()) {
     return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR;
   }
 
   IDBTransaction* transaction = AsyncConnectionHelper::GetCurrentTransaction();
 
   if (!transaction ||
       transaction != mTransaction ||
       mTransaction->Mode() != nsIIDBTransaction::VERSION_CHANGE) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
   ObjectStoreInfo* info;
-  if (!ObjectStoreInfo::Get(mTransaction->Database()->Id(), mName, &info)) {
+  if (!mTransaction->Database()->Info()->GetObjectStore(mName, &info)) {
     NS_ERROR("This should never fail!");
   }
 
   bool found = false;
   PRUint32 indexCount = info->indexes.Length();
   for (PRUint32 index = 0; index < indexCount; index++) {
     if (info->indexes[index].name == aName) {
       found = true;
@@ -1297,75 +1297,53 @@ IDBObjectStore::CreateIndex(const nsAStr
 
   NS_ASSERTION(mTransaction->IsOpen(), "Impossible!");
 
   bool unique = false;
 
   // Get optional arguments.
   if (!JSVAL_IS_VOID(aOptions) && !JSVAL_IS_NULL(aOptions)) {
     if (JSVAL_IS_PRIMITIVE(aOptions)) {
-    // XXX Update spec for a real code here
-      return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR;
+      // XXX Update spec for a real code here
+      return NS_ERROR_DOM_TYPE_ERR;
     }
 
     NS_ASSERTION(JSVAL_IS_OBJECT(aOptions), "Huh?!");
     JSObject* options = JSVAL_TO_OBJECT(aOptions);
 
-    js::AutoIdArray ids(aCx, JS_Enumerate(aCx, options));
-    if (!ids) {
+    jsval val;
+    if (!JS_GetPropertyById(aCx, options, nsDOMClassInfo::sUnique_id, &val)) {
+      NS_WARNING("JS_GetPropertyById failed!");
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
 
-    for (size_t index = 0; index < ids.length(); index++) {
-      jsid id = ids[index];
-
-      if (id != nsDOMClassInfo::sUnique_id) {
-        // XXX Update spec for a real code here
-        return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR;
-      }
-
-      jsval val;
-      if (!JS_GetPropertyById(aCx, options, id, &val)) {
-        NS_WARNING("JS_GetPropertyById failed!");
-        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-      }
-
-      if (id == nsDOMClassInfo::sUnique_id) {
-        JSBool boolVal;
-        if (!JS_ValueToBoolean(aCx, val, &boolVal)) {
-          NS_WARNING("JS_ValueToBoolean failed!");
-          return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-        }
-        unique = !!boolVal;
-      }
-      else {
-        NS_NOTREACHED("Shouldn't be able to get here!");
-      }
+    JSBool boolVal;
+    if (!JS_ValueToBoolean(aCx, val, &boolVal)) {
+      NS_WARNING("JS_ValueToBoolean failed!");
+      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
+    unique = !!boolVal;
   }
 
-  DatabaseInfo* databaseInfo;
-  if (!DatabaseInfo::Get(mTransaction->Database()->Id(), &databaseInfo)) {
-    NS_ERROR("This should never fail!");
-  }
+  DatabaseInfo* databaseInfo = mTransaction->Database()->Info();
 
   IndexInfo* indexInfo = info->indexes.AppendElement();
   if (!indexInfo) {
     NS_WARNING("Out of memory!");
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   indexInfo->id = databaseInfo->nextIndexId++;
   indexInfo->name = aName;
   indexInfo->keyPath = aKeyPath;
   indexInfo->unique = unique;
   indexInfo->autoIncrement = mAutoIncrement;
 
   // Don't leave this in the list if we fail below!
-  AutoRemoveIndex autoRemove(databaseInfo->id, mName, aName);
+  AutoRemoveIndex autoRemove(mTransaction->Database(), mName, aName);
 
 #ifdef DEBUG
   for (PRUint32 index = 0; index < mCreatedIndexes.Length(); index++) {
     if (mCreatedIndexes[index]->Name() == aName) {
       NS_ERROR("Already created this one!");
     }
   }
 #endif
@@ -1394,22 +1372,18 @@ IDBObjectStore::Index(const nsAString& a
                       nsIIDBIndex** _retval)
 {
   NS_PRECONDITION(NS_IsMainThread(), "Wrong thread!");
 
   if (!mTransaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
-  if (aName.IsEmpty()) {
-    return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR;
-  }
-
   ObjectStoreInfo* info;
-  if (!ObjectStoreInfo::Get(mTransaction->Database()->Id(), mName, &info)) {
+  if (!mTransaction->Database()->Info()->GetObjectStore(mName, &info)) {
     NS_ERROR("This should never fail!");
   }
 
   IndexInfo* indexInfo = nsnull;
   PRUint32 indexCount = info->indexes.Length();
   for (PRUint32 index = 0; index < indexCount; index++) {
     if (info->indexes[index].name == aName) {
       indexInfo = &(info->indexes[index]);
@@ -1444,32 +1418,28 @@ IDBObjectStore::Index(const nsAString& a
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBObjectStore::DeleteIndex(const nsAString& aName)
 {
   NS_PRECONDITION(NS_IsMainThread(), "Wrong thread!");
 
-  if (aName.IsEmpty()) {
-    return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR;
-  }
-
   IDBTransaction* transaction = AsyncConnectionHelper::GetCurrentTransaction();
 
   if (!transaction ||
       transaction != mTransaction ||
       mTransaction->Mode() != nsIIDBTransaction::VERSION_CHANGE) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
   NS_ASSERTION(mTransaction->IsOpen(), "Impossible!");
 
   ObjectStoreInfo* info;
-  if (!ObjectStoreInfo::Get(mTransaction->Database()->Id(), mName, &info)) {
+  if (!mTransaction->Database()->Info()->GetObjectStore(mName, &info)) {
     NS_ERROR("This should never fail!");
   }
 
   PRUint32 index = 0;
   for (; index < info->indexes.Length(); index++) {
     if (info->indexes[index].name == aName) {
       break;
     }
@@ -1830,18 +1800,17 @@ DeleteHelper::DoDatabaseWork(mozIStorage
 
   return NS_OK;
 }
 
 nsresult
 DeleteHelper::GetSuccessResult(JSContext* aCx,
                                jsval* aVal)
 {
-  // XXX Will fix this for real in a bit.
-  *aVal = JSVAL_TRUE;
+  *aVal = JSVAL_VOID;
   return NS_OK;
 }
 
 nsresult
 ClearHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   NS_PRECONDITION(aConnection, "Passed a null connection!");
 
@@ -1992,17 +1961,17 @@ OpenCursorHelper::DoDatabaseWork(mozISto
                    NS_LITERAL_CSTRING(" WHERE object_store_id = :") + id +
                    keyRangeClause + directionClause +
                    NS_LITERAL_CSTRING(" LIMIT 1");
 
   mContinueToQuery = NS_LITERAL_CSTRING("SELECT ") + keyColumn +
                      NS_LITERAL_CSTRING(", data FROM ") + table +
                      NS_LITERAL_CSTRING(" WHERE object_store_id = :") + id +
                      continueToKeyRangeClause + directionClause +
-                     NS_LITERAL_CSTRING(" LIMIT 1");
+                     NS_LITERAL_CSTRING(" LIMIT ");
 
   return NS_OK;
 }
 
 nsresult
 OpenCursorHelper::GetSuccessResult(JSContext* aCx,
                                    jsval* aVal)
 {
--- a/dom/indexedDB/IDBTransaction.cpp
+++ b/dom/indexedDB/IDBTransaction.cpp
@@ -487,17 +487,16 @@ IDBTransaction::IsOpen() const
   return false;
 }
 
 already_AddRefed<IDBObjectStore>
 IDBTransaction::GetOrCreateObjectStore(const nsAString& aName,
                                        ObjectStoreInfo* aObjectStoreInfo)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!aName.IsEmpty(), "Empty name!");
   NS_ASSERTION(aObjectStoreInfo, "Null pointer!");
 
   nsRefPtr<IDBObjectStore> retval;
 
   for (PRUint32 index = 0; index < mCreatedObjectStores.Length(); index++) {
     nsRefPtr<IDBObjectStore>& objectStore = mCreatedObjectStores[index];
     if (objectStore->Name() == aName) {
       retval = objectStore;
@@ -591,20 +590,17 @@ IDBTransaction::GetObjectStoreNames(nsID
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   nsRefPtr<nsDOMStringList> list(new nsDOMStringList());
 
   nsAutoTArray<nsString, 10> stackArray;
   nsTArray<nsString>* arrayOfNames;
 
   if (mMode == IDBTransaction::VERSION_CHANGE) {
-    DatabaseInfo* info;
-    if (!DatabaseInfo::Get(mDatabase->Id(), &info)) {
-      NS_ERROR("This should never fail!");
-    }
+    DatabaseInfo* info = mDatabase->Info();
 
     if (!info->GetObjectStoreNames(stackArray)) {
       NS_ERROR("Out of memory!");
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
 
     arrayOfNames = &stackArray;
   }
@@ -631,17 +627,17 @@ IDBTransaction::ObjectStore(const nsAStr
   if (!IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
   ObjectStoreInfo* info = nsnull;
 
   if (mMode == nsIIDBTransaction::VERSION_CHANGE ||
       mObjectStoreNames.Contains(aName)) {
-    ObjectStoreInfo::Get(mDatabase->Id(), aName, &info);
+    mDatabase->Info()->GetObjectStore(aName, &info);
   }
 
   if (!info) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR;
   }
 
   nsRefPtr<IDBObjectStore> objectStore = GetOrCreateObjectStore(aName, info);
   NS_ENSURE_TRUE(objectStore, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
@@ -830,20 +826,17 @@ CommitHelper::Run()
     mTransaction->mReadyState = nsIIDBTransaction::DONE;
 
     nsCOMPtr<nsIDOMEvent> event;
     if (mAborted) {
       if (mHaveMetadata) {
         NS_ASSERTION(mTransaction->Mode() == nsIIDBTransaction::VERSION_CHANGE,
                      "Bad transaction type!");
 
-        DatabaseInfo* dbInfo;
-        if (!DatabaseInfo::Get(mTransaction->Database()->Id(), &dbInfo)) {
-          NS_ERROR("This should never fail!");
-        }
+        DatabaseInfo* dbInfo = mTransaction->Database()->Info();
 
         if (NS_FAILED(IDBFactory::UpdateDatabaseMetadata(dbInfo, mOldVersion,
                                                          mOldObjectStores))) {
           NS_WARNING("Failed to update database metadata!");
         }
         else {
           NS_ASSERTION(mOldObjectStores.IsEmpty(), "Should have swapped!");
         }
--- a/dom/indexedDB/OpenDatabaseHelper.cpp
+++ b/dom/indexedDB/OpenDatabaseHelper.cpp
@@ -471,19 +471,22 @@ CreateDatabaseConnection(const nsAString
     "PRAGMA foreign_keys = ON;"
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
   connection.forget(aConnection);
   return NS_OK;
 }
 
+class VersionChangeEventsRunnable;
+
 class SetVersionHelper : public AsyncConnectionHelper,
                          public IDBTransactionListener
 {
+  friend class VersionChangeEventsRunnable;
 public:
   SetVersionHelper(IDBTransaction* aTransaction,
                    IDBOpenDBRequest* aRequest,
                    OpenDatabaseHelper* aHelper,
                    PRUint64 aRequestedVersion,
                    PRUint64 aCurrentVersion)
   : AsyncConnectionHelper(aTransaction, aRequest),
     mOpenRequest(aRequest), mOpenHelper(aHelper),
@@ -493,40 +496,88 @@ public:
     mTransaction->SetTransactionListener(this);
   }
 
   NS_DECL_ISUPPORTS_INHERITED
 
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
-  static
-  void QueueVersionChange(nsTArray<nsRefPtr<IDBDatabase> >& aDatabases,
-                          void* aClosure);
 protected:
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult Init();
 
   // SetVersionHelper never fires an error event at the request.  It hands that
   // responsibility back to the OpenDatabaseHelper
   void OnError() { }
 
   // Need an upgradeneeded event here.
   already_AddRefed<nsDOMEvent> CreateSuccessEvent();
 
   nsresult NotifyTransactionComplete(IDBTransaction* aTransaction);
 
+  PRUint64 RequestedVersion() const
+  {
+    return mRequestedVersion;
+  }
+
 private:
   // In-params
   nsRefPtr<OpenDatabaseHelper> mOpenHelper;
   nsRefPtr<IDBOpenDBRequest> mOpenRequest;
   PRUint64 mRequestedVersion;
   PRUint64 mCurrentVersion;
 };
 
+class DeleteDatabaseHelper : public AsyncConnectionHelper
+{
+  friend class VersionChangeEventsRunnable;
+public:
+  DeleteDatabaseHelper(IDBOpenDBRequest* aRequest,
+                       OpenDatabaseHelper* aHelper,
+                       PRUint64 aCurrentVersion,
+                       const nsAString& aName,
+                       const nsACString& aASCIIOrigin)
+  : AsyncConnectionHelper(static_cast<IDBDatabase*>(nsnull), aRequest),
+    mOpenRequest(aRequest), mOpenHelper(aHelper),
+    mCurrentVersion(aCurrentVersion), mName(aName),
+    mASCIIOrigin(aASCIIOrigin)
+  { }
+
+  nsresult GetSuccessResult(JSContext* aCx,
+                            jsval* aVal);
+
+protected:
+  nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
+  nsresult Init();
+
+  // DeleteDatabaseHelper never fires events at the request.  It hands that
+  // responsibility back to the OpenDatabaseHelper
+  void OnError()
+  {
+    mOpenHelper->NotifyDeleteFinished();
+  }
+  nsresult OnSuccess()
+  {
+    return mOpenHelper->NotifyDeleteFinished();
+  }
+
+  PRUint64 RequestedVersion() const
+  {
+    return 0;
+  }
+private:
+  // In-params
+  nsRefPtr<OpenDatabaseHelper> mOpenHelper;
+  nsRefPtr<IDBOpenDBRequest> mOpenRequest;
+  PRUint64 mCurrentVersion;
+  nsString mName;
+  nsCString mASCIIOrigin;
+};
+
 // Responsible for firing "versionchange" events at all live and non-closed
 // databases, and for firing a "blocked" event at the requesting database if any
 // databases fail to close.
 class VersionChangeEventsRunnable : public nsRunnable
 {
 public:
   VersionChangeEventsRunnable(
                             IDBDatabase* aRequestingDatabase,
@@ -594,16 +645,20 @@ public:
 
         break;
       }
     }
 
     return NS_OK;
   }
 
+  template <class T>
+  static
+  void QueueVersionChange(nsTArray<nsRefPtr<IDBDatabase> >& aDatabases,
+                          void* aClosure);
 private:
   nsRefPtr<IDBDatabase> mRequestingDatabase;
   nsRefPtr<IDBOpenDBRequest> mRequest;
   nsTArray<nsRefPtr<IDBDatabase> > mWaitingDatabases;
   PRInt64 mOldVersion;
   PRInt64 mNewVersion;
 };
 
@@ -740,16 +795,21 @@ OpenDatabaseHelper::DoDatabaseWork()
     nsAutoPtr<ObjectStoreInfo>& objectStoreInfo = mObjectStores[i];
     for (PRUint32 j = 0; j < objectStoreInfo->indexes.Length(); j++) {
       IndexInfo& indexInfo = objectStoreInfo->indexes[j];
       mLastIndexId = NS_MAX(indexInfo.id, mLastIndexId);
     }
     mLastObjectStoreId = NS_MAX(objectStoreInfo->id, mLastObjectStoreId);
   }
 
+  if (mForDeletion) {
+    mState = eDeletePending;
+    return NS_OK;
+  }
+
   // See if we need to do a VERSION_CHANGE transaction
 
   // Optional version semantics.
   if (!mRequestedVersion) {
     // If the requested version was not specified and the database was created,
     // treat it as if version 1 were requested.
     if (mCurrentVersion == 0) {
       mRequestedVersion = 1;
@@ -792,27 +852,56 @@ OpenDatabaseHelper::StartSetVersion()
   nsRefPtr<SetVersionHelper> helper =
     new SetVersionHelper(transaction, mOpenDBRequest, this, mRequestedVersion,
                          mCurrentVersion);
 
   IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
   NS_ASSERTION(mgr, "This should never be null!");
 
   rv = mgr->AcquireExclusiveAccess(mDatabase, helper,
-                                   &SetVersionHelper::QueueVersionChange,
+            &VersionChangeEventsRunnable::QueueVersionChange<SetVersionHelper>,
                                    helper);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   // The SetVersionHelper is responsible for dispatching us back to the
   // main thread again and changing the state to eSetVersionCompleted.
   mState = eSetVersionPending;
 
   return NS_OK;
 }
 
+nsresult
+OpenDatabaseHelper::StartDelete()
+{
+  NS_ASSERTION(mState == eDeletePending, "Why are we here?");
+
+  // In case we fail, fire error events
+  mState = eFiringEvents;
+
+  nsresult rv = EnsureSuccessResult();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<DeleteDatabaseHelper> helper =
+    new DeleteDatabaseHelper(mOpenDBRequest, this, mCurrentVersion, mName,
+                             mASCIIOrigin);
+
+  IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
+  NS_ASSERTION(mgr, "This should never be null!");
+
+  rv = mgr->AcquireExclusiveAccess(mDatabase, helper,
+        &VersionChangeEventsRunnable::QueueVersionChange<DeleteDatabaseHelper>,
+                                   helper);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  // The DeleteDatabaseHelper is responsible for dispatching us back to the
+  // main thread again and changing the state to eDeleteCompleted.
+  mState = eDeletePending;
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 OpenDatabaseHelper::Run()
 {
   NS_ASSERTION(mState != eCreated, "Dispatch was not called?!?");
 
   if (NS_IsMainThread()) {
     // If we need to queue up a SetVersionHelper, do that here.
     if (mState == eSetVersionPending) {
@@ -820,40 +909,68 @@ OpenDatabaseHelper::Run()
 
       if (NS_SUCCEEDED(rv)) {
         return rv;
       }
 
       SetError(rv);
       // fall through and run the default error processing
     }
+    else if (mState == eDeletePending) {
+      nsresult rv = StartDelete();
+
+      if (NS_SUCCEEDED(rv)) {
+        return rv;
+      }
+
+      SetError(rv);
+      // fall through and run the default error processing
+    }
 
     // We've done whatever work we need to do on the DB thread, and any
-    // SetVersion stuff is done by now.
+    // SetVersion/DeleteDatabase stuff is done by now.
     NS_ASSERTION(mState == eFiringEvents ||
-                 mState == eSetVersionCompleted, "Why are we here?");
+                 mState == eSetVersionCompleted ||
+                 mState == eDeleteCompleted, "Why are we here?");
 
-    if (mState == eSetVersionCompleted) {
-      // Allow transaction creation/other version change transactions to proceed
-      // before we fire events.  Other version changes will be postd to the end
-      // of the event loop, and will be behind whatever the page does in
-      // its error/success event handlers.
-      mDatabase->ExitSetVersionTransaction();
+    switch (mState) {
+      case eSetVersionCompleted: {
+        // Allow transaction creation/other version change transactions to proceed
+        // before we fire events.  Other version changes will be postd to the end
+        // of the event loop, and will be behind whatever the page does in
+        // its error/success event handlers.
+        mDatabase->ExitSetVersionTransaction();
+
+        mState = eFiringEvents;
+        break;
+      }
 
-      mState = eFiringEvents;
-    } else {
-      // Notify the request that we're done, but only if we didn't just finish
-      // a SetVersionHelper.  In the SetVersionHelper case, that helper tells
-      // the request that it is done, and we avoid calling NotifyHandlerCompleted
-      // twice.
+      case eDeleteCompleted: {
+        // Destroy the database now (we should have the only ref).
+        mDatabase = nsnull;
+
+        mState = eFiringEvents;
+        break;
+      }
 
-      nsresult rv = mOpenDBRequest->NotifyHelperCompleted(this);
-      if (NS_SUCCEEDED(mResultCode) && NS_FAILED(rv)) {
-        mResultCode = rv;
+      case eFiringEvents: {
+        // Notify the request that we're done, but only if we didn't just
+        // finish a [SetVersion/DeleteDatabase]Helper.  In that case, the
+        // helper tells the request that it is done, and we avoid calling
+        // NotifyHelperCompleted twice.
+
+        nsresult rv = mOpenDBRequest->NotifyHelperCompleted(this);
+        if (NS_SUCCEEDED(mResultCode) && NS_FAILED(rv)) {
+          mResultCode = rv;
+        }
+        break;
       }
+
+      default:
+        NS_NOTREACHED("Shouldn't get here!");
     }
 
     NS_ASSERTION(mState == eFiringEvents, "Why are we here?");
 
     if (NS_FAILED(mResultCode)) {
       DispatchErrorEvent();
     } else {
       DispatchSuccessEvent();
@@ -875,36 +992,34 @@ OpenDatabaseHelper::Run()
   NS_ASSERTION(mState != eDBWork, "We should be doing something else now.");
 
   return NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
 }
 
 nsresult
 OpenDatabaseHelper::EnsureSuccessResult()
 {
-  DatabaseInfo* dbInfo;
-  if (DatabaseInfo::Get(mDatabaseId, &dbInfo)) {
-    NS_ASSERTION(dbInfo->referenceCount, "Bad reference count!");
-    ++dbInfo->referenceCount;
+  nsRefPtr<DatabaseInfo> dbInfo;
+  if (DatabaseInfo::Get(mDatabaseId, getter_AddRefs(dbInfo))) {
 
 #ifdef DEBUG
     {
       NS_ASSERTION(dbInfo->name == mName &&
                    dbInfo->version == mCurrentVersion &&
                    dbInfo->id == mDatabaseId &&
                    dbInfo->filePath == mDatabaseFilePath,
                    "Metadata mismatch!");
 
       PRUint32 objectStoreCount = mObjectStores.Length();
       for (PRUint32 index = 0; index < objectStoreCount; index++) {
         nsAutoPtr<ObjectStoreInfo>& info = mObjectStores[index];
         NS_ASSERTION(info->databaseId == mDatabaseId, "Huh?!");
 
         ObjectStoreInfo* otherInfo;
-        NS_ASSERTION(ObjectStoreInfo::Get(mDatabaseId, info->name, &otherInfo),
+        NS_ASSERTION(dbInfo->GetObjectStore(info->name, &otherInfo),
                      "ObjectStore not known!");
 
         NS_ASSERTION(info->name == otherInfo->name &&
                      info->id == otherInfo->id &&
                      info->keyPath == otherInfo->keyPath &&
                      info->autoIncrement == otherInfo->autoIncrement &&
                      info->databaseId == otherInfo->databaseId,
                      "Metadata mismatch!");
@@ -929,43 +1044,44 @@ OpenDatabaseHelper::EnsureSuccessResult(
                        "Bad index autoIncrement value!");
         }
       }
     }
 #endif
 
   }
   else {
-    nsAutoPtr<DatabaseInfo> newInfo(new DatabaseInfo());
+    nsRefPtr<DatabaseInfo> newInfo(new DatabaseInfo());
 
     newInfo->name = mName;
     newInfo->id = mDatabaseId;
     newInfo->filePath = mDatabaseFilePath;
-    newInfo->referenceCount = 1;
 
     if (!DatabaseInfo::Put(newInfo)) {
       NS_ERROR("Failed to add to hash!");
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
 
-    dbInfo = newInfo.forget();
+    newInfo.swap(dbInfo);
 
     nsresult rv = IDBFactory::UpdateDatabaseMetadata(dbInfo, mCurrentVersion,
                                                      mObjectStores);
     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
     NS_ASSERTION(mObjectStores.IsEmpty(), "Should have swapped!");
   }
 
   dbInfo->nextObjectStoreId = mLastObjectStoreId + 1;
   dbInfo->nextIndexId = mLastIndexId + 1;
 
   nsRefPtr<IDBDatabase> database =
     IDBDatabase::Create(mOpenDBRequest->ScriptContext(),
-                        mOpenDBRequest->Owner(), dbInfo, mASCIIOrigin);
+                        mOpenDBRequest->Owner(),
+                        dbInfo.forget(),
+                        mASCIIOrigin);
   if (!database) {
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   NS_ASSERTION(!mDatabase, "Shouldn't have a database yet!");
   mDatabase.swap(database);
 
   return NS_OK;
@@ -992,30 +1108,40 @@ OpenDatabaseHelper::NotifySetVersionFini
   NS_ASSERTION(mState = eSetVersionPending, "How did we get here?");
 
   mState = eSetVersionCompleted;
   
   // Dispatch ourself back to the main thread
   return NS_DispatchToCurrentThread(this);
 }
 
+nsresult
+OpenDatabaseHelper::NotifyDeleteFinished()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread");
+  NS_ASSERTION(mState == eDeletePending, "How did we get here?");
+
+  mState = eDeleteCompleted;
+  
+  // Dispatch ourself back to the main thread
+  return NS_DispatchToCurrentThread(this);
+}
+
 void
 OpenDatabaseHelper::BlockDatabase()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(mDatabase, "This is going bad fast.");
 
   mDatabase->EnterSetVersionTransaction();
 }
 
 void
 OpenDatabaseHelper::DispatchSuccessEvent()
 {
-  NS_ASSERTION(mDatabase, "Doesn't seem very successful to me.");
-
   nsRefPtr<nsDOMEvent> event =
     CreateGenericEvent(NS_LITERAL_STRING(SUCCESS_EVT_STR));
   if (!event) {
     NS_ERROR("Failed to create event!");
     return;
   }
 
   bool dummy;
@@ -1088,48 +1214,45 @@ SetVersionHelper::DoDatabaseWork(mozISto
 
   return NS_OK;
 }
 
 nsresult
 SetVersionHelper::GetSuccessResult(JSContext* aCx,
                                    jsval* aVal)
 {
-  DatabaseInfo* info;
-  if (!DatabaseInfo::Get(mDatabase->Id(), &info)) {
-    NS_ERROR("This should never fail!");
-    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-  }
+  DatabaseInfo* info = mDatabase->Info();
   info->version = mRequestedVersion;
 
   NS_ASSERTION(mTransaction, "Better have a transaction!");
 
   mOpenRequest->SetTransaction(mTransaction);
 
   return WrapNative(aCx, NS_ISUPPORTS_CAST(nsIDOMEventTarget*, mDatabase),
                     aVal);
 }
 
 // static
+template <class T>
 void
-SetVersionHelper::QueueVersionChange(nsTArray<nsRefPtr<IDBDatabase> >& aDatabases,
-                                     void* aClosure)
+VersionChangeEventsRunnable::QueueVersionChange(
+                                  nsTArray<nsRefPtr<IDBDatabase> >& aDatabases,
+                                  void* aClosure)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!aDatabases.IsEmpty(), "Why are we here?");
 
-  SetVersionHelper* helper = static_cast<SetVersionHelper*>(aClosure);
-  NS_ASSERTION(helper, "Why don't we have a helper?");
+  T* closure = static_cast<T*>(aClosure);
 
   nsRefPtr<VersionChangeEventsRunnable> eventsRunnable =
-    new VersionChangeEventsRunnable(helper->mOpenHelper->Database(),
-                                    helper->mOpenRequest,
+    new VersionChangeEventsRunnable(closure->mOpenHelper->Database(),
+                                    closure->mOpenRequest,
                                     aDatabases,
-                                    helper->mCurrentVersion,
-                                    helper->mRequestedVersion);
+                                    closure->mCurrentVersion,
+                                    closure->RequestedVersion());
 
   NS_DispatchToCurrentThread(eventsRunnable);
 }
 
 already_AddRefed<nsDOMEvent>
 SetVersionHelper::CreateSuccessEvent()
 {
   NS_ASSERTION(mCurrentVersion < mRequestedVersion, "Huh?");
@@ -1158,8 +1281,46 @@ SetVersionHelper::NotifyTransactionCompl
 
   mOpenRequest->SetTransaction(nsnull);
 
   rv = mOpenHelper->NotifySetVersionFinished();
   mOpenHelper = nsnull;
 
   return rv;
 }
+
+nsresult
+DeleteDatabaseHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
+{
+  NS_ASSERTION(!aConnection, "How did we get a connection here?");
+
+  nsCOMPtr<nsIFile> dbFile;
+  nsresult rv = GetDatabaseFile(mASCIIOrigin, mName, getter_AddRefs(dbFile));
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  NS_ASSERTION(dbFile, "What?");
+
+  bool exists = false;
+  rv = dbFile->Exists(&exists);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  if (exists) {
+    rv = dbFile->Remove(false);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+DeleteDatabaseHelper::GetSuccessResult(JSContext* aCx, jsval* aVal)
+{
+  return NS_OK;
+}
+
+nsresult
+DeleteDatabaseHelper::Init()
+{
+  // Note that there's no need to block the database here, since the page
+  // never gets to touch it, and all other databases must be closed.
+
+  return NS_OK;
+}
--- a/dom/indexedDB/OpenDatabaseHelper.h
+++ b/dom/indexedDB/OpenDatabaseHelper.h
@@ -51,23 +51,27 @@ class mozIStorageConnection;
 BEGIN_INDEXEDDB_NAMESPACE
 
 class OpenDatabaseHelper : public HelperBase
 {
 public:
   OpenDatabaseHelper(IDBOpenDBRequest* aRequest,
                      const nsAString& aName,
                      const nsACString& aASCIIOrigin,
-                     PRUint64 aRequestedVersion)
+                     PRUint64 aRequestedVersion,
+                     bool aForDeletion)
     : HelperBase(aRequest), mOpenDBRequest(aRequest), mName(aName),
       mASCIIOrigin(aASCIIOrigin), mRequestedVersion(aRequestedVersion),
-      mCurrentVersion(0), mDataVersion(DB_SCHEMA_VERSION), mDatabaseId(0),
-      mLastObjectStoreId(0), mLastIndexId(0), mState(eCreated),
-      mResultCode(NS_OK)
-  { }
+      mForDeletion(aForDeletion), mCurrentVersion(0),
+      mDataVersion(DB_SCHEMA_VERSION), mDatabaseId(0), mLastObjectStoreId(0),
+      mLastIndexId(0), mState(eCreated), mResultCode(NS_OK)
+  {
+    NS_ASSERTION(!aForDeletion || !aRequestedVersion,
+                 "Can't be for deletion and request a version!");
+  }
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIRUNNABLE
 
   nsresult Init();
 
   nsresult Dispatch(nsIEventTarget* aDatabaseThread);
   nsresult RunImmediately();
@@ -79,16 +83,17 @@ public:
   }
 
   nsresult GetResultCode()
   {
     return mResultCode;
   }
 
   nsresult NotifySetVersionFinished();
+  nsresult NotifyDeleteFinished();
   void BlockDatabase();
 
   nsIAtom* Id() const
   {
     return mDatabaseId.get();
   }
 
   IDBDatabase* Database() const
@@ -96,31 +101,33 @@ public:
     NS_ASSERTION(mDatabase, "Calling at the wrong time!");
     return mDatabase;
   }
 
 protected:
   // Methods only called on the main thread
   nsresult EnsureSuccessResult();
   nsresult StartSetVersion();
+  nsresult StartDelete();
   nsresult GetSuccessResult(JSContext* aCx,
                           jsval* aVal);
   void DispatchSuccessEvent();
   void DispatchErrorEvent();
   void ReleaseMainThreadObjects();
 
   // Methods only called on the DB thread
   nsresult DoDatabaseWork();
 
 private:
   // In-params.
   nsRefPtr<IDBOpenDBRequest> mOpenDBRequest;
   nsString mName;
   nsCString mASCIIOrigin;
   PRUint64 mRequestedVersion;
+  bool mForDeletion;
   nsCOMPtr<nsIAtom> mDatabaseId;
 
   // Out-params.
   nsTArray<nsAutoPtr<ObjectStoreInfo> > mObjectStores;
   PRUint64 mCurrentVersion;
   PRUint32 mDataVersion;
   nsString mDatabaseFilePath;
   PRInt64 mLastObjectStoreId;
@@ -129,16 +136,18 @@ private:
 
   // State variables
   enum OpenDatabaseState {
     eCreated = 0, // Not yet dispatched to the DB thread
     eDBWork, // Waiting to do/doing work on the DB thread
     eFiringEvents, // Waiting to fire/firing events on the main thread
     eSetVersionPending, // Waiting on a SetVersionHelper
     eSetVersionCompleted, // SetVersionHelper is done
+    eDeletePending, // Waiting on a DeleteDatabaseHelper
+    eDeleteCompleted, // DeleteDatabaseHelper is done
   };
   OpenDatabaseState mState;
   nsresult mResultCode;
 };
 
 END_INDEXEDDB_NAMESPACE
 
 #endif // mozilla_dom_indexeddb_opendatabasehelper_h__
--- a/dom/indexedDB/nsIIDBCursor.idl
+++ b/dom/indexedDB/nsIIDBCursor.idl
@@ -41,17 +41,17 @@
 
 interface nsIIDBRequest;
 
 /**
  * IDBCursor interface.  See
  * http://dev.w3.org/2006/webapi/WebSimpleDB/#idl-def-IDBCursor for more
  * information.
  */
-[scriptable, builtinclass, uuid(462a3607-b2d6-4f4b-9dd7-8ca0b26d3414)]
+[scriptable, builtinclass, uuid(9d5ddd43-132d-418e-81e8-17d64a6467a2)]
 interface nsIIDBCursor : nsISupports
 {
   const unsigned short NEXT = 0;
   const unsigned short NEXT_NO_DUPLICATE = 1;
   const unsigned short PREV = 2;
   const unsigned short PREV_NO_DUPLICATE = 3;
   readonly attribute unsigned short direction;
 
@@ -70,9 +70,12 @@ interface nsIIDBCursor : nsISupports
 
   // Success fires IDBTransactionEvent, result == key
   [implicit_jscontext]
   nsIIDBRequest update(in jsval value);
 
   // Success fires IDBTransactionEvent, result == null
   [implicit_jscontext]
   nsIIDBRequest delete();
+
+  void
+  advance(in long count);
 };
--- a/dom/indexedDB/nsIIDBDatabase.idl
+++ b/dom/indexedDB/nsIIDBDatabase.idl
@@ -45,17 +45,17 @@ interface nsIIDBTransaction;
 interface nsIDOMDOMStringList;
 interface nsIDOMEventListener;
 
 /**
  * IDBDatabase interface.  See
  * http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBDatabase
  * for more information.
  */
-[scriptable, builtinclass, uuid(ddc9dd43-704f-45da-bb91-20f4b3103117)]
+[scriptable, builtinclass, uuid(7ad626df-3328-476f-ba10-8ccec4924340)]
 interface nsIIDBDatabase : nsISupports
 {
   readonly attribute DOMString name;
 
   readonly attribute unsigned long long version;
 
   readonly attribute nsIDOMDOMStringList objectStoreNames;
 
@@ -64,21 +64,21 @@ interface nsIIDBDatabase : nsISupports
    *   - keyPath (string):
    *       Specifies key path on objects in the objectStore. Defaults to no key
    *       path.
    *   - autoIncrement (boolean):
    *       Specifies if the objectStore has a key generator. Defaults to false.
    */
   [implicit_jscontext]
   nsIIDBObjectStore
-  createObjectStore(in AString name,
+  createObjectStore([Null(Stringify)] in DOMString name,
                     [optional /* none */] in jsval options);
 
   void
-  deleteObjectStore(in AString name);
+  deleteObjectStore([Null(Stringify)] in DOMString name);
 
   [optional_argc, implicit_jscontext]
   nsIIDBTransaction
   transaction(in jsval storeNames, // js array of strings
               [optional /* READ_ONLY */] in unsigned short mode);
 
   void
   close();
--- a/dom/indexedDB/nsIIDBFactory.idl
+++ b/dom/indexedDB/nsIIDBFactory.idl
@@ -43,16 +43,19 @@
 interface nsIIDBKeyRange;
 interface nsIIDBOpenDBRequest;
 
 /**
  * Interface that defines the indexedDB property on a window.  See
  * http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBFactory
  * for more information.
  */
-[scriptable, builtinclass, uuid(d2889b8f-662a-42d3-8a8f-ac5179b9d5b0)]
+[scriptable, builtinclass, uuid(885abbb7-cf81-4945-b5f1-07fed07ada82)]
 interface nsIIDBFactory : nsISupports
 {
-  [implicit_jscontext, optional_argc]
+  [optional_argc]
   nsIIDBOpenDBRequest
-  open(in AString name,
+  open([Null(Stringify)] in DOMString name,
        [optional] in long long version);
+
+  nsIIDBOpenDBRequest
+  deleteDatabase(in AString name);
 };
--- a/dom/indexedDB/nsIIDBObjectStore.idl
+++ b/dom/indexedDB/nsIIDBObjectStore.idl
@@ -45,17 +45,17 @@ 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(d25dff2f-81ad-4531-bcbe-e85c8a19f11a)]
+[scriptable, builtinclass, uuid(adc6a1e2-9fd7-4d28-a7f9-9c653313124b)]
 interface nsIIDBObjectStore : nsISupports
 {
   readonly attribute DOMString name;
 
   readonly attribute DOMString keyPath;
 
   readonly attribute nsIDOMDOMStringList indexNames;
 
@@ -103,24 +103,24 @@ interface nsIIDBObjectStore : nsISupport
   /**
    * Optional arguments:
    *   - unique (boolean):
    *       Specifies whether values in the index must be unique. Defaults to
    *       false.
    */
   [implicit_jscontext]
   nsIIDBIndex
-  createIndex(in AString name,
-              in AString keyPath,
+  createIndex([Null(Stringify)] in DOMString name,
+              [Null(Stringify)] in DOMString keyPath,
               [optional /* none */] in jsval options);
 
   // Returns object immediately
   nsIIDBIndex
-  index(in AString name);
+  index([Null(Stringify)] in DOMString name);
 
   void
-  deleteIndex(in AString name);
+  deleteIndex([Null(Stringify)] in DOMString name);
 
   // Accepts null, a key value, or a nsIIDBKeyRange object.
   [implicit_jscontext, optional_argc]
   nsIIDBRequest
   count([optional] in jsval key);
 };
--- a/dom/indexedDB/nsIIDBTransaction.idl
+++ b/dom/indexedDB/nsIIDBTransaction.idl
@@ -45,17 +45,17 @@ interface nsIIDBRequest;
 interface nsIIDBDatabase;
 interface nsIDOMDOMStringList;
 
 /**
  * IDBDTransaction interface.  See
  * http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBTransaction
  * for more information.
  */
-[scriptable, builtinclass, uuid(13e551a1-1a58-42ec-b0bd-7102ec0f64d6)]
+[scriptable, builtinclass, uuid(4f25832d-de40-4c0b-a176-358d94384b19)]
 interface nsIIDBTransaction : nsISupports
 {
   readonly attribute nsIIDBDatabase db;
 
   const unsigned short INITIAL = 0;
   const unsigned short LOADING = 1;
   const unsigned short COMMITTING = 2;
   const unsigned short DONE = 3;
@@ -64,17 +64,17 @@ interface nsIIDBTransaction : nsISupport
   const unsigned short READ_ONLY = 0;
   const unsigned short READ_WRITE = 1;
   const unsigned short VERSION_CHANGE = 2;
   readonly attribute unsigned short mode;
 
   readonly attribute nsIDOMDOMStringList objectStoreNames;
 
   nsIIDBObjectStore
-  objectStore(in AString name);
+  objectStore([Null(Stringify)] in DOMString name);
 
   // Don't commit the transaction.
   void abort();
 
   attribute nsIDOMEventListener onerror;
 
   // Event listener that fires when the transaction is completed
   // successfully. Receives an Event.
--- a/dom/indexedDB/test/Makefile.in
+++ b/dom/indexedDB/test/Makefile.in
@@ -48,27 +48,30 @@ TEST_FILES = \
   bfcache_iframe1.html \
   bfcache_iframe2.html \
   error_events_abort_transactions_iframe.html \
   event_propagation_iframe.html \
   exceptions_in_success_events_iframe.html \
   helpers.js \
   leaving_page_iframe.html \
   test_add_twice_failure.html \
+  test_advance.html \
   test_autoIncrement_indexes.html \
   test_bad_keypath.html \
   test_bfcache.html \
   test_clear.html \
   test_count.html \
   test_create_index.html \
   test_create_index_with_integer_keys.html \
   test_create_objectStore.html \
   test_cursors.html \
   test_cursor_mutation.html \
   test_cursor_update_updates_indexes.html \
+  test_deleteDatabase.html \
+  test_deleteDatabase_interactions.html \
   test_error_events_abort_transactions.html \
   test_event_propagation.html \
   test_event_source.html \
   test_exceptions_in_success_events.html \
   test_getAll.html \
   test_global_data.html \
   test_index_getAll.html \
   test_index_getAllObjects.html \
--- a/dom/indexedDB/test/test_add_twice_failure.html
+++ b/dom/indexedDB/test/test_add_twice_failure.html
@@ -19,17 +19,17 @@
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       let event = yield;
 
       let db = request.result;
 
       ok(event.target === request, "Good event target");
 
-      let objectStore = db.createObjectStore("foo", { keyPath: "" });
+      let objectStore = db.createObjectStore("foo", { keyPath: null });
       let key = 10;
 
       request = objectStore.add({}, key);
       request.onerror = errorHandler;
       request.onsuccess = grabEventAndContinueHandler;
       event = yield;
 
       is(request.result, key, "Correct key");
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_advance.html
@@ -0,0 +1,204 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<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 testSteps()
+    {
+      const dataCount = 30;
+
+      let request = mozIndexedDB.open(window.location.pathname, 1);
+      request.onerror = errorHandler;
+      request.onupgradeneeded = grabEventAndContinueHandler;
+      let event = yield;
+
+      let db = event.target.result;
+      db.onerror = errorHandler;
+
+      event.target.onsuccess = continueToNextStep;
+
+      let objectStore = db.createObjectStore("", { keyPath: "key" });
+      objectStore.createIndex("", "index");
+
+      for (let i = 0; i < dataCount; i++) {
+        objectStore.add({ key: i, index: i });
+      }
+      yield;
+
+      function getObjectStore() {
+        return db.transaction("").objectStore("");
+      }
+
+      function getIndex() {
+        return db.transaction("").objectStore("").index("");
+      }
+
+      let count = 0;
+
+      getObjectStore().openCursor().onsuccess = function(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          count++;
+          cursor.continue();
+        }
+        else {
+          continueToNextStep();
+        }
+      };
+      yield;
+
+      is(count, dataCount, "Saw all data");
+
+      count = 0;
+
+      getObjectStore().openCursor().onsuccess = function(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          is(cursor.primaryKey, count, "Got correct object");
+          if (count) {
+            count++;
+            cursor.continue();
+          }
+          else {
+            count = 10;
+            cursor.advance(10);
+          }
+        }
+        else {
+          continueToNextStep();
+        }
+      };
+      yield;
+
+      is(count, dataCount, "Saw all data");
+
+      count = 0;
+
+      getIndex().openCursor().onsuccess = function(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          is(cursor.primaryKey, count, "Got correct object");
+          if (count) {
+            count++;
+            cursor.continue();
+          }
+          else {
+            count = 10;
+            cursor.advance(10);
+          }
+        }
+        else {
+          continueToNextStep();
+        }
+      };
+      yield;
+
+      is(count, dataCount, "Saw all data");
+
+      count = 0;
+
+      getIndex().openKeyCursor().onsuccess = function(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          is(cursor.primaryKey, count, "Got correct object");
+          if (count) {
+            count++;
+            cursor.continue();
+          }
+          else {
+            count = 10;
+            cursor.advance(10);
+          }
+        }
+        else {
+          continueToNextStep();
+        }
+      };
+      yield;
+
+      is(count, dataCount, "Saw all data");
+
+      count = 0;
+
+      getObjectStore().openCursor().onsuccess = function(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          is(cursor.primaryKey, count, "Got correct object");
+          if (count == 0) {
+            cursor.advance(dataCount + 1);
+          }
+          else {
+            ok(false, "Should never get here!");
+            cursor.continue();
+          }
+        }
+        else {
+          continueToNextStep();
+        }
+      };
+      yield;
+
+      is(count, 0, "Saw all data");
+
+      count = dataCount - 1;
+
+      getObjectStore().openCursor(null, IDBCursor.PREV).onsuccess = function(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          is(cursor.primaryKey, count, "Got correct object");
+          count--;
+          if (count == dataCount - 2) {
+            cursor.advance(10);
+            count -= 9;
+          }
+          else {
+            cursor.continue();
+          }
+        }
+        else {
+          continueToNextStep();
+        }
+      };
+      yield;
+
+      is(count, -1, "Saw all data");
+
+      count = dataCount - 1;
+
+      getObjectStore().openCursor(null, IDBCursor.PREV).onsuccess = function(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          is(cursor.primaryKey, count, "Got correct object");
+          if (count == dataCount - 1) {
+            cursor.advance(dataCount + 1);
+          }
+          else {
+            ok(false, "Should never get here!");
+            cursor.continue();
+          }
+        }
+        else {
+          continueToNextStep();
+        }
+      };
+      yield;
+
+      is(count, dataCount - 1, "Saw all data");
+
+      finishTest();
+      yield;
+    }
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
--- a/dom/indexedDB/test/test_create_index.html
+++ b/dom/indexedDB/test/test_create_index.html
@@ -20,90 +20,80 @@
       const objectStoreInfo = [
         { name: "a", options: { keyPath: "id", autoIncrement: true } },
         { name: "b", options: { keyPath: "id", autoIncrement: false } },
       ];
       const indexInfo = [
         { name: "1", keyPath: "unique_value", options: { unique: true } },
         { name: "2", keyPath: "value", options: { unique: false } },
         { name: "3", keyPath: "value", options: { unique: false } },
+        { name: "", keyPath: "value", options: { unique: false } },
+        { name: null, keyPath: "value", options: { unique: false } },
+        { name: undefined, keyPath: "value", options: { unique: false } },
       ];
 
       let request = mozIndexedDB.open(name, 1, description);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       let event = yield;
       let db = event.target.result;
 
       for (let i = 0; i < objectStoreInfo.length; i++) {
         let info = objectStoreInfo[i];
         let objectStore = info.hasOwnProperty("options") ?
                           db.createObjectStore(info.name, info.options) :
                           db.createObjectStore(info.name);
 
-        // Test basic failure conditions.
-        try {
-          request = objectStore.createIndex(null, null);
-          ok(false, "createIndex with null name should throw");
-        }
-        catch(e) {
-          ok(true, "createIndex with null name should throw");
-        }
-
-        try {
-          request = objectStore.createIndex("", "");
-          ok(false, "createIndex with empty name should throw");
-        }
-        catch(e) {
-          ok(true, "createIndex with empty name should throw");
-        }
-
         try {
           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("foo", "bar", 10);
           ok(false, "createIndex with bad options should throw");
         }
         catch(e) {
           ok(true, "createIndex with bad options threw");
         }
 
-        try {
-          request = objectStore.createIndex("foo", "bar", { foo: "" });
-          ok(false, "createIndex with bad options should throw");
-        }
-        catch(e) {
-          ok(true, "createIndex with bad options threw");
-        }
+        ok(objectStore.createIndex("foo", "bar", { foo: "" }),
+           "createIndex with unknown options should not throw");
+        objectStore.deleteIndex("foo");
 
         // Test index creation, and that it ends up in indexNames.
         let objectStoreName = info.name;
         for (let j = 0; j < indexInfo.length; j++) {
           let info = indexInfo[j];
           let count = objectStore.indexNames.length;
           let index = info.hasOwnProperty("options") ?
                       objectStore.createIndex(info.name, info.keyPath,
                                               info.options) :
                       objectStore.createIndex(info.name, info.keyPath);
 
-          is(index.name, info.name, "correct name");
+          let name = info.name;
+          if (name === null) {
+            name = "null";
+          }
+          else if (name === undefined) {
+            name = "undefined";
+          }
+
+          is(index.name, name, "correct name");
           is(index.keyPath, info.keyPath, "correct keyPath");
           is(index.unique, info.options.unique, "correct uniqueness");
 
           is(objectStore.indexNames.length, count + 1,
              "indexNames grew in size");
           let found = false;
           for (let k = 0; k < objectStore.indexNames.length; k++) {
-            if (objectStore.indexNames.item(k) == info.name) {
+            if (objectStore.indexNames.item(k) == name) {
               found = true;
               break;
             }
           }
           ok(found, "Name is on objectStore.indexNames");
 
           ok(event.target.transaction, "event has a transaction");
           ok(event.target.transaction.db === db,
--- a/dom/indexedDB/test/test_create_objectStore.html
+++ b/dom/indexedDB/test/test_create_objectStore.html
@@ -13,109 +13,100 @@
     function testSteps()
     {
       const nsIIDBObjectStore = Components.interfaces.nsIIDBObjectStore;
       const nsIIDBTransaction = Components.interfaces.nsIIDBTransaction;
 
       const name = window.location.pathname;
       const description = "My Test Database";
       const objectStoreInfo = [
-        { name: "1", options: { keyPath: "" } },
-        { name: "2", options: { keyPath: "", autoIncrement: true } },
-        { name: "3", options: { keyPath: "", autoIncrement: false } },
-        { name: "4", options: { keyPath: "" } },
+        { name: "1", options: { keyPath: null } },
+        { name: "2", options: { keyPath: null, autoIncrement: true } },
+        { name: "3", options: { keyPath: null, autoIncrement: false } },
+        { name: "4", options: { keyPath: null } },
         { name: "5", options: { keyPath: "foo" } },
         { name: "6" },
         { name: "7", options: null },
         { name: "8", options: { autoIncrement: true } },
         { name: "9", options: { autoIncrement: false } },
         { name: "10", options: { keyPath: "foo", autoIncrement: false } },
-        { name: "11", options: { keyPath: "foo", autoIncrement: true } }
+        { name: "11", options: { keyPath: "foo", autoIncrement: true } },
+        { name: "" },
+        { name: null },
+        { name: undefined }
       ];
 
       let request = mozIndexedDB.open(name, 1, description);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
 
       let count = db.objectStoreNames.length;
       is(count, 0, "correct objectStoreNames length");
 
       try {
-        db.createObjectStore(null);
-        ok(false, "createObjectStore with null name should throw");
-      }
-      catch(e) {
-        ok(true, "createObjectStore with null name should throw");
-      }
-
-      try {
-        db.createObjectStore("");
-        ok(false, "createObjectStore with empty name should throw");
-      }
-      catch(e) {
-        ok(true, "createObjectStore with empty name should throw");
-      }
-
-      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", { foo: "" });
-        ok(false, "createObjectStore with bad options should throw");
-      }
-      catch(e) {
-        ok(true, "createObjectStore with bad options");
-      }
+      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];
 
         let objectStore = info.hasOwnProperty("options") ?
                           db.createObjectStore(info.name, info.options) :
                           db.createObjectStore(info.name);
 
         is(db.objectStoreNames.length, index + 1,
            "updated objectStoreNames list");
 
+        let name = info.name;
+        if (name === null) {
+          name = "null";
+        }
+        else if (name === undefined) {
+          name = "undefined";
+        }
+
         let found = false;
         for (let i = 0; i <= index; i++) {
-          if (db.objectStoreNames.item(i) == info.name) {
+          if (db.objectStoreNames.item(i) == name) {
             found = true;
             break;
           }
         }
         is(found, true, "objectStoreNames contains name");
 
-        is(objectStore.name, info.name, "Bad name");
+        is(objectStore.name, name, "Bad name");
         is(objectStore.keyPath, info.options && info.options.keyPath ?
                                 info.options.keyPath : "",
            "Bad keyPath");
         if(objectStore.indexNames.length, 0, "Bad indexNames");
 
         ok(event.target.transaction, "event has a transaction");
         ok(event.target.transaction.db === db, "transaction has the right db");
         is(event.target.transaction.readyState, nsIIDBTransaction.LOADING,
            "transaction has the correct readyState");
         is(event.target.transaction.mode, nsIIDBTransaction.VERSION_CHANGE,
            "transaction has the correct mode");
         is(event.target.transaction.objectStoreNames.length, index + 1,
            "transaction has correct objectStoreNames list");
         found = false;
         for (let j = 0; j < event.target.transaction.objectStoreNames.length;
              j++) {
-          if (event.target.transaction.objectStoreNames.item(j) == info.name) {
+          if (event.target.transaction.objectStoreNames.item(j) == name) {
             found = true;
             break;
           }
         }
         is(found, true, "transaction has correct objectStoreNames list");
       }
 
       finishTest();
--- a/dom/indexedDB/test/test_cursor_update_updates_indexes.html
+++ b/dom/indexedDB/test/test_cursor_update_updates_indexes.html
@@ -15,21 +15,21 @@
       const nsIIDBObjectStore = Components.interfaces.nsIIDBObjectStore;
       const nsIIDBTransaction = Components.interfaces.nsIIDBTransaction;
 
       const name = window.location.pathname;
       const description = "My Test Database";
       const START_DATA = "hi";
       const END_DATA = "bye";
       const objectStoreInfo = [
-        { name: "1", options: { keyPath: "" }, key: 1,
+        { name: "1", options: { keyPath: null }, key: 1,
           entry: { data: START_DATA } },
         { name: "2", options: { keyPath: "foo" },
           entry: { foo: 1, data: START_DATA } },
-        { name: "3", options: { keyPath: "", autoIncrement: true },
+        { name: "3", options: { keyPath: null, autoIncrement: true },
           entry: { data: START_DATA } },
         { name: "4", options: { keyPath: "foo", autoIncrement: true },
           entry: { data: START_DATA } },
       ];
 
       for (let i = 0; i < objectStoreInfo.length; i++) {
         // Create our object stores.
         let info = objectStoreInfo[i];
--- a/dom/indexedDB/test/test_cursors.html
+++ b/dom/indexedDB/test/test_cursors.html
@@ -299,17 +299,17 @@
           is(cursor.key, sortedKeys[keyIndex], "Correct key");
           is(cursor.primaryKey, sortedKeys[keyIndex], "Correct primary key");
           is(cursor.value, "foo", "Correct value");
 
           if (keyIndex == 4) {
             request = cursor.delete();
             request.onerror = errorHandler;
             request.onsuccess = function(event) {
-              is(event.target.result, true, "Actually deleted something");
+              ok(event.target.result === undefined, "Should be undefined");
               is(keyIndex, 5, "Got result of remove before next continue");
               gotRemoveEvent = true;
             };
           }
 
           keyIndex++;
           cursor.continue();
         }
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_deleteDatabase.html
@@ -0,0 +1,114 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database DeleteDatabase 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 testSteps()
+  {
+    const READ_WRITE = Components.interfaces.nsIIDBTransaction.READ_WRITE;
+    const VERSION_CHANGE = Components.interfaces.nsIIDBTransaction.VERSION_CHANGE;
+
+    const name = window.location.pathname;
+
+    ok(mozIndexedDB.deleteDatabase, "deleteDatabase function should exist!");
+
+    let request = mozIndexedDB.open(name, 10);
+    request.onerror = errorHandler;
+    request.onsuccess = unexpectedSuccessHandler;
+    request.onupgradeneeded = grabEventAndContinueHandler;
+
+    ok(request instanceof IDBOpenDBRequest, "Expect an IDBOpenDBRequest");
+
+    let event = yield;
+
+    is(event.type, "upgradeneeded", "Expect an upgradeneeded event");
+    ok(event instanceof IDBVersionChangeEvent, "Expect a versionchange event");
+
+    let db = event.target.result;
+    db.createObjectStore("stuff");
+
+    request.onsuccess = grabEventAndContinueHandler;
+
+    event = yield;
+
+    is(event.type, "success", "Expect a success event");
+    is(event.target, request, "Event has right target");
+    ok(event.target.result instanceof IDBDatabase, "Result should be a database");
+    is(db.objectStoreNames.length, 1, "Expect an objectStore here");
+
+    let request = mozIndexedDB.open(name, 10);
+    request.onerror = errorHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+
+    event = yield;
+    is(event.type, "success", "Expect a success event");
+    is(event.target, request, "Event has right target");
+    ok(event.target.result instanceof IDBDatabase, "Result should be a database");
+    let db2 = event.target.result;
+    is(db2.objectStoreNames.length, 1, "Expect an objectStore here");
+
+    var onversionchangecalled = false;
+
+    function closeDBs(event) {
+      onversionchangecalled = true;
+      ok(event instanceof IDBVersionChangeEvent, "expect a versionchange event");
+      is(event.oldVersion, 10, "oldVersion should be 10");
+      todo(event.newVersion, null, "newVersion should be null");
+      db.close();
+      db2.close();
+      db.onversionchange = errorHandler;
+      db2.onversionchange = errorHandler;
+    };
+
+    // The IDB spec doesn't guarantee the order that onversionchange will fire
+    // on the dbs.
+    db.onversionchange = closeDBs;
+    db2.onversionchange = closeDBs;
+
+    let request = mozIndexedDB.deleteDatabase(name);
+    request.onerror = errorHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+
+    ok(request instanceof IDBOpenDBRequest, "Expect an IDBOpenDBRequest");
+
+    event = yield;
+    ok(onversionchangecalled, "Expected versionchange events");
+    is(event.type, "success", "expect a success event");
+    is(event.target, request, "event has right target");
+    is(event.target.result, null, "event should have no result");
+
+    let request = mozIndexedDB.open(name, 1);
+    request.onerror = errorHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+
+    event = yield;
+    is(event.target.result.version, 1, "DB has proper version");
+    is(event.target.result.objectStoreNames.length, 0, "DB should have no object stores");
+
+
+    let request = mozIndexedDB.deleteDatabase("thisDatabaseHadBetterNotExist");
+    request.onerror = errorHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+
+    event = yield;
+    ok(true, "deleteDatabase on a non-existent database succeeded");
+
+    finishTest();
+    yield;
+  }
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_deleteDatabase_interactions.html
@@ -0,0 +1,79 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database DeleteDatabase 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 testSteps()
+  {
+    const READ_WRITE = Components.interfaces.nsIIDBTransaction.READ_WRITE;
+    const VERSION_CHANGE = Components.interfaces.nsIIDBTransaction.VERSION_CHANGE;
+
+    const name = window.location.pathname;
+
+    let request = mozIndexedDB.open(name, 10);
+    request.onerror = errorHandler;
+    request.onsuccess = unexpectedSuccessHandler;
+    request.onupgradeneeded = grabEventAndContinueHandler;
+
+    ok(request instanceof IDBOpenDBRequest, "Expect an IDBOpenDBRequest");
+
+    let event = yield;
+
+    is(event.type, "upgradeneeded", "Expect an upgradeneeded event");
+    ok(event instanceof IDBVersionChangeEvent, "Expect a versionchange event");
+
+    let db = event.target.result;
+    db.createObjectStore("stuff");
+
+    request.onsuccess = grabEventAndContinueHandler;
+
+    event = yield;
+
+    is(event.type, "success", "Expect a success event");
+    is(event.target, request, "Event has right target");
+    ok(event.target.result instanceof IDBDatabase, "Result should be a database");
+    is(db.objectStoreNames.length, 1, "Expect an objectStore here");
+
+    db.close();
+
+    let request = mozIndexedDB.deleteDatabase(name);
+
+    request.onerror = errorHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+
+    ok(request instanceof IDBOpenDBRequest, "Expect an IDBOpenDBRequest");
+
+    let openRequest = mozIndexedDB.open(name, 1);
+    openRequest.onerror = errorHandler;
+    openRequest.onsuccess = unexpectedSuccessHandler;
+
+    event = yield;
+    is(event.type, "success", "expect a success event");
+    is(event.target, request, "event has right target");
+    is(event.target.result, null, "event should have no result");
+
+    openRequest.onsuccess = grabEventAndContinueHandler;
+
+    event = yield;
+    is(event.target.result.version, 1, "DB has proper version");
+    is(event.target.result.objectStoreNames.length, 0, "DB should have no object stores");
+
+    finishTest();
+    yield;
+  }
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
--- a/dom/indexedDB/test/test_index_object_cursors.html
+++ b/dom/indexedDB/test/test_index_object_cursors.html
@@ -8,25 +8,25 @@
 
   <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 testSteps()
     {
       const objectStoreData = [
-        { name: "3", options: { keyPath: "id", autoIncrement: true } },
-        { name: "1", options: { keyPath: "ss" } },
-        { name: "2", options: { } },
+        { name: "", options: { keyPath: "id", autoIncrement: true } },
+        { name: null, options: { keyPath: "ss" } },
+        { name: undefined, options: { } },
         { name: "4", options: { autoIncrement: true } },
       ];
 
       const indexData = [
-        { name: "name", keyPath: "name", options: { unique: true } },
-        { name: "height", keyPath: "height", options: { } }
+        { name: "", keyPath: "name", options: { unique: true } },
+        { name: null, keyPath: "height", options: { } }
       ];
 
       const data = [
         { ss: "237-23-7732", name: "Ann", height: 60 },
         { ss: "237-23-7733", name: "Bob", height: 65 }
       ];
 
       let request = mozIndexedDB.open(window.location.pathname, 1);
@@ -82,17 +82,18 @@
 
           index.openCursor().onsuccess = function(event) {
             let cursor = event.target.result;
             if (!cursor) {
               continueToNextStep();
               return;
             }
 
-            is(cursor.key, data[keyIndex][indexName], "Good key");
+            is(cursor.key, data[keyIndex][indexData[indexIndex].keyPath],
+               "Good key");
             is(cursor.value.ss, data[keyIndex].ss, "Correct ss");
             is(cursor.value.name, data[keyIndex].name, "Correct name");
             is(cursor.value.height, data[keyIndex].height, "Correct height");
 
             if (!keyIndex) {
               let obj = cursor.value;
               obj.updated = true;
 
--- a/dom/indexedDB/test/test_indexes.html
+++ b/dom/indexedDB/test/test_indexes.html
@@ -71,17 +71,17 @@
       let request = mozIndexedDB.open(name, 1, description);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       request.onsuccess = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
 
-      let objectStore = db.createObjectStore(objectStoreName, { keyPath: "" });
+      let objectStore = db.createObjectStore(objectStoreName, { keyPath: null });
 
       // First, add all our data to the object store.
       let addedData = 0;
       for (let i in objectStoreData) {
         request = objectStore.add(objectStoreData[i].value,
                                   objectStoreData[i].key);
         request.onerror = errorHandler;
         request.onsuccess = function(event) {
@@ -887,16 +887,41 @@
         else {
           testGenerator.next();
         }
       }
       yield;
 
       is(keyIndex, 4, "Saw all the expected keys");
 
+      ok(true, "Test group 21.5");
+
+      keyIndex = 5;
+
+      request = objectStore.index("height").openKeyCursor(null, PREV);
+      request.onerror = errorHandler;
+      request.onsuccess = function (event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          is(cursor.key, objectStoreDataHeightSort[keyIndex].value.height,
+             "Correct key");
+          is(cursor.primaryKey, objectStoreDataHeightSort[keyIndex].key,
+             "Correct value");
+
+          cursor.continue();
+          keyIndex--;
+        }
+        else {
+          testGenerator.next();
+        }
+      }
+      yield;
+
+      is(keyIndex, -1, "Saw all the expected keys");
+
       ok(true, "Test group 22");
 
       keyIndex = 5;
 
       request = objectStore.index("height").openKeyCursor(null,
                                                           PREV_NO_DUPLICATE);
       request.onerror = errorHandler;
       request.onsuccess = function (event) {
@@ -989,16 +1014,51 @@
         else {
           testGenerator.next();
         }
       }
       yield;
 
       is(keyIndex, 4, "Saw all the expected keys");
 
+      ok(true, "Test group 24.5");
+
+      keyIndex = 5;
+
+      request = objectStore.index("height").openCursor(null, PREV);
+      request.onerror = errorHandler;
+      request.onsuccess = function (event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          is(cursor.key, objectStoreDataHeightSort[keyIndex].value.height,
+             "Correct key");
+          is(cursor.primaryKey, objectStoreDataHeightSort[keyIndex].key,
+             "Correct primary key");
+          is(cursor.value.name, objectStoreDataHeightSort[keyIndex].value.name,
+             "Correct name");
+          is(cursor.value.height,
+             objectStoreDataHeightSort[keyIndex].value.height,
+             "Correct height");
+          if ("weight" in cursor.value) {
+            is(cursor.value.weight,
+               objectStoreDataHeightSort[keyIndex].value.weight,
+               "Correct weight");
+          }
+
+          cursor.continue();
+          keyIndex--;
+        }
+        else {
+          testGenerator.next();
+        }
+      }
+      yield;
+
+      is(keyIndex, -1, "Saw all the expected keys");
+
       ok(true, "Test group 25");
 
       keyIndex = 5;
 
       request = objectStore.index("height").openCursor(null,
                                                        PREV_NO_DUPLICATE);
       request.onerror = errorHandler;
       request.onsuccess = function (event) {
--- a/dom/indexedDB/test/test_objectStore_remove_values.html
+++ b/dom/indexedDB/test/test_objectStore_remove_values.html
@@ -35,17 +35,17 @@ function testSteps()
       autoIncrement: true,
       storedObject: {name: "Lincoln"},
       keyName: undefined,
       keyValue: undefined,
     },
     { name: "out of line key; no key generator",
       autoIncrement: false,
       storedObject: {name: "Lincoln"},
-      keyName: "",
+      keyName: null,
       keyValue: 1,
     }
   ];
 
   for (let i = 0; i < data.length; i++) {
     let test = data[i];
 
     let request = mozIndexedDB.open(name, i+1, description);
--- a/dom/indexedDB/test/test_odd_result_order.html
+++ b/dom/indexedDB/test/test_odd_result_order.html
@@ -69,17 +69,17 @@
 
       key = undefined;
       setTimeout(function() {
         key = request.result;
         continueToNextStep();
       }, 0);
       yield;
 
-      is(key, true, "Got the right key");
+      ok(key === undefined, "Got the right value");
 
       finishTest();
       yield;
     }
   </script>
   <script type="text/javascript;version=1.7" src="helpers.js"></script>
 </head>
 
--- a/dom/indexedDB/test/test_open_empty_db.html
+++ b/dom/indexedDB/test/test_open_empty_db.html
@@ -7,55 +7,48 @@
   <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 testSteps()
     {
-      const name = window.location.pathname;
-      const description = "My Test Database";
+      const names = [
+        "",
+        null,
+        undefined,
+        window.location.pathname
+      ];
 
-      let request;
+      const version = 1;
+
+      for each (let name in names) {
+        let request = mozIndexedDB.open(name, version);
+        request.onerror = errorHandler;
+        request.onsuccess = grabEventAndContinueHandler;
+        let event = yield;
 
-      try {
-        request = mozIndexedDB.open("", 1);
-        ok(false, "Open with empty name should have thrown!");
-      }
-      catch(e) {
-        is(e instanceof IDBDatabaseException, true, "Got IDBDatabaseException");
-        is(e.code, IDBDatabaseException.NON_TRANSIENT_ERR, "Good error code");
-        is(request, undefined, "Shouldn't be set to anything");
-      }
+        if (name === null) {
+          name = "null";
+        }
+        else if (name === undefined) {
+          name = "undefined";
+        }
 
-      try {
-        request = mozIndexedDB.open(null, 1);
-        ok(false, "Open with null name should have thrown!");
+        let db = event.target.result;
+        is(db.name, name, "Bad name");
+        is(db.version, version, "Bad version");
+        is(db.objectStoreNames.length, 0, "Bad objectStores list");
+
+        is(db.name, request.result.name, "Bad name");
+        is(db.version, request.result.version, "Bad version");
+        is(db.objectStoreNames.length, request.result.objectStoreNames.length,
+           "Bad objectStores list");
       }
-      catch(e) {
-        is(e instanceof IDBDatabaseException, true, "Got IDBDatabaseException");
-        is(e.code, IDBDatabaseException.NON_TRANSIENT_ERR, "Good error code");
-        is(request, undefined, "Shouldn't be set to anything");
-      }
-
-      request = mozIndexedDB.open(name, 1, description);
-      request.onerror = errorHandler;
-      request.onsuccess = grabEventAndContinueHandler;
-      let event = yield;
-
-      let db = event.target.result;
-      is(db.name, name, "Bad name");
-      is(db.version, 1, "Bad version");
-      is(db.objectStoreNames.length, 0, "Bad objectStores list");
-
-      is(db.name, request.result.name, "Bad name");
-      is(db.version, request.result.version, "Bad version");
-      is(db.objectStoreNames.length, request.result.objectStoreNames.length,
-         "Bad objectStores list");
 
       finishTest();
       yield;
     }
   </script>
   <script type="text/javascript;version=1.7" src="helpers.js"></script>
 
 </head>
--- a/dom/indexedDB/test/test_optionalArguments.html
+++ b/dom/indexedDB/test/test_optionalArguments.html
@@ -189,41 +189,41 @@
       objectStore.count().onsuccess = grabEventAndContinueHandler;
       event = yield;
 
       is(event.target.result, data.length, "Correct count");
 
       objectStore.delete(data[2].ssn).onsuccess = grabEventAndContinueHandler;
       event = yield;
 
-      is(event.target.result, true, "Correct result");
+      ok(event.target.result === undefined, "Correct result");
 
       objectStore.count().onsuccess = grabEventAndContinueHandler;
       event = yield;
 
       is(event.target.result, data.length - 1, "Correct count");
 
       keyRange = IDBKeyRange.bound(data[3].ssn, data[5].ssn);
 
       objectStore.delete(keyRange).onsuccess = grabEventAndContinueHandler;
       event = yield;
 
-      is(event.target.result, true, "Correct result");
+      ok(event.target.result === undefined, "Correct result");
 
       objectStore.count().onsuccess = grabEventAndContinueHandler;
       event = yield;
 
       is(event.target.result, data.length - 4, "Correct count");
 
       keyRange = IDBKeyRange.lowerBound(10);
 
       objectStore.delete(keyRange).onsuccess = grabEventAndContinueHandler;
       event = yield;
 
-      is(event.target.result, true, "Correct result");
+      ok(event.target.result === undefined, "Correct result");
 
       objectStore.count().onsuccess = grabEventAndContinueHandler;
       event = yield;
 
       is(event.target.result, 0, "Correct count");
 
       event.target.transaction.oncomplete = grabEventAndContinueHandler;
 
--- a/dom/indexedDB/test/test_setVersion_events.html
+++ b/dom/indexedDB/test/test_setVersion_events.html
@@ -137,19 +137,19 @@
       is(event.oldVersion, 3, "Correct event oldVersion");
       is(event.newVersion, 4, "Correct event newVersion");
 
       request.onsuccess = grabEventAndContinueHandler;
 
       event = yield;
       ok(event.target.result instanceof IDBDatabase, "Expect a database here");
       is(event.target.result.version, 4, "Right version");
-      todo_is(db3.version, 3, "After closing the version should not change!");
-      todo_is(db2.version, 2, "After closing the version should not change!");
-      todo_is(db1.version, 1, "After closing the version should not change!");
+      is(db3.version, 3, "After closing the version should not change!");
+      is(db2.version, 2, "After closing the version should not change!");
+      is(db1.version, 1, "After closing the version should not change!");
 
       is(versionChangeEventCount, 3, "Saw all expected events");
 
       finishTest();
       yield;
     }
   </script>
   <script type="text/javascript;version=1.7" src="helpers.js"></script>
--- a/dom/indexedDB/test/test_transaction_abort.html
+++ b/dom/indexedDB/test/test_transaction_abort.html
@@ -31,33 +31,38 @@
       request.onupgradeneeded = grabEventAndContinueHandler;
       request.onsuccess = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
 
       let transaction;
       let objectStore;
+      let index;
 
       transaction = event.target.transaction;
       objectStore = db.createObjectStore("foo", { autoIncrement: true });
+      index = objectStore.createIndex("fooindex", "indexKey", { unique: true });
 
       is(transaction.db, db, "Correct database");
       is(transaction.readyState, LOADING, "Correct readyState");
       is(transaction.mode, VERSION_CHANGE, "Correct mode");
       is(transaction.objectStoreNames.length, 1, "Correct names length");
       is(transaction.objectStoreNames.item(0), "foo", "Correct name");
       is(transaction.objectStore("foo"), objectStore, "Can get stores");
       is(transaction.oncomplete, null, "No complete listener");
       is(transaction.onabort, null, "No abort listener");
       is(transaction.ontimeout, null, "No timeout listener");
 
       is(objectStore.name, "foo", "Correct name");
       is(objectStore.keyPath, "", "Correct keyPath");
-      is(objectStore.indexNames.length, 0, "Correct indexNames");
+
+      is(objectStore.indexNames.length, 1, "Correct indexNames length");
+      is(objectStore.indexNames[0], "fooindex", "Correct indexNames name");
+      is(objectStore.index("fooindex"), index, "Can get index");
 
       // Wait until it's complete!
       transaction.oncomplete = grabEventAndContinueHandler;
       event = yield;
 
       is(transaction.db, db, "Correct database");
       is(transaction.readyState, DONE, "Correct readyState");
       is(transaction.mode, VERSION_CHANGE, "Correct mode");
@@ -71,17 +76,19 @@
         ok(false, "Should have thrown");
       }
       catch (e) {
         ok(true, "Out of scope transaction can't make stores");
       }
 
       is(objectStore.name, "foo", "Correct name");
       is(objectStore.keyPath, "", "Correct keyPath");
-      is(objectStore.indexNames.length, 0, "Correct indexNames");
+
+      is(objectStore.indexNames.length, 1, "Correct indexNames length");
+      is(objectStore.indexNames[0], "fooindex", "Correct indexNames name");
 
       try {
         objectStore.add({});
         ok(false, "Should have thrown");
       }
       catch (e) {
         ok(true, "Add threw");
       }
@@ -359,16 +366,45 @@
       event = yield;
       is(transaction.readyState, DONE, "in DONE state");
 
       // Since the previous transaction shouldn't have caused any error events,
       // we know that all events should have fired by now.
       is(abortEventCount, expectedAbortEventCount,
          "All abort errors fired");
 
+      // Abort both failing and succeeding requests
+      transaction = db.transaction("foo", READ_WRITE);
+      transaction.onabort = transaction.oncomplete = grabEventAndContinueHandler;
+      transaction.objectStore("foo").add({indexKey: "key"}).onsuccess = function(event) {
+        transaction.abort();
+      };
+      let request1 = transaction.objectStore("foo").add({indexKey: "key"});
+      request1.onsuccess = grabEventAndContinueHandler;
+      request1.onerror = grabEventAndContinueHandler;
+      let request2 = transaction.objectStore("foo").get(1);
+      request2.onsuccess = grabEventAndContinueHandler;
+      request2.onerror = grabEventAndContinueHandler;
+
+      event = yield;
+      is(event.type, "error", "abort() should make all requests fail");
+      is(event.target, request1, "abort() should make all requests fail");
+      is(event.target.errorCode, IDBDatabaseException.ABORT_ERR, "abort() should make all requests fail");
+      event.preventDefault();
+
+      event = yield;
+      is(event.type, "error", "abort() should make all requests fail");
+      is(event.target, request2, "abort() should make all requests fail");
+      is(event.target.errorCode, IDBDatabaseException.ABORT_ERR, "abort() should make all requests fail");
+      event.preventDefault();
+
+      event = yield;
+      is(event.type, "abort", "transaction should fail");
+      is(event.target, transaction, "transaction should fail");
+
       finishTest();
       yield;
     }
   </script>
   <script type="text/javascript;version=1.7" src="helpers.js"></script>
 
 </head>
 
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -533,40 +533,48 @@ struct MainThreadChromeWorkerStructuredC
 {
   static JSObject*
   Read(JSContext* aCx, JSStructuredCloneReader* aReader, uint32 aTag,
        uint32 aData, void* aClosure)
   {
     AssertIsOnMainThread();
 
     JSObject* clone =
+      MainThreadWorkerStructuredCloneCallbacks::Read(aCx, aReader, aTag, aData,
+                                                     aClosure);
+    if (clone) {
+      return clone;
+    }
+
+    clone =
       ChromeWorkerStructuredCloneCallbacks::Read(aCx, aReader, aTag, aData,
                                                  aClosure);
     if (clone) {
       return clone;
     }
 
     JS_ClearPendingException(aCx);
     return NS_DOMReadStructuredClone(aCx, aReader, aTag, aData, nsnull);
   }
 
   static JSBool
   Write(JSContext* aCx, JSStructuredCloneWriter* aWriter, JSObject* aObj,
         void* aClosure)
   {
     AssertIsOnMainThread();
 
-    JSBool ok =
-      ChromeWorkerStructuredCloneCallbacks::Write(aCx, aWriter, aObj, aClosure);
-    if (ok) {
-      return ok;
+    if (MainThreadWorkerStructuredCloneCallbacks::Write(aCx, aWriter, aObj,
+                                                        aClosure) ||
+        ChromeWorkerStructuredCloneCallbacks::Write(aCx, aWriter, aObj,
+                                                    aClosure) ||
+        NS_DOMWriteStructuredClone(aCx, aWriter, aObj, nsnull)) {
+      return true;
     }
 
-    JS_ClearPendingException(aCx);
-    return NS_DOMWriteStructuredClone(aCx, aWriter, aObj, nsnull);
+    return false;
   }
 
   static void
   Error(JSContext* aCx, uint32 aErrorId)
   {
     AssertIsOnMainThread();
 
     NS_DOMStructuredCloneError(aCx, aErrorId);
--- a/dom/workers/XMLHttpRequest.cpp
+++ b/dom/workers/XMLHttpRequest.cpp
@@ -37,16 +37,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "XMLHttpRequest.h"
 
 #include "jsapi.h"
 #include "jscntxt.h"
 #include "jsfriendapi.h"
 
+#include "Exceptions.h"
 #include "WorkerPrivate.h"
 #include "XMLHttpRequestPrivate.h"
 
 #include "WorkerInlines.h"
 
 #define PROPERTY_FLAGS \
   JSPROP_ENUMERATE | JSPROP_SHARED
 
@@ -270,20 +271,22 @@ class XMLHttpRequest
 
   enum SLOT {
     SLOT_channel = 0,
     SLOT_responseXML,
     SLOT_responseText,
     SLOT_status,
     SLOT_statusText,
     SLOT_readyState,
+    SLOT_response,
     SLOT_multipart,
     SLOT_mozBackgroundRequest,
     SLOT_withCredentials,
     SLOT_upload,
+    SLOT_responseType,
 
     SLOT_COUNT
   };
 
   enum {
     UNSENT = 0,
     OPENED = 1,
     HEADERS_RECEIVED = 2,
@@ -337,16 +340,17 @@ public:
       return false; \
     } \
   }
 
     HANDLE_STATE_VALUE(mResponseText, SLOT_responseText)
     HANDLE_STATE_VALUE(mStatus, SLOT_status)
     HANDLE_STATE_VALUE(mStatusText, SLOT_statusText)
     HANDLE_STATE_VALUE(mReadyState, SLOT_readyState)
+    HANDLE_STATE_VALUE(mResponse, SLOT_response)
 
 #undef HANDLE_STATE_VALUE
 
     return true;
   }
 
 private:
   // No instance of this class should ever be created so these are explicitly
@@ -390,29 +394,36 @@ private:
   static JSBool
   Construct(JSContext* aCx, uintN aArgc, jsval* aVp)
   {
     JSObject* obj = JS_NewObject(aCx, &sClass, NULL, NULL);
     if (!obj) {
       return false;
     }
 
+    JSString* textStr = JS_NewStringCopyN(aCx, "text", 4);
+    if (!textStr) {
+      return false;
+    }
+
     jsval emptyString = JS_GetEmptyStringValue(aCx);
     jsval zero = INT_TO_JSVAL(0);
 
     if (!JS_SetReservedSlot(aCx, obj, SLOT_channel, JSVAL_NULL) ||
         !JS_SetReservedSlot(aCx, obj, SLOT_responseXML, JSVAL_NULL) ||
         !JS_SetReservedSlot(aCx, obj, SLOT_responseText, emptyString) ||
         !JS_SetReservedSlot(aCx, obj, SLOT_status, zero) ||
         !JS_SetReservedSlot(aCx, obj, SLOT_statusText, emptyString) ||
         !JS_SetReservedSlot(aCx, obj, SLOT_readyState, zero) ||
         !JS_SetReservedSlot(aCx, obj, SLOT_multipart, JSVAL_FALSE) ||
         !JS_SetReservedSlot(aCx, obj, SLOT_mozBackgroundRequest, JSVAL_FALSE) ||
         !JS_SetReservedSlot(aCx, obj, SLOT_withCredentials, JSVAL_FALSE) ||
-        !JS_SetReservedSlot(aCx, obj, SLOT_upload, JSVAL_NULL)) {
+        !JS_SetReservedSlot(aCx, obj, SLOT_upload, JSVAL_NULL) ||
+        !JS_SetReservedSlot(aCx, obj, SLOT_responseType,
+                            STRING_TO_JSVAL(textStr))) {
       return false;
     }
 
     WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
     XMLHttpRequestPrivate* priv = new XMLHttpRequestPrivate(obj, workerPrivate);
     if (!SetJSPrivateSafeish(aCx, obj, priv)) {
       delete priv;
       return false;
@@ -457,17 +468,17 @@ private:
 
     jsval rval;
     if (!JS_GetReservedSlot(aCx, aObj, slot, &rval)) {
       return false;
     }
 
     if (JSVAL_IS_VOID(rval)) {
       // Throw an exception.
-      JS_ReportError(aCx, "Unable to retrieve %s property", name);
+      exceptions::ThrowDOMExceptionForCode(aCx, INVALID_STATE_ERR);
       return false;
     }
 
     *aVp = rval;
     return true;
   }
 
   static JSBool
@@ -530,29 +541,35 @@ private:
     int32 slot = JSID_TO_INT(aIdval);                                          \
                                                                                \
     XMLHttpRequestPrivate* priv =                                              \
       GetInstancePrivate(aCx, aObj, sProperties[slot].name);                   \
     if (!priv) {                                                               \
       return false;                                                            \
     }                                                                          \
                                                                                \
+    jsval oldVal;                                                              \
+    if (!JS_GetReservedSlot(aCx, aObj, slot, &oldVal)) {                       \
+      return false;                                                            \
+    }                                                                          \
+                                                                               \
     jsval rval = *aVp;                                                         \
-    if (!priv->Set##_name (aCx, &rval) ||                                      \
+    if (!priv->Set##_name (aCx, oldVal, &rval) ||                              \
         !JS_SetReservedSlot(aCx, aObj, slot, rval)) {                          \
       return false;                                                            \
     }                                                                          \
                                                                                \
     *aVp = rval;                                                               \
     return true;                                                               \
   }
 
   IMPL_SETTER(Multipart)
   IMPL_SETTER(MozBackgroundRequest)
   IMPL_SETTER(WithCredentials)
+  IMPL_SETTER(ResponseType)
 
 #undef IMPL_SETTER
 
   static JSBool
   GetEventListener(JSContext* aCx, JSObject* aObj, jsid aIdval, jsval* aVp)
   {
     JS_ASSERT(JSID_IS_INT(aIdval));
     JS_ASSERT(JSID_TO_INT(aIdval) >= 0 && JSID_TO_INT(aIdval) < STRING_COUNT);
@@ -778,23 +795,27 @@ JSPropertySpec XMLHttpRequest::sProperti
     js_GetterOnlyPropertyStub },
 
   GENERIC_READONLY_PROPERTY(channel)
   GENERIC_READONLY_PROPERTY(responseXML)
   GENERIC_READONLY_PROPERTY(responseText)
   GENERIC_READONLY_PROPERTY(status)
   GENERIC_READONLY_PROPERTY(statusText)
   GENERIC_READONLY_PROPERTY(readyState)
+  GENERIC_READONLY_PROPERTY(response)
 
-  { "multipart", 7, PROPERTY_FLAGS, GetProperty, SetMultipart },
-  { "mozBackgroundRequest", 8, PROPERTY_FLAGS, GetProperty,
-    SetMozBackgroundRequest },
-  { "withCredentials", 9, PROPERTY_FLAGS, GetProperty, SetWithCredentials },
-  { "upload", SLOT_upload, PROPERTY_FLAGS, GetUpload, 
+  { "multipart", SLOT_multipart, PROPERTY_FLAGS, GetProperty, SetMultipart },
+  { "mozBackgroundRequest", SLOT_mozBackgroundRequest, PROPERTY_FLAGS,
+    GetProperty, SetMozBackgroundRequest },
+  { "withCredentials", SLOT_withCredentials, PROPERTY_FLAGS, GetProperty,
+    SetWithCredentials },
+  { "upload", SLOT_upload, PROPERTY_FLAGS, GetUpload,
     js_GetterOnlyPropertyStub },
+  { "responseType", SLOT_responseType, PROPERTY_FLAGS, GetProperty,
+    SetResponseType },
   { sEventStrings[STRING_onreadystatechange], STRING_onreadystatechange,
     PROPERTY_FLAGS, GetEventListener, SetEventListener },
   { sEventStrings[STRING_onabort], STRING_onabort, PROPERTY_FLAGS,
     GetEventListener, SetEventListener },
   { sEventStrings[STRING_onerror], STRING_onerror, PROPERTY_FLAGS,
     GetEventListener, SetEventListener },
   { sEventStrings[STRING_onload], STRING_onload, PROPERTY_FLAGS,
     GetEventListener, SetEventListener },
--- a/dom/workers/XMLHttpRequest.h
+++ b/dom/workers/XMLHttpRequest.h
@@ -51,20 +51,22 @@ bool
 InitClasses(JSContext* aCx, JSObject* aGlobal, JSObject* aProto);
 
 struct StateData
 {
   jsval mResponseText;
   jsval mStatus;
   jsval mStatusText;
   jsval mReadyState;
+  jsval mResponse;
   bool mResponseTextException;
   bool mStatusException;
   bool mStatusTextException;
   bool mReadyStateException;
+  bool mResponseException;
 };
 
 bool
 UpdateXHRState(JSContext* aCx, JSObject* aObj, bool aIsUpload,
                const StateData& aNewState);
 
 } // namespace xhr
 
--- a/dom/workers/XMLHttpRequestPrivate.cpp
+++ b/dom/workers/XMLHttpRequestPrivate.cpp
@@ -36,17 +36,16 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "XMLHttpRequestPrivate.h"
 
 #include "nsIDOMEvent.h"
 #include "nsIDOMEventListener.h"
 #include "nsIDOMProgressEvent.h"
-#include "nsIJSContextStack.h"
 #include "nsIRunnable.h"
 #include "nsIXMLHttpRequest.h"
 #include "nsIXPConnect.h"
 
 #include "jstypedarray.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsContentUtils.h"
@@ -83,17 +82,16 @@ public:
   PRUint64 mLastUploadTotal;
   bool mIsSyncXHR;
   bool mLastLengthComputable;
   bool mLastUploadLengthComputable;
   bool mSeenLoadStart;
   bool mSeenUploadLoadStart;
 
   // Only touched on the main thread.
-  nsString mPreviousResponseText;
   nsCString mPreviousStatusText;
   PRUint32 mSyncQueueKey;
   PRUint32 mSyncEventResponseSyncQueueKey;
   bool mUploadEventListenersAttached;
   bool mMainThreadSeenLoadStart;
 
 public:
   NS_DECL_ISUPPORTS
@@ -153,17 +151,16 @@ public:
   bool
   AddRemoveEventListeners(bool aUpload, bool aAdd);
 
   void
   Reset()
   {
     AssertIsOnMainThread();
 
-    mPreviousResponseText.Truncate();
     mPreviousStatusText.Truncate();
 
     if (mUploadEventListenersAttached) {
       AddRemoveEventListeners(true, false);
     }
   }
 
   PRUint32
@@ -434,70 +431,98 @@ public:
   }
 };
 
 NS_IMPL_ISUPPORTS2(LoadStartDetectionRunnable, nsIRunnable, nsIDOMEventListener)
 
 class EventRunnable : public MainThreadProxyRunnable
 {
   nsString mType;
-  nsString mResponseText;
+  nsString mResponseType;
+  JSAutoStructuredCloneBuffer mResponseBuffer;
+  nsTArray<nsCOMPtr<nsISupports> > mClonedObjects;
+  jsval mResponse;
   nsCString mStatusText;
   PRUint64 mLoaded;
   PRUint64 mTotal;
   PRUint32 mChannelId;
   PRUint32 mStatus;
   PRUint16 mReadyState;
   bool mUploadEvent;
   bool mProgressEvent;
   bool mLengthComputable;
   bool mResponseTextException;
   bool mStatusException;
   bool mStatusTextException;
   bool mReadyStateException;
+  bool mResponseException;
 
 public:
   EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType,
                 bool aLengthComputable, PRUint64 aLoaded, PRUint64 aTotal)
   : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy), mType(aType),
-    mLoaded(aLoaded), mTotal(aTotal), mChannelId(aProxy->mInnerChannelId),
-    mStatus(0), mReadyState(0), mUploadEvent(aUploadEvent),
-    mProgressEvent(true), mLengthComputable(aLengthComputable),
-    mResponseTextException(false), mStatusException(false),
-    mStatusTextException(false), mReadyStateException(false)
+    mResponse(JSVAL_VOID), mLoaded(aLoaded), mTotal(aTotal),
+    mChannelId(aProxy->mInnerChannelId), mStatus(0), mReadyState(0),
+    mUploadEvent(aUploadEvent), mProgressEvent(true),
+    mLengthComputable(aLengthComputable), mResponseTextException(false),
+    mStatusException(false), mStatusTextException(false),
+    mReadyStateException(false), mResponseException(false)
   { }
 
   EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType)
   : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy), mType(aType),
-    mLoaded(0), mTotal(0), mChannelId(aProxy->mInnerChannelId), mStatus(0),
-    mReadyState(0), mUploadEvent(aUploadEvent), mProgressEvent(false),
-    mLengthComputable(0), mResponseTextException(false),
-    mStatusException(false), mStatusTextException(false),
-    mReadyStateException(false)
+    mResponse(JSVAL_VOID), mLoaded(0), mTotal(0),
+    mChannelId(aProxy->mInnerChannelId), mStatus(0), mReadyState(0),
+    mUploadEvent(aUploadEvent), mProgressEvent(false), mLengthComputable(0),
+    mResponseTextException(false), mStatusException(false),
+    mStatusTextException(false), mReadyStateException(false),
+    mResponseException(false)
   { }
 
   bool
   PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
   {
     nsRefPtr<nsXMLHttpRequest>& xhr = mProxy->mXHR;
     NS_ASSERTION(xhr, "Must have an XHR here!");
 
-    if (NS_SUCCEEDED(xhr->GetResponseText(mResponseText))) {
-      if (mResponseText == mProxy->mPreviousResponseText) {
-        mResponseText.SetIsVoid(true);
+    if (NS_FAILED(xhr->GetResponseType(mResponseType))) {
+      NS_ERROR("This should never fail!");
+    }
+
+    jsval response;
+    if (NS_SUCCEEDED(xhr->GetResponse(aCx, &response))) {
+      if (JSVAL_IS_UNIVERSAL(response)) {
+        mResponse = response;
       }
       else {
-        mProxy->mPreviousResponseText = mResponseText;
+        // Anything subject to GC must be cloned.
+        JSStructuredCloneCallbacks* callbacks =
+          aWorkerPrivate->IsChromeWorker() ?
+          ChromeWorkerStructuredCloneCallbacks(true) :
+          WorkerStructuredCloneCallbacks(true);
+
+        nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
+
+        if (mResponseBuffer.write(aCx, response, callbacks, &clonedObjects)) {
+          mClonedObjects.SwapElements(clonedObjects);
+        }
+        else {
+          NS_ASSERTION(JS_IsExceptionPending(aCx),
+                       "This should really never fail unless OOM!");
+          mResponseException = true;
+        }
       }
-      mResponseTextException = false;
     }
     else {
-      mResponseTextException = true;
+      mResponseException = true;
     }
 
+    nsString responseText;
+    mResponseTextException = NS_FAILED(xhr->GetResponseText(responseText));
+
     mStatusException = NS_FAILED(xhr->GetStatus(&mStatus));
 
     if (NS_SUCCEEDED(xhr->GetStatusText(mStatusText))) {
       if (mStatusText == mProxy->mPreviousStatusText) {
         mStatusText.SetIsVoid(true);
       }
       else {
         mProxy->mPreviousStatusText = mStatusText;
@@ -561,31 +586,54 @@ public:
                        mProxy->mXMLHttpRequestPrivate->GetJSObject();
     if (!target) {
       NS_ASSERTION(mUploadEvent, "How else is this possible?!");
       return true;
     }
 
     xhr::StateData state;
 
+    state.mResponseException = mResponseException;
+    if (!mResponseException) {
+      if (mResponseBuffer.data()) {
+        NS_ASSERTION(JSVAL_IS_VOID(mResponse), "Huh?!");
+
+        JSStructuredCloneCallbacks* callbacks =
+          aWorkerPrivate->IsChromeWorker() ?
+          ChromeWorkerStructuredCloneCallbacks(false) :
+          WorkerStructuredCloneCallbacks(false);
+
+        nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
+        clonedObjects.SwapElements(mClonedObjects);
+
+        jsval response;
+        if (!mResponseBuffer.read(aCx, &response, callbacks, &clonedObjects)) {
+          return false;
+        }
+
+        mResponseBuffer.clear();
+        state.mResponse = response;
+      }
+      else {
+        state.mResponse = mResponse;
+      }
+    }
+
+    // This logic is all based on the assumption that mResponseTextException
+    // should be set if the responseType isn't "text". Otherwise we're going to
+    // hand out the wrong result if someone gets the responseText property.
     state.mResponseTextException = mResponseTextException;
-    if (mResponseTextException || mResponseText.IsVoid()) {
-      state.mResponseText = JSVAL_VOID;
-    }
-    else if (mResponseText.IsEmpty()) {
-      state.mResponseText = JS_GetEmptyStringValue(aCx);
+    if (!mResponseTextException) {
+      NS_ASSERTION(JSVAL_IS_STRING(state.mResponse) ||
+                   JSVAL_IS_NULL(state.mResponse),
+                   "Bad response!");
+      state.mResponseText = state.mResponse;
     }
     else {
-      JSString* responseText = JS_NewUCStringCopyN(aCx, mResponseText.get(),
-                                                   mResponseText.Length());
-      if (!responseText) {
-        return false;
-      }
-      mResponseText.Truncate();
-      state.mResponseText = STRING_TO_JSVAL(responseText);
+      state.mResponseText = JSVAL_VOID;
     }
 
     state.mStatusException = mStatusException;
     state.mStatus = mStatusException ? JSVAL_VOID : INT_TO_JSVAL(mStatus);
 
     state.mStatusTextException = mStatusTextException;
     if (mStatusTextException || mStatusText.IsVoid()) {
       state.mStatusText = JSVAL_VOID;
@@ -626,16 +674,29 @@ public:
       return false;
     }
 
     bool dummy;
     if (!events::DispatchEventToTarget(aCx, target, event, &dummy)) {
       JS_ReportPendingException(aCx);
     }
 
+    // After firing the event set mResponse to JSVAL_NULL for chunked response
+    // types.
+    if (StringBeginsWith(mResponseType, NS_LITERAL_STRING("moz-chunked-"))) {
+      xhr::StateData newState = {
+        JSVAL_NULL, JSVAL_VOID, JSVAL_VOID, JSVAL_VOID, JSVAL_NULL,
+        false, false, false, false, false
+      };
+
+      if (!xhr::UpdateXHRState(aCx, target, mUploadEvent, newState)) {
+        return false;
+      }
+    }
+
     return true;
   }
 };
 
 class WorkerThreadProxySyncRunnable : public nsRunnable
 {
 protected:
   WorkerPrivate* mWorkerPrivate;
@@ -778,16 +839,44 @@ public:
   intN
   MainThreadRun()
   {
     nsresult rv = mProxy->mXHR->SetWithCredentials(mValue);
     return GetDOMExceptionCodeFromResult(rv);
   }
 };
 
+class SetResponseTypeRunnable : public WorkerThreadProxySyncRunnable
+{
+  nsString mResponseType;
+
+public:
+  SetResponseTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+                          const nsAString& aResponseType)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+    mResponseType(aResponseType)
+  { }
+
+  intN
+  MainThreadRun()
+  {
+    nsresult rv = mProxy->mXHR->SetResponseType(mResponseType);
+    mResponseType.Truncate();
+    if (NS_SUCCEEDED(rv)) {
+      rv = mProxy->mXHR->GetResponseType(mResponseType);
+    }
+    return GetDOMExceptionCodeFromResult(rv);
+  }
+
+  void
+  GetResponseType(nsAString& aResponseType) {
+    aResponseType.Assign(mResponseType);
+  }
+};
+
 class AbortRunnable : public WorkerThreadProxySyncRunnable
 {
 public:
   AbortRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
   : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
   { }
 
   intN
@@ -906,20 +995,22 @@ public:
     if (mWithCredentials) {
       rv = mProxy->mXHR->SetWithCredentials(mWithCredentials);
       if (NS_FAILED(rv)) {
         return GetDOMExceptionCodeFromResult(rv);
       }
     }
 
     mProxy->mInnerChannelId++;
-    mProxy->mPreviousResponseText.Truncate();
     mProxy->mPreviousStatusText.Truncate();
 
     rv = mProxy->mXHR->Open(mMethod, mURL, true, mUser, mPassword, 1);
+    if (NS_SUCCEEDED(rv)) {
+      rv = mProxy->mXHR->SetResponseType(NS_LITERAL_STRING("text"));
+    }
     return GetDOMExceptionCodeFromResult(rv);
   }
 };
 
 class SendRunnable : public WorkerThreadProxySyncRunnable
 {
   JSAutoStructuredCloneBuffer mBody;
   PRUint32 mSyncQueueKey;
@@ -1170,17 +1261,20 @@ Proxy::HandleEvent(nsIDOMEvent* aEvent)
     }
     runnable = new EventRunnable(this, !!uploadTarget, type, lengthComputable,
                                  loaded, total);
   }
   else {
     runnable = new EventRunnable(this, !!uploadTarget, type);
   }
 
-  runnable->Dispatch(nsnull);
+  {
+    RuntimeService::AutoSafeJSContext cx;
+    runnable->Dispatch(cx);
+  }
 
   if (!uploadTarget) {
     if (type.EqualsASCII(sEventStrings[STRING_loadstart])) {
       NS_ASSERTION(!mMainThreadSeenLoadStart, "Huh?!");
       mMainThreadSeenLoadStart = true;
     }
     else if (mMainThreadSeenLoadStart &&
              type.EqualsASCII(sEventStrings[STRING_loadend])) {
@@ -1271,17 +1365,17 @@ XMLHttpRequestPrivate::Notify(JSContext*
     mCanceled = true;
     ReleaseProxy();
   }
 
   return true;
 }
 
 bool
-XMLHttpRequestPrivate::SetMultipart(JSContext* aCx, jsval *aVp)
+XMLHttpRequestPrivate::SetMultipart(JSContext* aCx, jsval aOldVal, jsval *aVp)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (mCanceled) {
     return false;
   }
 
   JSBool multipart;
@@ -1301,17 +1395,18 @@ XMLHttpRequestPrivate::SetMultipart(JSCo
   if (!runnable->Dispatch(aCx)) {
     return false;
   }
 
   return true;
 }
 
 bool
-XMLHttpRequestPrivate::SetMozBackgroundRequest(JSContext* aCx, jsval *aVp)
+XMLHttpRequestPrivate::SetMozBackgroundRequest(JSContext* aCx, jsval aOldVal,
+                                               jsval *aVp)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (mCanceled) {
     return false;
   }
 
   JSBool backgroundRequest;
@@ -1331,17 +1426,18 @@ XMLHttpRequestPrivate::SetMozBackgroundR
   if (!runnable->Dispatch(aCx)) {
     return false;
   }
 
   return true;
 }
 
 bool
-XMLHttpRequestPrivate::SetWithCredentials(JSContext* aCx, jsval *aVp)
+XMLHttpRequestPrivate::SetWithCredentials(JSContext* aCx, jsval aOldVal,
+                                          jsval *aVp)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (mCanceled) {
     return false;
   }
 
   JSBool withCredentials;
@@ -1361,16 +1457,78 @@ XMLHttpRequestPrivate::SetWithCredential
   if (!runnable->Dispatch(aCx)) {
     return false;
   }
 
   return true;
 }
 
 bool
+XMLHttpRequestPrivate::SetResponseType(JSContext* aCx, jsval aOldVal,
+                                       jsval *aVp)
+{
+  mWorkerPrivate->AssertIsOnWorkerThread();
+
+  if (mCanceled) {
+    return false;
+  }
+
+  if (!mProxy || SendInProgress()) {
+    ThrowDOMExceptionForCode(aCx, INVALID_STATE_ERR);
+    return false;
+  }
+
+  JSString* jsstr = JS_ValueToString(aCx, *aVp);
+  if (!jsstr) {
+    return false;
+  }
+
+  nsDependentJSString responseType;
+  if (!responseType.init(aCx, jsstr)) {
+    return false;
+  }
+
+  // "document" is fine for the main thread but not for a worker. Short-circuit
+  // that here.
+  if (responseType.EqualsLiteral("document")) {
+    *aVp = aOldVal;
+    return true;
+  }
+
+  nsRefPtr<SetResponseTypeRunnable> runnable =
+    new SetResponseTypeRunnable(mWorkerPrivate, mProxy, responseType);
+  if (!runnable->Dispatch(aCx)) {
+    return false;
+  }
+
+  nsString acceptedResponseType;
+  runnable->GetResponseType(acceptedResponseType);
+
+
+  if (acceptedResponseType == responseType) {
+    // Leave *aVp unchanged.
+  }
+  else if (acceptedResponseType.IsEmpty()) {
+    // Empty string.
+    *aVp = JS_GetEmptyStringValue(aCx);
+  }
+  else {
+    // Some other string.
+    jsstr = JS_NewUCStringCopyN(aCx, acceptedResponseType.get(),
+                                acceptedResponseType.Length());
+    if (!jsstr) {
+      return false;
+    }
+    *aVp = STRING_TO_JSVAL(jsstr);
+  }
+
+  return true;
+}
+
+bool
 XMLHttpRequestPrivate::Abort(JSContext* aCx)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (mCanceled) {
     return false;
   }
 
@@ -1675,18 +1833,18 @@ XMLHttpRequestPrivate::OverrideMimeType(
 bool
 XMLHttpRequestPrivate::MaybeDispatchPrematureAbortEvents(JSContext* aCx,
                                                          bool aFromOpen)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
   NS_ASSERTION(mProxy, "Must have a proxy here!");
 
   xhr::StateData state = {
-    JSVAL_VOID, JSVAL_VOID, JSVAL_VOID, INT_TO_JSVAL(4),
-    false, false, false, false
+    JSVAL_VOID, JSVAL_VOID, JSVAL_VOID, INT_TO_JSVAL(4), JSVAL_VOID,
+    false, false, false, false, false
   };
 
   if (mProxy->mSeenUploadLoadStart) {
     JSObject* target = mProxy->mXMLHttpRequestPrivate->GetUploadJSObject();
     NS_ASSERTION(target, "Must have a target!");
 
     if (!xhr::UpdateXHRState(aCx, target, true, state) ||
         !DispatchPrematureAbortEvent(aCx, target, STRING_abort, true) ||
--- a/dom/workers/XMLHttpRequestPrivate.h
+++ b/dom/workers/XMLHttpRequestPrivate.h
@@ -106,23 +106,26 @@ public:
   using events::EventTarget::TraceInstance;
   using events::EventTarget::GetEventListenerOnEventTarget;
   using events::EventTarget::SetEventListenerOnEventTarget;
 
   bool
   Notify(JSContext* aCx, Status aStatus);
 
   bool
-  SetMultipart(JSContext* aCx, jsval *aVp);
+  SetMultipart(JSContext* aCx, jsval aOldVal, jsval *aVp);
 
   bool
-  SetMozBackgroundRequest(JSContext* aCx, jsval *aVp);
+  SetMozBackgroundRequest(JSContext* aCx, jsval aOldVal, jsval *aVp);
 
   bool
-  SetWithCredentials(JSContext* aCx, jsval *aVp);
+  SetWithCredentials(JSContext* aCx, jsval aOldVal, jsval *aVp);
+
+  bool
+  SetResponseType(JSContext* aCx, jsval aOldVal, jsval *aVp);
 
   bool
   Abort(JSContext* aCx);
 
   JSString*
   GetAllResponseHeaders(JSContext* aCx);
 
   JSString*
--- a/dom/workers/test/Makefile.in
+++ b/dom/workers/test/Makefile.in
@@ -99,16 +99,18 @@ include $(topsrcdir)/config/rules.mk
   threadErrors_worker3.js \
   threadErrors_worker4.js \
   test_threadTimeouts.html \
   threadTimeouts_worker.js \
   test_throwingOnerror.html \
   throwingOnerror_worker.js \
   test_xhr.html \
   xhr_worker.js \
+  test_xhr2.html \
+  xhr2_worker.js \
   test_xhrAbort.html \
   xhrAbort_worker.js \
   testXHR.txt \
   test_fibonacci.html \
   fibonacci_worker.js \
   test_newError.html \
   newError_worker.js \
   test_chromeWorker.html \
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_xhr2.html
@@ -0,0 +1,38 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads XHR(Bug 450452 )
+-->
+<head>
+  <title>Test for DOM Worker Threads XHR (Bug 450452 )</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450452">DOM Worker Threads XHR (Bug 450452)</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+  var worker = new Worker("xhr2_worker.js");
+
+  worker.onmessage = function(event) {
+    is(event.data, "done", "Got correct result");
+    SimpleTest.finish();
+  }
+  worker.postMessage("testXHR.txt");
+
+  SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/xhr2_worker.js
@@ -0,0 +1,155 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+onmessage = function(event) {
+  const url = event.data;
+
+  var xhr = new XMLHttpRequest();
+  xhr.open("GET", url, false);
+  xhr.send();
+
+  const refText = xhr.responseText;
+
+  function getResponse(type) {
+    var xhr = new XMLHttpRequest();
+    xhr.open("GET", url, false);
+    if (type !== undefined) {
+      xhr.responseType = type;
+    }
+    xhr.send();
+    return xhr.response;
+  }
+
+  if (getResponse() != refText) {
+    throw new Error("unset responseType failed");
+  }
+
+  if (getResponse("") != refText) {
+    throw new Error("'' responseType failed");
+  }
+
+  if (getResponse("text") != refText) {
+    throw new Error("'text' responseType failed");
+  }
+
+  var array = new Uint8Array(getResponse("arraybuffer"));
+  if (String.fromCharCode.apply(String, array) != refText) {
+    throw new Error("'arraybuffer' responseType failed");
+  }
+
+  var blob = getResponse("blob");
+  if (new FileReaderSync().readAsText(blob) != refText) {
+    throw new Error("'blob' responseType failed");
+  }
+
+  // Make sure that we get invalid state exceptions when getting the wrong
+  // property.
+
+  function testResponseTextException(type) {
+    var xhr = new XMLHttpRequest();
+    xhr.open("GET", url, false);
+    xhr.responseType = type;
+    xhr.send();
+
+    var exception;
+
+    try {
+      xhr.responseText;
+    }
+    catch(e) {
+      exception = e;
+    }
+
+    if (!exception || exception.code != DOMException.INVALID_STATE_ERR) {
+      throw new Error("Failed to throw when getting responseText on '" + type +
+                      "' type");
+    }
+  }
+
+  testResponseTextException("arraybuffer");
+  testResponseTextException("blob");
+
+  // Make sure "document" works, but returns text.
+  xhr = new XMLHttpRequest();
+
+  if (xhr.responseType != "text") {
+    throw new Error("Default value for responseType is wrong!");
+  }
+
+  xhr.open("GET", url, false);
+  xhr.responseType = "document";
+  xhr.send();
+
+  if (xhr.responseText != refText) {
+    throw new Error("'document' type not working correctly");
+  }
+
+  // Make sure setting responseType before open or after send fails.
+  var exception;
+
+  xhr = new XMLHttpRequest();
+  try {
+    xhr.responseType = "arraybuffer";
+  }
+  catch(e) {
+    exception = e;
+  }
+
+  if (!exception || exception.code != DOMException.INVALID_STATE_ERR) {
+    throw new Error("Failed to throw when setting responseType before " +
+                    "calling open()");
+  }
+
+  xhr.open("GET", url);
+  xhr.responseType = "text";
+  xhr.onload = function(event) {
+    if (event.target.response != refText) {
+      throw new Error("Bad response!");
+    }
+
+    xhr = new XMLHttpRequest();
+    xhr.open("GET", url);
+    xhr.responseType = "moz-chunked-text";
+
+    var lastIndex = 0;
+    xhr.onprogress = function(event) {
+      if (refText.substr(lastIndex, xhr.response.length) != xhr.response) {
+        throw new Error("Bad chunk!");
+      }
+
+      lastIndex += xhr.response.length;
+    };
+
+    xhr.onload = function(event) {
+      if (lastIndex != refText.length) {
+        throw new Error("Didn't see all the data!");
+      }
+
+      setTimeout(function() {
+        if (xhr.response !== null) {
+          throw new Error("Should have gotten null response outside of event!");
+        }
+        postMessage("done");
+      }, 0);
+    }
+
+    xhr.send(null);
+  };
+  xhr.send();
+
+  exception = null;
+
+  try {
+    xhr.responseType = "arraybuffer";
+  }
+  catch(e) {
+    exception = e;
+  }
+
+  if (!exception || exception.code != DOMException.INVALID_STATE_ERR) {
+    throw new Error("Failed to throw when setting responseType after " +
+                    "calling send()");
+  }
+}
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1642,16 +1642,26 @@ JSID_IS_EMPTY(jsid id)
 #ifdef JS_USE_JSID_STRUCT_TYPES
 extern JS_PUBLIC_DATA(jsid) JSID_VOID;
 extern JS_PUBLIC_DATA(jsid) JSID_EMPTY;
 #else
 # define JSID_VOID ((jsid)JSID_TYPE_VOID)
 # define JSID_EMPTY ((jsid)JSID_TYPE_OBJECT)
 #endif
 
+/*
+ * Returns true iff the given jsval is immune to GC and can be used across
+ * multiple JSRuntimes without requiring any conversion API.
+ */
+static JS_ALWAYS_INLINE JSBool
+JSVAL_IS_UNIVERSAL(jsval v)
+{
+    return !JSVAL_IS_GCTHING(v);
+}
+
 /************************************************************************/
 
 /* Lock and unlock the GC thing held by a jsval. */
 #define JSVAL_LOCK(cx,v)        (JSVAL_IS_GCTHING(v)                          \
                                  ? JS_LockGCThing(cx, JSVAL_TO_GCTHING(v))    \
                                  : JS_TRUE)
 #define JSVAL_UNLOCK(cx,v)      (JSVAL_IS_GCTHING(v)                          \
                                  ? JS_UnlockGCThing(cx, JSVAL_TO_GCTHING(v))  \
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -452,16 +452,18 @@ struct JSRuntime
     js::GCLocks         gcLocksHash;
     jsrefcount          gcKeepAtoms;
     uint32              gcBytes;
     uint32              gcTriggerBytes;
     size_t              gcLastBytes;
     size_t              gcMaxBytes;
     size_t              gcMaxMallocBytes;
     uint32              gcEmptyArenaPoolLifespan;
+    /* We access this without the GC lock, however a race will not affect correctness */
+    volatile uint32     gcNumFreeArenas;
     uint32              gcNumber;
     js::GCMarker        *gcMarkingTracer;
     bool                gcChunkAllocationSinceLastGC;
     int64               gcNextFullGCTime;
     int64               gcJitReleaseTime;
     JSGCMode            gcMode;
     volatile jsuword    gcIsNeeded;
     js::WeakMapBase     *gcWeakMapList;
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -542,31 +542,31 @@ ChunkPool::countDecommittedArenas(JSRunt
 }
 
 /* static */ Chunk *
 Chunk::allocate(JSRuntime *rt)
 {
     Chunk *chunk = static_cast<Chunk *>(AllocChunk());
     if (!chunk)
         return NULL;
-    chunk->init();
+    chunk->init(rt);
     rt->gcStats.count(gcstats::STAT_NEW_CHUNK);
     return chunk;
 }
 
 /* static */ inline void
 Chunk::release(JSRuntime *rt, Chunk *chunk)
 {
     JS_ASSERT(chunk);
     rt->gcStats.count(gcstats::STAT_DESTROY_CHUNK);
     FreeChunk(chunk);
 }
 
 void
-Chunk::init()
+Chunk::init(JSRuntime *rt)
 {
     JS_POISON(this, JS_FREE_PATTERN, ChunkSize);
 
     /*
      * We clear the bitmap to guard against xpc_IsGrayGCThing being called on
      * uninitialized data, which would happen before the first GC cycle.
      */
     bitmap.clear();
@@ -575,16 +575,17 @@ Chunk::init()
     decommittedArenas.clear(false);
 
     /* Initialize the chunk info. */
     info.freeArenasHead = &arenas[0].aheader;
     info.lastDecommittedArenaOffset = 0;
     info.numArenasFree = ArenasPerChunk;
     info.numArenasFreeCommitted = ArenasPerChunk;
     info.age = 0;
+    rt->gcNumFreeArenas += ArenasPerChunk;
 
     /* Initialize the arena header state. */
     for (jsuint i = 0; i < ArenasPerChunk; i++) {
         arenas[i].aheader.setAsNotAllocated();
         arenas[i].aheader.next = (i + 1 < ArenasPerChunk)
                                  ? &arenas[i + 1].aheader
                                  : NULL;
     }
@@ -663,41 +664,43 @@ Chunk::fetchNextDecommittedArena()
     Arena *arena = &arenas[offset];
     CommitMemory(arena, ArenaSize);
     arena->aheader.setAsNotAllocated();
 
     return &arena->aheader;
 }
 
 inline ArenaHeader *
-Chunk::fetchNextFreeArena()
+Chunk::fetchNextFreeArena(JSRuntime *rt)
 {
     JS_ASSERT(info.numArenasFreeCommitted > 0);
 
     ArenaHeader *aheader = info.freeArenasHead;
     info.freeArenasHead = aheader->next;
     --info.numArenasFreeCommitted;
     --info.numArenasFree;
+    --rt->gcNumFreeArenas;
 
     return aheader;
 }
 
 ArenaHeader *
 Chunk::allocateArena(JSCompartment *comp, AllocKind thingKind)
 {
     JS_ASSERT(!noAvailableArenas());
 
+    JSRuntime *rt = comp->rt;
+
     ArenaHeader *aheader = JS_LIKELY(info.numArenasFreeCommitted > 0)
-                           ? fetchNextFreeArena()
+                           ? fetchNextFreeArena(rt)
                            : fetchNextDecommittedArena();
     aheader->init(comp, thingKind);
     if (JS_UNLIKELY(noAvailableArenas()))
         removeFromAvailableList();
 
-    JSRuntime *rt = comp->rt;
     Probes::resizeHeap(comp, rt->gcBytes, rt->gcBytes + ArenaSize);
     JS_ATOMIC_ADD(&rt->gcBytes, ArenaSize);
     JS_ATOMIC_ADD(&comp->gcBytes, ArenaSize);
     if (comp->gcBytes >= comp->gcTriggerBytes)
         TriggerCompartmentGC(comp, gcstats::ALLOCTRIGGER);
 
     return aheader;
 }
@@ -727,16 +730,17 @@ Chunk::releaseArena(ArenaHeader *aheader
     JS_ATOMIC_ADD(&rt->gcBytes, -int32(ArenaSize));
     JS_ATOMIC_ADD(&comp->gcBytes, -int32(ArenaSize));
 
     aheader->setAsNotAllocated();
     aheader->next = info.freeArenasHead;
     info.freeArenasHead = aheader;
     ++info.numArenasFreeCommitted;
     ++info.numArenasFree;
+    ++rt->gcNumFreeArenas;
 
     if (info.numArenasFree == 1) {
         JS_ASSERT(!info.prevp);
         JS_ASSERT(!info.next);
         addToAvailableList(comp);
     } else if (!unused()) {
         JS_ASSERT(info.prevp);
     } else {
@@ -2132,17 +2136,17 @@ MaybeGC(JSContext *cx)
     }
 
     /*
      * On 32 bit setting gcNextFullGCTime below is not atomic and a race condition
      * could trigger an GC. We tolerate this.
      */
     int64 now = PRMJ_Now();
     if (rt->gcNextFullGCTime && rt->gcNextFullGCTime <= now) {
-        if (rt->gcChunkAllocationSinceLastGC)
+        if (rt->gcChunkAllocationSinceLastGC || rt->gcNumFreeArenas > MaxFreeCommittedArenas)
             js_GC(cx, NULL, GC_SHRINK, gcstats::MAYBEGC);
         else
             rt->gcNextFullGCTime = now + GC_IDLE_FULL_SPAN;
     }
 }
 
 } /* namespace js */
 
@@ -2399,16 +2403,17 @@ DecommitFreePages(JSContext *cx)
                     aheader->next = chunk->info.freeArenasHead;
                     chunk->info.freeArenasHead = aheader;
                     continue;
                 }
 
                 size_t arenaOffset = Chunk::arenaIndex(reinterpret_cast<uintptr_t>(aheader));
                 chunk->decommittedArenas.set(arenaOffset);
                 --chunk->info.numArenasFreeCommitted;
+                --rt->gcNumFreeArenas;
 
                 aheader = next;
             }
 
             chunk = chunk->info.next;
         }
     }
 }
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -90,16 +90,22 @@ struct Arena;
  */
 const size_t MAX_BACKGROUND_FINALIZE_KINDS = FINALIZE_LIMIT - (FINALIZE_OBJECT_LAST + 1) / 2;
 
 const size_t ArenaShift = 12;
 const size_t ArenaSize = size_t(1) << ArenaShift;
 const size_t ArenaMask = ArenaSize - 1;
 
 /*
+ * This is the maximum number of arenas we allow in the FreeCommitted state
+ * before we trigger a GC_SHRINK to release free arenas to the OS.
+ */
+const static uint32 MaxFreeCommittedArenas = (32 << 20) / ArenaSize;
+
+/*
  * The mark bitmap has one bit per each GC cell. For multi-cell GC things this
  * wastes space but allows to avoid expensive devisions by thing's size when
  * accessing the bitmap. In addition this allows to use some bits for colored
  * marking during the cycle GC.
  */
 const size_t ArenaCellCount = size_t(1) << (ArenaShift - Cell::CellShift);
 const size_t ArenaBitmapBits = ArenaCellCount;
 const size_t ArenaBitmapBytes = ArenaBitmapBits / 8;
@@ -737,24 +743,24 @@ struct Chunk {
     ArenaHeader *allocateArena(JSCompartment *comp, AllocKind kind);
 
     void releaseArena(ArenaHeader *aheader);
 
     static Chunk *allocate(JSRuntime *rt);
     static inline void release(JSRuntime *rt, Chunk *chunk);
 
   private:
-    inline void init();
+    inline void init(JSRuntime *rt);
 
     /* Search for a decommitted arena to allocate. */
     jsuint findDecommittedArenaOffset();
     ArenaHeader* fetchNextDecommittedArena();
 
     /* Unlink and return the freeArenasHead. */
-    inline ArenaHeader* fetchNextFreeArena();
+    inline ArenaHeader* fetchNextFreeArena(JSRuntime *rt);
 };
 
 JS_STATIC_ASSERT(sizeof(Chunk) == ChunkSize);
 
 class ChunkPool {
     Chunk   *emptyChunkListHead;
     size_t  emptyCount;