Merge mozilla-central into mozilla-aurora
authorEhsan Akhgari <ehsan@mozilla.com>
Sat, 19 Jan 2013 11:10:03 -0500
changeset 119364 323d01be9d0d1e3650bdfdbd688932d749316cb8
parent 119363 8472e5898021a48e06e3bb3656e269ad22cc2b05 (current diff)
parent 119131 01a8559f55605a5458de7d42544af2f0c6b6a462 (diff)
child 119365 c059c73cd16c2c335bc9a6490a328a55be28eb23
push id24197
push userryanvm@gmail.com
push dateSun, 20 Jan 2013 05:25:28 +0000
treeherdermozilla-central@1d122eaa9070 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone21.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central into mozilla-aurora
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0"?>
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1357926611000">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1358464191000">
   <emItems>
       <emItem  blockID="i58" id="webmaster@buzzzzvideos.info">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i41" id="{99079a25-328f-4bd4-be04-00955acaa0a7}">
                         <versionRange  minVersion="0.1" maxVersion="4.3.1.00" severity="1">
                     </versionRange>
@@ -215,16 +215,20 @@
                     </versionRange>
                   </emItem>
       <emItem  blockID="i22" id="ShopperReports@ShopperReports.com">
                         <versionRange  minVersion="3.1.22.0" maxVersion="3.1.22.0">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i44" id="sigma@labs.mozilla">
                         </emItem>
+      <emItem  blockID="i246" id="support@vide1flash2.com">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i48" id="admin@youtubespeedup.com">
                         </emItem>
       <emItem  blockID="i109" id="{392e123b-b691-4a5e-b52f-c4c1027e749c}">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i79" id="GifBlock@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*">
@@ -583,31 +587,31 @@
       <pluginItem  blockID="p178">
                   <match name="filename" exp="(NPSWF[0-9_]*\.dll)|(Flash\ Player\.plugin)" />                                    <versionRange  minVersion="11.0" maxVersion="11.4.402.286.999" severity="0" vulnerabilitystatus="1">
                                 <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
                               <versionRange  minVersion="19.0a1" maxVersion="*" />
                           </targetApplication>
                   </versionRange>
                   </pluginItem>
       <pluginItem  blockID="p180">
-                  <match name="filename" exp="JavaAppletPlugin\.plugin" />                                    <versionRange  minVersion="Java 7 Update 0" maxVersion="Java 7 Update 10" severity="0" vulnerabilitystatus="2">
+                  <match name="filename" exp="JavaAppletPlugin\.plugin" />                                    <versionRange  minVersion="Java 7 Update 0" maxVersion="Java 7 Update 11" severity="0" vulnerabilitystatus="2">
                                 <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
                               <versionRange  minVersion="17.0" maxVersion="*" />
                           </targetApplication>
                   </versionRange>
                   </pluginItem>
       <pluginItem  blockID="p182">
-      <match name="name" exp="Java\(TM\) Platform SE 7 U([0-9]|10)(\s[^\d\._U]|$)" />            <match name="filename" exp="npjp2\.dll" />                                    <versionRange  severity="0" vulnerabilitystatus="2">
+      <match name="name" exp="Java\(TM\) Platform SE 7 U([0-9]|(1[0-1]))(\s[^\d\._U]|$)" />            <match name="filename" exp="npjp2\.dll" />                                    <versionRange  severity="0" vulnerabilitystatus="2">
                                 <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
                               <versionRange  minVersion="17.0" maxVersion="*" />
                           </targetApplication>
                   </versionRange>
                   </pluginItem>
       <pluginItem  blockID="p184">
