Bug 618196 - 'IndexedDB: Error events and exceptions thrown during success events should abort transactions'. r=sicking, a=blocking.
authorBen Turner <bent.mozilla@gmail.com>
Wed, 15 Dec 2010 13:20:57 -0800
changeset 59263 b01a871ee77a07bbedd4683fc060798db91eda55
parent 59262 a9928aa3aa6812efe76a5b2e93bf3dc4fab39842
child 59264 39811128613a826bffd16a8339ac54794fdb4c30
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewerssicking, blocking
bugs618196
milestone2.0b9pre
Bug 618196 - 'IndexedDB: Error events and exceptions thrown during success events should abort transactions'. r=sicking, a=blocking.
content/events/src/nsEventListenerManager.cpp
dom/indexedDB/AsyncConnectionHelper.cpp
dom/indexedDB/test/Makefile.in
dom/indexedDB/test/error_events_abort_transactions_iframe.html
dom/indexedDB/test/event_propagation_iframe.html
dom/indexedDB/test/exceptions_in_success_events_iframe.html
dom/indexedDB/test/test_cursors.html
dom/indexedDB/test/test_error_events_abort_transactions.html
dom/indexedDB/test/test_exceptions_in_success_events.html
dom/indexedDB/test/test_key_requirements.html
widget/public/nsGUIEvent.h
--- a/content/events/src/nsEventListenerManager.cpp
+++ b/content/events/src/nsEventListenerManager.cpp
@@ -1201,18 +1201,21 @@ found:
             }
             nsRefPtr<nsIDOMEventListener> kungFuDeathGrip = ls->mListener;
             if (useTypeInterface) {
               aPusher->Pop();
               DispatchToInterface(*aDOMEvent, ls->mListener,
                                   dispData->method, *typeData->iid);
             } else if (useGenericInterface &&
                        aPusher->RePush(aCurrentTarget)) {
-              HandleEventSubType(ls, ls->mListener, *aDOMEvent,
-                                 aCurrentTarget, aFlags, aPusher);
+              if (NS_FAILED(HandleEventSubType(ls, ls->mListener, *aDOMEvent,
+                                               aCurrentTarget, aFlags,
+                                               aPusher))) {
+                aEvent->flags |= NS_EVENT_FLAG_EXCEPTION_THROWN;
+              }
             }
           }
         }
       }
     }
   }
 
   aEvent->currentTarget = nsnull;
--- a/dom/indexedDB/AsyncConnectionHelper.cpp
+++ b/dom/indexedDB/AsyncConnectionHelper.cpp
@@ -375,35 +375,61 @@ AsyncConnectionHelper::OnSuccess(nsIDOME
   nsCOMPtr<nsIDOMEvent> event =
     IDBSuccessEvent::Create(mRequest, variant, mTransaction);
   if (!event) {
     NS_ERROR("Failed to create event!");
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   PRBool dummy;
-  aTarget->DispatchEvent(event, &dummy);
+  rv = aTarget->DispatchEvent(event, &dummy);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event);
+  NS_ASSERTION(privateEvent, "This should always QI properly!");
+
+  nsEvent* internalEvent = privateEvent->GetInternalNSEvent();
+  NS_ASSERTION(internalEvent, "This should never be null!");
+
+  if ((internalEvent->flags & NS_EVENT_FLAG_EXCEPTION_THROWN) &&
+      mTransaction &&
+      mTransaction->TransactionIsOpen()) {
+    rv = mTransaction->Abort();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   return NS_OK;
 }
 
 void
 AsyncConnectionHelper::OnError(nsIDOMEventTarget* aTarget,
                                nsresult aErrorCode)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   // Make an error event and fire it at the target.
   nsCOMPtr<nsIDOMEvent> event(IDBErrorEvent::Create(mRequest, aErrorCode));
   if (!event) {
     NS_ERROR("Failed to create event!");
     return;
   }
 
-  PRBool dummy;
-  aTarget->DispatchEvent(event, &dummy);
+  PRBool doDefault;
+  nsresult rv = aTarget->DispatchEvent(event, &doDefault);
+  if (NS_SUCCEEDED(rv)) {
+    if (doDefault &&
+        mTransaction &&
+        mTransaction->TransactionIsOpen() &&
+        NS_FAILED(mTransaction->Abort())) {
+      NS_WARNING("Failed to abort transaction!");
+    }
+  }
+  else {
+    NS_WARNING("DispatchEvent failed!");
+  }
 }
 
 nsresult
 AsyncConnectionHelper::GetSuccessResult(nsIWritableVariant* /* aResult */)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   // Leave the variant remain set to empty.