-      <match name="name" exp="Java\(TM\) Plug-in 1\.7\.0(_0?([0-9]|10)?)?([^\d\._]|$)" />            <match name="filename" exp="libnpjp2\.so" />                                    <versionRange  severity="0" vulnerabilitystatus="2">
+      <match name="name" exp="Java\(TM\) Plug-in 1\.7\.0(_0?([0-9]|(1[0-1]))?)?([^\d\._]|$)" />            <match name="filename" exp="libnpjp2\.so" />                                    <versionRange  severity="0" vulnerabilitystatus="2">
                                 <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
                               <versionRange  minVersion="17.0" maxVersion="*" />
                           </targetApplication>
                   </versionRange>
                   </pluginItem>
       <pluginItem  blockID="p186">
       <match name="name" exp="Java\(TM\) Platform SE 6 U3[1-8](\s[^\d\._U]|$)" />            <match name="filename" exp="npjp2\.dll" />                                    <versionRange  severity="0" vulnerabilitystatus="2">
                                 <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
@@ -655,16 +659,32 @@
                   </pluginItem>
       <pluginItem  os="Darwin" blockID="p242">
             <match name="description" exp="Flip4Mac" />                                          <versionRange  minVersion="0" maxVersion="2.4.3.999" severity="1">
                                 <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
                               <versionRange  minVersion="18.0a1" maxVersion="*" />
                           </targetApplication>
                   </versionRange>
                   </pluginItem>
+      <pluginItem  blockID="p248">
+                  <match name="filename" exp="Scorch\.plugin" />                      <versionRange  minVersion="0" maxVersion="6.2.0" severity="1"></versionRange>
+                  </pluginItem>
+      <pluginItem  blockID="p250">
+                  <match name="filename" exp="npFoxitReaderPlugin\.dll" />                      <versionRange  minVersion="0" maxVersion="2.2.1.530" severity="0" vulnerabilitystatus="2"></versionRange>
+                  </pluginItem>
+      <pluginItem  os="Darwin" blockID="p252">
+                  <match name="filename" exp="AdobePDFViewerNPAPI\.plugin" />                      <versionRange  minVersion="11.0.0" maxVersion="11.0.01" severity="1"></versionRange>
+                  </pluginItem>
+      <pluginItem  blockID="p254">
+                  <match name="filename" exp="PDF Browser Plugin\.plugin" />                                    <versionRange  minVersion="0" maxVersion="2.4.2" severity="1">
+                                <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+                              <versionRange  minVersion="18.0a1" maxVersion="*" />
+                          </targetApplication>
+                  </versionRange>
+                  </pluginItem>
     </pluginItems>
 
   <gfxItems>
     <gfxBlacklistEntry  blockID="g35">      <os>WINNT 6.1</os>      <vendor>0x10de</vendor>              <devices>
                       <device>0x0a6c</device>
                   </devices>
             <feature>DIRECT2D</feature>      <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>      <driverVersion>8.17.12.5896</driverVersion>      <driverVersionComparator>LESS_THAN_OR_EQUAL</driverVersionComparator>    </gfxBlacklistEntry>
     <gfxBlacklistEntry  blockID="g36">      <os>WINNT 6.1</os>      <vendor>0x10de</vendor>              <devices>
--- a/services/sync/modules/engines/history.js
+++ b/services/sync/modules/engines/history.js
@@ -321,24 +321,16 @@ HistoryStore.prototype = {
     PlacesUtils.history.removePage(uri);
     this._log.trace("Removed page: " + [record.id, page.url, page.title]);
   },
 
   itemExists: function HistStore_itemExists(id) {
     return !!this._findURLByGUID(id);
   },
 