--- a/dom/indexedDB/test/Makefile.in
+++ b/dom/indexedDB/test/Makefile.in
@@ -42,29 +42,33 @@ VPATH = @srcdir@
 relativesrcdir = dom/indexedDB/test
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 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 \
   test_add_twice_failure.html \
   test_bad_keypath.html \
   test_bfcache.html \
   test_clear.html \
   test_create_index.html \
   test_create_objectStore.html \
   test_cursors.html \
   test_cursor_mutation.html \
   test_cursor_update_updates_indexes.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 \
   test_indexes.html \
   test_indexes_bad_values.html \
   test_key_requirements.html \
   test_null_keys.html \
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/error_events_abort_transactions_iframe.html
@@ -0,0 +1,146 @@
+<!--
+  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;version=1.7">
+
+    let testGenerator = testSteps();
+
+    function ok(val, message) {
+      val = val ? "true" : "false";
+      window.parent.postMessage("SimpleTest.ok(" + val + ", '" + message +
+                                "');", "*");
+    }
+
+    function is(a, b, message) {
+      ok(a == b, message);
+    }
+
+    function grabEventAndContinueHandler(event) {
+      testGenerator.send(event);
+    }
+
+    function errorHandler(event) {
+      ok(false, "indexedDB error (" + event.code + "): " + event.message);
+      finishTest();
+    }
+
+    function unexpectedSuccessHandler(event) {
+      ok(false, "got success when it was not expected!");
+      finishTest();
+    }
+
+    function finishTest() {
+      // Let window.onerror have a chance to fire
+      setTimeout(function() {
+        setTimeout(function() {
+          testGenerator.close();
+          window.parent.postMessage("SimpleTest.finish();", "*");
+        }, 0);
+      }, 0);
+    }
+
+    window.onerror = function(event) {
+    };
+
+    function testSteps() {
+      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+      let uri = window.document.documentURIObject;
+      Components.classes["@mozilla.org/permissionmanager;1"]
+                .getService(Components.interfaces.nsIPermissionManager)
+                .add(uri, "indexedDB",
+                     Components.interfaces.nsIPermissionManager.ALLOW_ACTION);
+
+      let request = moz_indexedDB.open(window.location.pathname);
+      request.onerror = errorHandler;
+      request.onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      let db = event.result;
+
+      is(db.version, "", "Correct version");
+      is(db.objectStoreNames.length, 0, "Correct objectStoreNames length");
+
+      let request = db.setVersion("1");
+      request.onsuccess = grabEventAndContinueHandler;
+      request.onerror = errorHandler;
+      event = yield;
+
+      event.transaction.oncomplete = unexpectedSuccessHandler;
+      event.transaction.onabort = grabEventAndContinueHandler;
+
+      is(db.version, "1", "Correct version");
+      is(db.objectStoreNames.length, 0, "Correct objectStoreNames length");
+
+      let objectStore = db.createObjectStore("foo", "");
+
+      is(db.objectStoreNames.length, 1, "Correct objectStoreNames length");
+      ok(db.objectStoreNames.contains("foo"), "Has correct objectStore");
+
+      request = objectStore.add({}, 1);
+      request.onsuccess = grabEventAndContinueHandler;
+      request.onerror = errorHandler;
+      event = yield;
+
+      request = objectStore.add({}, 1);
+      request.onsuccess = unexpectedSuccessHandler;
+      request.onerror = function(event) {
+        // Don't do anything! We want this error.
+      }
+      event = yield;
+
+      is(event.type, "abort", "Got a transaction abort event");
+
+      is(db.version, "", "Correct version");
+      is(db.objectStoreNames.length, 0, "Correct objectStoreNames length");
+
+      request = db.setVersion("1");
+      request.onsuccess = grabEventAndContinueHandler;
+      request.onerror = errorHandler;
+      event = yield;
+
+      event.transaction.oncomplete = grabEventAndContinueHandler;
+      event.transaction.onabort = unexpectedSuccessHandler;
+
+      is(db.version, "1", "Correct version");
+      is(db.objectStoreNames.length, 0, "Correct objectStoreNames length");
+
+      let objectStore = db.createObjectStore("foo", "");
+
+      is(db.objectStoreNames.length, 1, "Correct objectStoreNames length");
+      ok(db.objectStoreNames.contains("foo"), "Has correct objectStore");
+
+      request = objectStore.add({}, 1);
+      request.onsuccess = grabEventAndContinueHandler;
+      request.onerror = errorHandler;
+      event = yield;
+
+      request = objectStore.add({}, 1);
+      request.onsuccess = unexpectedSuccessHandler;
+      request.onerror = function(event) {
+        // Expected, but prevent the abort.
+        event.preventDefault();
+      }
+      event = yield;
+
+      is(event.type, "complete", "Got a transaction complete event");
+
+      is(db.version, "1", "Correct version");
+      is(db.objectStoreNames.length, 1, "Correct objectStoreNames length");
+      ok(db.objectStoreNames.contains("foo"), "Has correct objectStore");
+
+      finishTest();
+      yield;
+    }
+  </script>
+
+</head>
+
+<body onload="testGenerator.next();"></body>
+
+</html>
--- a/dom/indexedDB/test/event_propagation_iframe.html
+++ b/dom/indexedDB/test/event_propagation_iframe.html
@@ -134,18 +134,23 @@
 
       transaction = db.transaction("foo", IDBTransaction.READ_WRITE);
       transaction.addEventListener("error", errorEventCounter, false);
       transaction.addEventListener("error", errorEventCounter, true);
 
       objectStore = transaction.objectStore("foo");
 
       request = objectStore.add({}, 1);