-  urlExists: function HistStore_urlExists(url) {
-    if (typeof(url) == "string") {
-      url = Utils.makeURI(url);
-    }
-    // Don't call isVisited on a null URL to work around crasher bug 492442.
-    return url ? PlacesUtils.history.isVisited(url) : false;
-  },
-
   createRecord: function createRecord(id, collection) {
     let foo = this._findURLByGUID(id);
     let record = new HistoryRec(collection, id);
     if (foo) {
       record.histUri = foo.url;
       record.title = foo.title;
       record.sortindex = foo.frecency;
       record.visits = this._getVisits(record.histUri);
--- a/services/sync/tests/unit/test_corrupt_keys.js
+++ b/services/sync/tests/unit/test_corrupt_keys.js
@@ -6,18 +6,19 @@ Cu.import("resource://services-sync/cons
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/tabs.js");
 Cu.import("resource://services-sync/engines/history.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/status.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
+Cu.import("resource://gre/modules/commonjs/promise/core.js");
 
-add_test(function test_locally_changed_keys() {
+add_task(function test_locally_changed_keys() {
   let passphrase = "abcdeabcdeabcdeabcdeabcdea";
 
   let hmacErrorCount = 0;
   function counting(f) {
     return function() {
       hmacErrorCount++;
       return f.call(this);
     };
@@ -140,21 +141,21 @@ add_test(function test_locally_changed_k
     _("HMAC error count: " + hmacErrorCount);
     // Now syncing should succeed, after one HMAC error.
     Service.sync();
     do_check_eq(hmacErrorCount, 1);
     _("Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair);
 
     // And look! We downloaded history!
     let store = Service.engineManager.get("history")._store;
-    do_check_true(store.urlExists("http://foo/bar?record-no--0"));
-    do_check_true(store.urlExists("http://foo/bar?record-no--1"));
-    do_check_true(store.urlExists("http://foo/bar?record-no--2"));
-    do_check_true(store.urlExists("http://foo/bar?record-no--3"));
-    do_check_true(store.urlExists("http://foo/bar?record-no--4"));
+    do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--0"));
+    do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--1"));
+    do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--2"));
+    do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--3"));
+    do_check_true(yield promiseIsURIVisited("http://foo/bar?record-no--4"));
     do_check_eq(hmacErrorCount, 1);
 
     _("Busting some new server values.");
     // Now what happens if we corrupt the HMAC on the server?
     for (let i = 5; i < 10; i++) {
       let id = 'record-no--' + i;
       let modified = 1 + (Date.now() / 1000);
 
@@ -183,26 +184,43 @@ add_test(function test_locally_changed_k
     Service.lastHMACEvent = 0;
 
     _("Syncing...");
     Service.sync();
     _("Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair);
     _("Server keys have been updated, and we skipped over 5 more HMAC errors without adjusting history.");
     do_check_true(johndoe.modified("crypto") > old_key_time);
     do_check_eq(hmacErrorCount, 6);
-    do_check_false(store.urlExists("http://foo/bar?record-no--5"));
-    do_check_false(store.urlExists("http://foo/bar?record-no--6"));
-    do_check_false(store.urlExists("http://foo/bar?record-no--7"));
-    do_check_false(store.urlExists("http://foo/bar?record-no--8"));
-    do_check_false(store.urlExists("http://foo/bar?record-no--9"));
-
+    do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--5"));
+    do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--6"));
+    do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--7"));
+    do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--8"));
+    do_check_false(yield promiseIsURIVisited("http://foo/bar?record-no--9"));
   } finally {
     Svc.Prefs.resetBranch("");
-    server.stop(run_next_test);
+    let deferred = Promise.defer();
+    server.stop(deferred.resolve);
+    yield deferred.promise;
   }
 });
 
 function run_test() {
   let logger = Log4Moz.repository.rootLogger;
   Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
 
   run_next_test();
 }
+
+/**
+ * Asynchronously check a url is visited.
+ * @param url the url
+ * @return {Promise}
+ * @resolves When the check has been added successfully.
+ * @rejects JavaScript exception.
+ */
+function promiseIsURIVisited(url) {
+  let deferred = Promise.defer();
+  PlacesUtils.asyncHistory.isURIVisited(Utils.makeURI(url), function(aURI, aIsVisited) {
+    deferred.resolve(aIsVisited);
+  });
+
+  return deferred.promise;
+}
--- a/toolkit/modules/Sqlite.jsm
+++ b/toolkit/modules/Sqlite.jsm
@@ -142,48 +142,57 @@ function openConnection(options) {
  * @param basename
  *        (string) The basename of this database name. Used for logging.
  * @param number
  *        (Number) The connection number to this database.
  */
 function OpenedConnection(connection, basename, number) {
   let log = Log4Moz.repository.getLogger("Sqlite.Connection." + basename);
 
+  // getLogger() returns a shared object. We can't modify the functions on this
+  // object since they would have effect on all instances and last write would
+  // win. So, we create a "proxy" object with our custom functions. Everything
+  // else is proxied back to the shared logger instance via prototype
+  // inheritance.
+  let logProxy = {__proto__: log};
+
   // Automatically prefix all log messages with the identifier.
   for (let level in Log4Moz.Level) {
     if (level == "Desc") {
       continue;
     }
 
     let lc = level.toLowerCase();
-    log[lc] = function (msg) {
-      return Log4Moz.Logger.prototype[lc].call(log, "Conn #" + number + ": " + msg);
-    }
+    logProxy[lc] = function (msg) {
+      return log[lc].call(log, "Conn #" + number + ": " + msg);
+    };
   }
 
-  this._log = log;
+  this._log = logProxy;
 
   this._log.info("Opened");
 
   this._connection = connection;
   this._open = true;
 
   this._cachedStatements = new Map();
   this._anonymousStatements = new Map();
   this._anonymousCounter = 0;
   this._inProgressStatements = new Map();
   this._inProgressCounter = 0;
 
   this._inProgressTransaction = null;
 }
 
 OpenedConnection.prototype = Object.freeze({
-  TRANSACTION_DEFERRED: Ci.mozIStorageConnection.TRANSACTION_DEFERRED,
-  TRANSACTION_IMMEDIATE: Ci.mozIStorageConnection.TRANSACTION_IMMEDIATE,
-  TRANSACTION_EXCLUSIVE: Ci.mozIStorageConnection.TRANSACTION_EXCLUSIVE,
+  TRANSACTION_DEFERRED: "DEFERRED",
+  TRANSACTION_IMMEDIATE: "IMMEDIATE",
+  TRANSACTION_EXCLUSIVE: "EXCLUSIVE",
+
+  TRANSACTION_TYPES: ["DEFERRED", "IMMEDIATE", "EXCLUSIVE"],
 
   get connectionReady() {
     return this._open && this._connection.connectionReady;
   },
 
   /**
    * The row ID from the last INSERT operation.
    *
@@ -244,31 +253,45 @@ OpenedConnection.prototype = Object.free
    *
    * @return Promise<>
    */
   close: function () {
     if (!this._connection) {
       return Promise.resolve();
     }
 
-    this._log.debug("Closing.");
+    this._log.debug("Request to close connection.");
+
+    let deferred = Promise.defer();
 
-    // Abort in-progress transaction.
-    if (this._inProgressTransaction) {
-      this._log.warn("Transaction in progress at time of close.");
-      try {
-        this._connection.rollbackTransaction();
-      } catch (ex) {
-        this._log.warn("Error rolling back transaction: " +
-                       CommonUtils.exceptionStr(ex));
-      }
-      this._inProgressTransaction.reject(new Error("Connection being closed."));
-      this._inProgressTransaction = null;
+    // We need to take extra care with transactions during shutdown.
+    //
+    // If we don't have a transaction in progress, we can proceed with shutdown
+    // immediately.
+    if (!this._inProgressTransaction) {
+      this._finalize(deferred);
+      return deferred.promise;
     }
 
+    // Else if we do have a transaction in progress, we forcefully roll it
+    // back. This is an async task, so we wait on it to finish before
+    // performing finalization.
+    this._log.warn("Transaction in progress at time of close. Rolling back.");
+
+    let onRollback = this._finalize.bind(this, deferred);
+
+    this.execute("ROLLBACK TRANSACTION").then(onRollback, onRollback);
+    this._inProgressTransaction.reject(new Error("Connection being closed."));
+    this._inProgressTransaction = null;
+
+    return deferred.promise;
+  },
+
+  _finalize: function (deferred) {
+    this._log.debug("Finalizing connection.");
     // Cancel any in-progress statements.
     for (let [k, statement] of this._inProgressStatements) {
       statement.cancel();
     }
     this._inProgressStatements.clear();
 
     // Next we finalize all active statements.
     for (let [k, statement] of this._anonymousStatements) {
@@ -280,28 +303,24 @@ OpenedConnection.prototype = Object.free
       statement.finalize();
     }
     this._cachedStatements.clear();
 
     // This guards against operations performed between the call to this
     // function and asyncClose() finishing. See also bug 726990.
     this._open = false;
 
-    let deferred = Promise.defer();
-
     this._log.debug("Calling asyncClose().");
     this._connection.asyncClose({
       complete: function () {
         this._log.info("Closed");
         this._connection = null;
         deferred.resolve();
       }.bind(this),
     });
-
-    return deferred.promise;
   },
 
   /**
    * Execute a SQL statement and cache the underlying statement object.
    *
    * This function executes a SQL statement and also caches the underlying
    * derived statement object so subsequent executions are faster and use
    * less resources.
@@ -418,17 +437,17 @@ OpenedConnection.prototype = Object.free
 
     return deferred.promise;
   },
 
   /**
    * Whether a transaction is currently in progress.
    */
   get transactionInProgress() {
-    return this._open && this._connection.transactionInProgress;
+    return this._open && !!this._inProgressTransaction;
   },
 
   /**
    * Perform a transaction.
    *
    * A transaction is specified by a user-supplied function that is a
    * generator function which can be used by Task.jsm's Task.spawn(). The
    * function receives this connection instance as its argument.
@@ -445,44 +464,86 @@ OpenedConnection.prototype = Object.free
    * the transaction is rolled back, the promise is rejected.
    *
    * @param func
    *        (function) What to perform as part of the transaction.
    * @param type optional
    *        One of the TRANSACTION_* constants attached to this type.
    */
   executeTransaction: function (func, type=this.TRANSACTION_DEFERRED) {
+    if (this.TRANSACTION_TYPES.indexOf(type) == -1) {
+      throw new Error("Unknown transaction type: " + type);
+    }
+
     this._ensureOpen();
 
-    if (this.transactionInProgress) {
+    if (this._inProgressTransaction) {
       throw new Error("A transaction is already active. Only one transaction " +
                       "can be active at a time.");
     }
 
     this._log.debug("Beginning transaction");
-    this._connection.beginTransactionAs(type);
-
     let deferred = Promise.defer();
     this._inProgressTransaction = deferred;
+    Task.spawn(function doTransaction() {
+      // It's tempting to not yield here and rely on the implicit serial
+      // execution of issued statements. However, the yield serves an important
+      // purpose: catching errors in statement execution.
+      yield this.execute("BEGIN " + type + " TRANSACTION");
 
-    Task.spawn(func(this)).then(
-      function onSuccess (result) {
-        this._connection.commitTransaction();
+      let result;
+      try {
+        result = yield Task.spawn(func(this));
+      } catch (ex) {
+        // It's possible that a request to close the connection caused the
+        // error.
+        // Assertion: close() will unset this._inProgressTransaction when
+        // called.
+        if (!this._inProgressTransaction) {
+          this._log.warn("Connection was closed while performing transaction. " +
+                         "Received error should be due to closed connection: " +
+                         CommonUtils.exceptionStr(ex));
+          throw ex;
+        }
+
+        this._log.warn("Error during transaction. Rolling back: " +
+                       CommonUtils.exceptionStr(ex));
+        try {
+          yield this.execute("ROLLBACK TRANSACTION");
+        } catch (inner) {
+          this._log.warn("Could not roll back transaction. This is weird: " +
+                         CommonUtils.exceptionStr(inner));
+        }
+
+        throw ex;
+      }
+
+      // See comment above about connection being closed during transaction.
+      if (!this._inProgressTransaction) {
+        this._log.warn("Connection was closed while performing transaction. " +
+                       "Unable to commit.");
+        throw new Error("Connection closed before transaction committed.");
+      }
+
+      try {
+        yield this.execute("COMMIT TRANSACTION");
+      } catch (ex) {
+        this._log.warn("Error committing transaction: " +
+                       CommonUtils.exceptionStr(ex));
+        throw ex;
+      }
+
+      throw new Task.Result(result);
+    }.bind(this)).then(
+      function onSuccess(result) {
         this._inProgressTransaction = null;
-        this._log.debug("Transaction committed.");
-
         deferred.resolve(result);
       }.bind(this),
-
-      function onError (error) {
-        this._log.warn("Error during transaction. Rolling back: " +
-                       CommonUtils.exceptionStr(error));
-        this._connection.rollbackTransaction();
+      function onError(error) {
         this._inProgressTransaction = null;
-
         deferred.reject(error);
       }.bind(this)
     );
 
     return deferred.promise;
   },
 
   /**
--- a/toolkit/modules/tests/xpcshell/test_sqlite.js
+++ b/toolkit/modules/tests/xpcshell/test_sqlite.js
@@ -208,16 +208,32 @@ add_task(function test_on_row_stop_itera
   });
 
   do_check_null(result);
   do_check_eq(i, 5);
 
   yield c.close();
 });
 
+add_task(function test_invalid_transaction_type() {
+  let c = yield getDummyDatabase("invalid_transaction_type");
+
+  let errored = false;
+  try {
+    c.executeTransaction(function () {}, "foobar");
+  } catch (ex) {
+    errored = true;
+    do_check_true(ex.message.startsWith("Unknown transaction type"));
+  } finally {
+    do_check_true(errored);
+  }
+
+  yield c.close();
+});
+
 add_task(function test_execute_transaction_success() {
   let c = yield getDummyDatabase("execute_transaction_success");
 
   do_check_false(c.transactionInProgress);
 
   yield c.executeTransaction(function transaction(conn) {
     do_check_eq(c, conn);
     do_check_true(conn.transactionInProgress);
@@ -252,8 +268,58 @@ add_task(function test_execute_transacti
   yield deferred.promise;
 
   let rows = yield c.execute("SELECT * FROM dirs");
   do_check_eq(rows.length, 0);
 
   yield c.close();
 });
 
+add_task(function test_close_during_transaction() {
+  let c = yield getDummyDatabase("close_during_transaction");
+
+  yield c.execute("INSERT INTO dirs (path) VALUES ('foo')");
+
+  let errored = false;
+  try {
+    yield c.executeTransaction(function transaction(conn) {
+      yield c.execute("INSERT INTO dirs (path) VALUES ('bar')");
+      yield c.close();
+    });
+  } catch (ex) {
+    errored = true;
+    do_check_eq(ex.message, "Connection being closed.");
+  } finally {
+    do_check_true(errored);
+  }
+
+  let c2 = yield getConnection("close_during_transaction");
+  let rows = yield c2.execute("SELECT * FROM dirs");
+  do_check_eq(rows.length, 1);
+
+  yield c2.close();
+});
+
+add_task(function test_detect_multiple_transactions() {
+  let c = yield getDummyDatabase("detect_multiple_transactions");
+
+  yield c.executeTransaction(function main() {
+    yield c.execute("INSERT INTO dirs (path) VALUES ('foo')");
+
+    let errored = false;
+    try {
+      yield c.executeTransaction(function child() {
+        yield c.execute("INSERT INTO dirs (path) VALUES ('bar')");
+      });
+    } catch (ex) {
+      errored = true;
+      do_check_true(ex.message.startsWith("A transaction is already active."));
+    } finally {
+      do_check_true(errored);
+    }
+  });
+
+  let rows = yield c.execute("SELECT * FROM dirs");
+  do_check_eq(rows.length, 1);
+
+  yield c.close();
+});
+