+      request.onsuccess = grabEventAndContinueHandler;
+      request.onerror = errorHandler;
+      event = yield;
+
+      request = objectStore.add({}, 1);
       request.onsuccess = function(event) {
-        ok(false, "Did not expect third add to succeed.");
+        ok(false, "Did not expect second add to succeed.");
       };
       request.onerror = errorEventCounter;
       yield;
     }
   </script>
 
 </head>
 
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/exceptions_in_success_events_iframe.html
@@ -0,0 +1,108 @@
+<!--
+  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="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+    let testGenerator = testSteps();
+
+    function ok(val, message) {
+      val = val ? "true" : "false";
+      window.parent.postMessage("SimpleTest.ok(" + val + ", '" + message +
+                                "');", "*");
+    }
+
+    function is(a, b, message) {
+      ok(a == b, message);
+    }
+
+    function grabEventAndContinueHandler(event) {
+      testGenerator.send(event);
+    }
+
+    function errorHandler(event) {
+      ok(false, "indexedDB error (" + event.code + "): " + event.message);
+      finishTest();
+    }
+
+    function unexpectedSuccessHandler(event) {
+      ok(false, "got success when it was not expected!");
+      finishTest();
+    }
+
+    function finishTest() {
+      // Let window.onerror have a chance to fire
+      setTimeout(function() {
+        setTimeout(function() {
+          testGenerator.close();
+          window.parent.postMessage("SimpleTest.finish();", "*");
+        }, 0);
+      }, 0);
+    }
+
+    window.onerror = function(event) {
+      event.preventDefault();
+    };
+
+    function testSteps() {
+      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+      let uri = window.document.documentURIObject;
+      Components.classes["@mozilla.org/permissionmanager;1"]
+                .getService(Components.interfaces.nsIPermissionManager)
+                .add(uri, "indexedDB",
+                     Components.interfaces.nsIPermissionManager.ALLOW_ACTION);
+
+      let request = moz_indexedDB.open(window.location.pathname);
+      request.onerror = errorHandler;
+      request.onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      let db = event.result;
+      db.onerror = function(event) {
+        event.preventDefault();
+      };
+
+      is(db.version, "", "Correct version");
+      is(db.objectStoreNames.length, 0, "Correct objectStoreNames length");
+
+      db.setVersion("1").onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      event.transaction.oncomplete = unexpectedSuccessHandler;
+      event.transaction.onabort = grabEventAndContinueHandler;
+
+      is(db.version, "1", "Correct version");
+      is(db.objectStoreNames.length, 0, "Correct objectStoreNames length");
+
+      let objectStore = db.createObjectStore("foo", "");
+
+      is(db.objectStoreNames.length, 1, "Correct objectStoreNames length");
+      ok(db.objectStoreNames.contains("foo"), "Has correct objectStore");
+
+      request = objectStore.add({}, 1);
+      request.onsuccess = function(event) {
+        throw "foo";
+      };
+
+      event = yield;
+
+      is(event.type, "abort", "Got transaction abort event");
+
+      finishTest();
+      yield;
+    }
+  </script>
+
+</head>
+
+<body onload="testGenerator.next();"></body>
+
+</html>
--- a/dom/indexedDB/test/test_cursors.html
+++ b/dom/indexedDB/test/test_cursors.html
@@ -8,21 +8,16 @@
 
   <script type="text/javascript" src="/MochiKit/packed.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript;version=1.7">
     function testSteps()
     {
-      const NOT_FOUND_ERR =
-        Components.interfaces.nsIIDBDatabaseException.NOT_FOUND_ERR;
-      const NEXT = Components.interfaces.nsIIDBCursor.NEXT;
-      const PREV = Components.interfaces.nsIIDBCursor.PREV;
-
       const name = window.location.pathname;
       const description = "My Test Database";
       const keys = [1, -1, 0, 10, 2000, "q", "z", "two", "b", "a"];
       const sortedKeys = [-1, 0, 1, 10, 2000, "a", "b", "q", "two", "z"];
 
       is(keys.length, sortedKeys.length, "Good key setup");
 
       let request = moz_indexedDB.open(name, description);
@@ -283,17 +278,17 @@
       request.onsuccess = grabEventAndContinueHandler;
       event = yield;
 
       keyIndex = 0;
 
       let gotRemoveEvent = false;
       let retval = false;
 
-      request = objectStore.openCursor(null, NEXT);
+      request = objectStore.openCursor(null, IDBCursor.NEXT);
       request.onerror = errorHandler;
       request.onsuccess = function (event) {
         let cursor = event.result;
         if (cursor) {
           is(cursor.key, sortedKeys[keyIndex], "Correct key");
           is(cursor.value, "foo", "Correct value");
 
           if (keyIndex == 4) {
@@ -314,28 +309,28 @@
         }
       }
       yield;
 
       is(keyIndex, keys.length, "Saw all the expected keys");
       is(gotRemoveEvent, true, "Saw the remove event");
 
       request = objectStore.get(sortedKeys[4]);
-      request.onerror = new ExpectError(NOT_FOUND_ERR);
+      request.onerror = new ExpectError(IDBDatabaseException.NOT_FOUND_ERR);
       request.onsuccess = unexpectedSuccessHandler;
       event = yield;
 
       request = objectStore.add("foo", sortedKeys[4]);
       request.onerror = errorHandler;
       request.onsuccess = grabEventAndContinueHandler;
       event = yield;
 
       keyIndex = sortedKeys.length - 1;
 
-      request = objectStore.openCursor(null, PREV);
+      request = objectStore.openCursor(null, IDBCursor.PREV);
       request.onerror = errorHandler;
       request.onsuccess = function (event) {
         let cursor = event.result;
         if (cursor) {
           is(cursor.key, sortedKeys[keyIndex], "Correct key");
           is(cursor.value, "foo", "Correct value");
 
           cursor.continue();
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_error_events_abort_transactions.html
@@ -0,0 +1,31 @@
+<!--
+  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="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+    function runTest() {
+      SimpleTest.waitForExplicitFinish();
+
+      function messageListener(event) {
+        eval(event.data);
+      }
+
+      window.addEventListener("message", messageListener, false);
+    }
+  </script>
+
+</head>
+
+<body onload="runTest();">
+  <iframe src="error_events_abort_transactions_iframe.html"></iframe>
+</body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_exceptions_in_success_events.html
@@ -0,0 +1,31 @@
+<!--
+  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="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+    function runTest() {
+      SimpleTest.waitForExplicitFinish();
+
+      function messageListener(event) {
+        eval(event.data);
+      }
+
+      window.addEventListener("message", messageListener, false);
+    }
+  </script>
+
+</head>
+
+<body onload="runTest();">
+  <iframe src="exceptions_in_success_events_iframe.html"></iframe>
+</body>
+
+</html>
--- a/dom/indexedDB/test/test_key_requirements.html
+++ b/dom/indexedDB/test/test_key_requirements.html
@@ -176,19 +176,19 @@
       request = objectStore.put({id:10});
       request.onerror = errorHandler;
       request.onsuccess = grabEventAndContinueHandler;
       event = yield;
 
       is(event.result, key1, "put gave back the same key");
 
       request = objectStore.add({id:10});
-      request.onerror = grabEventAndContinueHandler;
+      request.onerror = new ExpectError(IDBDatabaseException.CONSTRAINT_ERR);
       request.onsuccess = unexpectedSuccessHandler;
-      yield;
+      event = yield;
 
       request = objectStore.add({}, null);
       request.onerror = errorHandler;
       request.onsuccess = grabEventAndContinueHandler;
       event = yield;
 
       is(typeof(event.result), "string", "Good generated key");
 
--- a/widget/public/nsGUIEvent.h
+++ b/widget/public/nsGUIEvent.h
@@ -151,16 +151,18 @@ class nsHashKey;
 // Use this flag if the event should be dispatched only to chrome.
 #define NS_EVENT_FLAG_ONLY_CHROME_DISPATCH 0x2000
 
 // A flag for drag&drop handling.
 #define NS_EVENT_FLAG_NO_DEFAULT_CALLED_IN_CONTENT 0x4000
 
 #define NS_PRIV_EVENT_UNTRUSTED_PERMITTED 0x8000
 
+#define NS_EVENT_FLAG_EXCEPTION_THROWN    0x10000
+
 #define NS_EVENT_CAPTURE_MASK             (~(NS_EVENT_FLAG_BUBBLE | NS_EVENT_FLAG_NO_CONTENT_DISPATCH))
 #define NS_EVENT_BUBBLE_MASK              (~(NS_EVENT_FLAG_CAPTURE | NS_EVENT_FLAG_NO_CONTENT_DISPATCH))
 
 #define NS_EVENT_TYPE_NULL                   0
 
 /**
  * GUI MESSAGES
  */