Bug 740218 - Support transactions in sqlitebridge and use them. r=gcp,rnewman,lucasr
☠☠ backed out by c347f2c72963 ☠ ☠
authorWes Johnston <wjohnston@mozilla.com>
Mon, 09 Apr 2012 10:08:37 -0700
changeset 94566 f5d6cc2e47260ec824438c5a2caae64cd9f5f62e
parent 94565 9a736b04b47fb62fdf549ce697f60d8d65c49a3d
child 94567 c347f2c7296346c6041ff31c3023fe61c1fc1f2c
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgcp, rnewman, lucasr
bugs740218
milestone14.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 740218 - Support transactions in sqlitebridge and use them. r=gcp,rnewman,lucasr
gfx/layers/Layers.cpp
gfx/layers/Layers.h
gfx/thebes/gfxASurface.cpp
gfx/thebes/gfxASurface.h
gfx/thebes/gfxUtils.cpp
gfx/thebes/gfxUtils.h
layout/base/nsLayoutDebugger.cpp
layout/base/nsLayoutUtils.cpp
layout/generic/nsFrame.cpp
mobile/android/base/db/FormHistoryProvider.java.in
mobile/android/base/db/GeckoProvider.java.in
mobile/android/base/db/PasswordsProvider.java.in
mobile/android/base/sqlite/SQLiteBridge.java
mobile/android/base/sqlite/SQLiteBridgeException.java
mozglue/android/SQLiteBridge.cpp
mozglue/android/SQLiteBridge.h
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -36,16 +36,19 @@
  * 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 "mozilla/layers/PLayers.h"
 #include "mozilla/layers/ShadowLayers.h"
 
+#define MOZ_DUMP_PAINTING 1
+#define MOZ_LAYERS_HAVE_LOG 1
+
 #include "ImageLayers.h"
 #include "Layers.h"
 #include "gfxPlatform.h"
 #include "ReadbackLayer.h"
 #include "gfxUtils.h"
 #include "nsPrintfCString.h"
 #include "mozilla/Util.h"
 #include "LayerSorter.h"
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -49,16 +49,18 @@
 #include "gfxColor.h"
 #include "gfxPattern.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 
 #include "mozilla/gfx/2D.h"
 #include "mozilla/TimeStamp.h"
 
+#define MOZ_DUMP_PAINTING 1
+
 #if defined(DEBUG) || defined(PR_LOGGING)
 #  include <stdio.h>            // FILE
 #  include "prlog.h"
 #  define MOZ_LAYERS_HAVE_LOG
 #  define MOZ_LAYERS_LOG(_args)                             \
   PR_LOG(LayerManager::GetLog(), PR_LOG_DEBUG, _args)
 #else
 struct PRLogModuleInfo;
--- a/gfx/thebes/gfxASurface.cpp
+++ b/gfx/thebes/gfxASurface.cpp
@@ -66,16 +66,18 @@
 
 #if defined(CAIRO_HAS_QT_SURFACE) && defined(MOZ_WIDGET_QT)
 #include "gfxQPainterSurface.h"
 #endif
 
 #include <stdio.h>
 #include <limits.h>
 
+#define MOZ_DUMP_PAINTING 1
+
 #include "imgIEncoder.h"
 #include "nsComponentManagerUtils.h"
 #include "prmem.h"
 #include "nsISupportsUtils.h"
 #include "plbase64.h"
 #include "nsCOMPtr.h"
 #include "nsIConsoleService.h"
 #include "nsServiceManagerUtils.h"
--- a/gfx/thebes/gfxASurface.h
+++ b/gfx/thebes/gfxASurface.h
@@ -39,16 +39,17 @@
 #define GFX_ASURFACE_H
 
 #include "gfxTypes.h"
 #include "gfxRect.h"
 #include "nsAutoPtr.h"
 #include "nsAutoRef.h"
 #include "nsThreadUtils.h"
 
+#define MOZ_DUMP_PAINTING 1
 
 typedef struct _cairo_surface cairo_surface_t;
 typedef struct _cairo_user_data_key cairo_user_data_key_t;
 
 typedef void (*thebes_destroy_func_t) (void *data);
 
 class gfxImageSurface;
 struct nsIntPoint;
--- a/gfx/thebes/gfxUtils.cpp
+++ b/gfx/thebes/gfxUtils.cpp
@@ -42,16 +42,18 @@
 #include "nsRegion.h"
 #include "yuv_convert.h"
 #include "ycbcr_to_rgb565.h"
 
 #ifdef XP_WIN
 #include "gfxWindowsPlatform.h"
 #endif
 
+#define MOZ_DUMP_PAINTING 1
+
 using namespace mozilla;
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
 
 static PRUint8 sUnpremultiplyTable[256*256];
 static PRUint8 sPremultiplyTable[256*256];
 static bool sTablesInitialized = false;
 
--- a/gfx/thebes/gfxUtils.h
+++ b/gfx/thebes/gfxUtils.h
@@ -39,16 +39,18 @@
 #define GFX_UTILS_H
 
 #include "gfxTypes.h"
 #include "gfxPattern.h"
 #include "gfxImageSurface.h"
 #include "ImageLayers.h"
 #include "mozilla/gfx/2D.h"
 
+#define MOZ_DUMP_PAINTING 1
+
 class gfxDrawable;
 class nsIntRegion;
 struct nsIntRect;
 
 class THEBES_API gfxUtils {
 public:
     /*
      * Premultiply or Unpremultiply aSourceSurface, writing the result
--- a/layout/base/nsLayoutDebugger.cpp
+++ b/layout/base/nsLayoutDebugger.cpp
@@ -222,16 +222,17 @@ PrintDisplayListTo(nsDisplayListBuilder*
     fputc('\n', aOutput);
     if (list) {
       PrintDisplayListTo(aBuilder, *list, aOutput);
     }
     fprintf(aOutput, "</li>");
   }
   
   fprintf(aOutput, "</ul>");
+  printf_stderr("DL: %s", aOutput);
 }
 
 void
 nsFrame::PrintDisplayList(nsDisplayListBuilder* aBuilder,
                           const nsDisplayList& aList,
                           FILE* aFile)
 {
   PrintDisplayListTo(aBuilder, aList, aFile);
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -107,16 +107,18 @@
 
 #include "nsSVGUtils.h"
 #include "nsSVGIntegrationUtils.h"
 #include "nsSVGForeignObjectFrame.h"
 #include "nsSVGOuterSVGFrame.h"
 
 #include "mozilla/Preferences.h"
 
+#define MOZ_DUMP_PAINTING 1
+
 #ifdef MOZ_XUL
 #include "nsXULPopupManager.h"
 #endif
 
 #include "sampler.h"
 
 using namespace mozilla;
 using namespace mozilla::layers;
@@ -1342,33 +1344,34 @@ nsLayoutUtils::CombineBreakType(PRUint8 
     }
   }
   return breakType;
 }
 
 #ifdef MOZ_DUMP_PAINTING
 #include <stdio.h>
 
-static bool gDumpEventList = false;
+static bool gDumpEventList = true;
 int gPaintCount = 0;
 #endif
 
 nsresult
 nsLayoutUtils::GetRemoteContentIds(nsIFrame* aFrame,
                                    const nsRect& aTarget,
                                    nsTArray<ViewID> &aOutIDs,
                                    bool aIgnoreRootScrollFrame)
 {
   nsDisplayListBuilder builder(aFrame, nsDisplayListBuilder::EVENT_DELIVERY,
                                false);
   nsDisplayList list;
 
+  nsIFrame* rootScrollFrame =
+    aFrame->PresContext()->PresShell()->GetRootScrollFrame();
+
   if (aIgnoreRootScrollFrame) {
-    nsIFrame* rootScrollFrame =
-      aFrame->PresContext()->PresShell()->GetRootScrollFrame();
     if (rootScrollFrame) {
       builder.SetIgnoreScrollFrame(rootScrollFrame);
     }
   }
 
   builder.EnterPresShell(aFrame, aTarget);
 
   nsresult rv =
@@ -1408,36 +1411,50 @@ nsLayoutUtils::GetFramesForArea(nsIFrame
 		                       false);
   nsDisplayList list;
   nsRect target(aRect);
 
   if (aShouldIgnoreSuppression) {
     builder.IgnorePaintSuppression();
   }
 
+  nsIFrame* rootScrollFrame =
+    aFrame->PresContext()->PresShell()->GetRootScrollFrame();
   if (aIgnoreRootScrollFrame) {
-    nsIFrame* rootScrollFrame =
-      aFrame->PresContext()->PresShell()->GetRootScrollFrame();
     if (rootScrollFrame) {
       builder.SetIgnoreScrollFrame(rootScrollFrame);
     }
   }
 
+  nsRect displayport;
+  if (rootScrollFrame) {
+    nsIContent* content = rootScrollFrame->GetContent();
+    bool usingDisplayPort = GetDisplayPort(content, &displayport);
+    if (usingDisplayPort) {
+      //printf_stderr("       xxx Setting display port %i%,%i,%i,%i",
+      //              displayport.x, displayport.y, displayport.width, displayport.height);
+      builder.SetDisplayPort(displayport);
+    }
+  }
+
   builder.EnterPresShell(aFrame, target);
 
   nsresult rv =
     aFrame->BuildDisplayListForStackingContext(&builder, target, &list);
 
   builder.LeavePresShell(aFrame, target);
   NS_ENSURE_SUCCESS(rv, rv);
 
 #ifdef MOZ_DUMP_PAINTING
   if (gDumpEventList) {
-    fprintf(stdout, "Event handling --- (%d,%d):\n", aRect.x, aRect.y);
-    nsFrame::PrintDisplayList(&builder, list);
+    printf_stderr("Event handling --- (%d,%d):\n", aRect.x, aRect.y);
+    FILE *handle;
+    handle = fopen("/sdcard/test.txt","rw");
+    nsFrame::PrintDisplayList(&builder, list, handle);
+    fclose(handle);
   }
 #endif
 
   nsDisplayItem::HitTestState hitTestState;
   list.HitTest(&builder, target, &hitTestState, &aOutFrames);
   list.DeleteAll();
   return NS_OK;
 }
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2015,18 +2015,21 @@ nsIFrame::BuildDisplayListForChild(nsDis
 
     // We can stop if child's frame subtree's intersection with the
     // dirty area is empty.
     // If the child is a scrollframe that we want to ignore, then we need
     // to descend into it because its scrolled child may intersect the dirty
     // area even if the scrollframe itself doesn't.
     if (child != aBuilder->GetIgnoreScrollFrame()) {
       nsRect childDirty;
-      if (!childDirty.IntersectRect(dirty, child->GetVisualOverflowRect()))
+      nsRect overflow = child->GetVisualOverflowRect();
+
+      if (!childDirty.IntersectRect(dirty, overflow)) {
         return NS_OK;
+      }
       // Usually we could set dirty to childDirty now but there's no
       // benefit, and it can be confusing. It can especially confuse
       // situations where we're going to ignore a scrollframe's clipping;
       // we wouldn't want to clip the dirty area to the scrollframe's
       // bounds in that case.
     }
   }
 
--- a/mobile/android/base/db/FormHistoryProvider.java.in
+++ b/mobile/android/base/db/FormHistoryProvider.java.in
@@ -147,27 +147,24 @@ public class FormHistoryProvider extends
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FormHistory:Init", null));
     }
 
     @Override
     public void onPreInsert(ContentValues values, Uri uri, SQLiteBridge db) {
         if (!values.containsKey(FormHistory.GUID)) {
             return;
         }
+
         String guid = values.getAsString(FormHistory.GUID);
-        try {
-            if (guid == null) {
-                db.delete(TABLE_DELETED_FORM_HISTORY, WHERE_GUID_IS_NULL, null);
-                return;
-            }
-            String[] args = new String[] { guid };
-            db.delete(TABLE_DELETED_FORM_HISTORY, WHERE_GUID_IS_VALUE, args);
-        } catch(SQLiteBridgeException ex) {
-            Log.w(getLogTag(), "Error removing entry with GUID " + guid, ex);
+        if (guid == null) {
+            db.delete(TABLE_DELETED_FORM_HISTORY, WHERE_GUID_IS_NULL, null);
+            return;
         }
+        String[] args = new String[] { guid };
+        db.delete(TABLE_DELETED_FORM_HISTORY, WHERE_GUID_IS_VALUE, args);
      }
 
     @Override
     public void onPreUpdate(ContentValues values, Uri uri, SQLiteBridge db) { }
 
     @Override
     public void onPostQuery(Cursor cursor, Uri uri, SQLiteBridge db) { }
 }
--- a/mobile/android/base/db/GeckoProvider.java.in
+++ b/mobile/android/base/db/GeckoProvider.java.in
@@ -5,16 +5,18 @@
 #filter substitution
 package @ANDROID_PACKAGE_NAME@.db;
 
 import java.io.File;
 import java.io.IOException;
 import java.lang.IllegalArgumentException;
 import java.util.HashMap;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
 import java.util.Random;
 
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoEventListener;
 import org.mozilla.gecko.db.BrowserContract.CommonColumns;
@@ -54,16 +56,40 @@ import android.util.Log;
 
 public abstract class GeckoProvider extends ContentProvider {
     private String mLogTag = "GeckoPasswordsProvider";
     private String mDBName = "";
     private int mDBVersion = 0;
     private HashMap<String, SQLiteBridge> mDatabasePerProfile;
     protected Context mContext = null;
 
+    @Override
+    public void shutdown() {
+        if (mDatabasePerProfile == null)
+          return;
+
+        Collection<SQLiteBridge> bridges = mDatabasePerProfile.values();
+        Iterator<SQLiteBridge> it = bridges.iterator();
+
+        while (it.hasNext()) {
+            SQLiteBridge bridge = it.next();
+            if (bridge != null) {
+                try {
+                    bridge.close();
+                } catch (Exception ex) { }
+            }
+        }
+
+        mDatabasePerProfile = null;
+    }
+
+    public void finalize() {
+        shutdown();
+    }
+
     protected void setLogTag(String aLogTag) {
         mLogTag = aLogTag;
     }
 
     protected String getLogTag() {
         return mLogTag;
     }
 
@@ -86,21 +112,24 @@ public abstract class GeckoProvider exte
     private SQLiteBridge getDB(Context context, final String databasePath) {
         SQLiteBridge bridge = null;
 
         boolean dbNeedsSetup = true;
         try {
             String resourcePath = context.getPackageResourcePath();
             GeckoAppShell.loadSQLiteLibs(context, resourcePath);
             GeckoAppShell.loadNSSLibs(context, resourcePath);
-            bridge = new SQLiteBridge(databasePath);
+            bridge = SQLiteBridge.openDatabase(databasePath, null, 0);
             int version = bridge.getVersion();
-            Log.i(mLogTag, version + " == " + mDBVersion);
             dbNeedsSetup = version != mDBVersion;
-        } catch(SQLiteBridgeException ex) {
+        } catch (SQLiteBridgeException ex) {
+            // close the database
+            if (bridge != null)
+                bridge.close();
+
             // this will throw if the database can't be found
             // we should attempt to set it up if Gecko is running
             dbNeedsSetup = true;
             Log.e(mLogTag, "Error getting version ", ex);
 
             // if Gecko is not running, we should bail out. Otherwise we try to
             // let Gecko build the database for us
             if (!GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning)) {
@@ -112,17 +141,18 @@ public abstract class GeckoProvider exte
         // If the database is not set up yet, or is the wrong schema version, we send an initialize
         // call to Gecko. Gecko will handle building the database file correctly, as well as any
         // migrations that are necessary
         if (dbNeedsSetup) {
             Log.i(mLogTag, "Sending init to gecko");
             bridge = null;
             initGecko();
         }
-        mDatabasePerProfile.put(databasePath, bridge);
+        if (bridge != null)
+            mDatabasePerProfile.put(databasePath, bridge);
 
         return bridge;
     }
 
     private SQLiteBridge getDatabaseForProfile(String profile) {
         if (TextUtils.isEmpty(profile)) {
             Log.d(mLogTag, "No profile provided, using default");
             profile = BrowserContract.DEFAULT_PROFILE;
@@ -199,16 +229,17 @@ public abstract class GeckoProvider exte
         final SQLiteBridge db = getDatabase(uri);
         if (db == null)
             return deleted;
 
         try {
             deleted = db.delete(getTable(uri), selection, selectionArgs);
         } catch (SQLiteBridgeException ex) {
             Log.e(mLogTag, "Error deleting record", ex);
+            throw ex;
         }
 
         return deleted;
     }
 
     @Override
     public Uri insert(Uri uri, ContentValues values) {
         long id = -1;
@@ -217,45 +248,94 @@ public abstract class GeckoProvider exte
         // If we can not get a SQLiteBridge instance, its likely that the database
         // has not been set up and Gecko is not running. We return null and expect
         // callers to try again later
         if (db == null)
             return null;
 
         setupDefaults(uri, values);
 
-        onPreInsert(values, uri, db);
-
+        boolean useTransaction = !db.inTransaction();
         try {
+            if (useTransaction) {
+                db.beginTransaction();
+            }
+ 
+            // onPreInsert does a check for the item in the deleted table in some cases
+            // so we put it inside this transaction
+            onPreInsert(values, uri, db);
             id = db.insert(getTable(uri), null, values);
-        } catch(SQLiteBridgeException ex) {
+
+            if (useTransaction) {
+                db.setTransactionSuccessful();
+            }
+        } catch (SQLiteBridgeException ex) {
             Log.e(mLogTag, "Error inserting in db", ex);
+            throw ex;
+        } finally {
+            if (useTransaction) {
+                db.endTransaction();
+            }
         }
 
         return ContentUris.withAppendedId(uri, id);
     }
 
     @Override
+    public int bulkInsert(Uri uri, ContentValues[] allValues) {
+        final SQLiteBridge db = getDatabase(uri);
+        // If we can not get a SQLiteBridge instance, its likely that the database
+        // has not been set up and Gecko is not running. We return 0 and expect
+        // callers to try again later
+        if (db == null)
+            return 0;
+
+        long id = -1;
+        int rowsAdded = 0;
+
+        String table = getTable(uri);
+
+        try {
+            db.beginTransaction();
+            for (ContentValues initialValues : allValues) {
+                ContentValues values = new ContentValues(initialValues);
+                setupDefaults(uri, values);
+                onPreInsert(values, uri, db);
+                id = db.insert(table, null, values);
+                rowsAdded++;
+            }
+            db.setTransactionSuccessful();
+        } catch (SQLiteBridgeException ex) {
+            Log.e(mLogTag, "Error inserting in db", ex);
+            throw ex;
+        } finally {
+            db.endTransaction();
+        }
+        return rowsAdded;
+    }
+
+    @Override
     public int update(Uri uri, ContentValues values, String selection,
             String[] selectionArgs) {
         int updated = 0;
         final SQLiteBridge db = getDatabase(uri);
 
         // If we can not get a SQLiteBridge instance, its likely that the database
         // has not been set up and Gecko is not running. We return null and expect
         // callers to try again later
         if (db == null)
             return updated;
 
         onPreUpdate(values, uri, db);
 
         try {
             updated = db.update(getTable(uri), values, selection, selectionArgs);
-        } catch(SQLiteBridgeException ex) {
+        } catch (SQLiteBridgeException ex) {
             Log.e(mLogTag, "Error updating table", ex);
+            throw ex;
         }
 
         return updated;
     }
 
     @Override
     public Cursor query(Uri uri, String[] projection, String selection,
             String[] selectionArgs, String sortOrder) {
@@ -270,16 +350,17 @@ public abstract class GeckoProvider exte
 
         sortOrder = getSortOrder(uri, sortOrder);
 
         try {
             cursor = db.query(getTable(uri), projection, selection, selectionArgs, null, null, sortOrder, null);
             onPostQuery(cursor, uri, db);
         } catch (SQLiteBridgeException ex) {
             Log.e(mLogTag, "Error querying database", ex);
+            throw ex;
         }
 
         return cursor;
     }
 
     public abstract String getTable(Uri uri);
 
     public abstract String getSortOrder(Uri uri, String aRequested);
--- a/mobile/android/base/db/PasswordsProvider.java.in
+++ b/mobile/android/base/db/PasswordsProvider.java.in
@@ -212,26 +212,22 @@ public class PasswordsProvider extends G
         }
         return result;
     }
 
     @Override
     public void onPreInsert(ContentValues values, Uri uri, SQLiteBridge db) {
         if (values.containsKey(Passwords.GUID)) {
             String guid = values.getAsString(Passwords.GUID);
-            try {
-                if (guid == null) {
-                    db.delete(TABLE_DELETED_PASSWORDS, WHERE_GUID_IS_NULL, null);
-                    return;
-                }
-                String[] args = new String[] { guid };
-                db.delete(TABLE_DELETED_PASSWORDS, WHERE_GUID_IS_VALUE, args);
-            } catch(SQLiteBridgeException ex) {
-                Log.w(getLogTag(), "Error removing entry with GUID " + guid, ex);
+            if (guid == null) {
+                db.delete(TABLE_DELETED_PASSWORDS, WHERE_GUID_IS_NULL, null);
+                return;
             }
+            String[] args = new String[] { guid };
+            db.delete(TABLE_DELETED_PASSWORDS, WHERE_GUID_IS_VALUE, args);
         }
 
         if (values.containsKey(Passwords.ENCRYPTED_PASSWORD)) {
             String res = doCrypto(values.getAsString(Passwords.ENCRYPTED_PASSWORD), uri, true);
             values.put(Passwords.ENCRYPTED_PASSWORD, res);
         }
 
         if (values.containsKey(Passwords.ENCRYPTED_USERNAME)) {
--- a/mobile/android/base/sqlite/SQLiteBridge.java
+++ b/mobile/android/base/sqlite/SQLiteBridge.java
@@ -3,49 +3,64 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.sqlite;
 
 import org.mozilla.gecko.sqlite.SQLiteBridgeException;
 import org.mozilla.gecko.sqlite.MatrixBlobCursor;
 import android.content.ContentValues;
 import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.database.DatabaseErrorHandler;
 import android.text.TextUtils;
-import android.util.Log;
 
 import java.lang.String;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import android.util.Log;
 import java.util.Map.Entry;
 import java.util.Set;
 
 /*
  * This class allows using the mozsqlite3 library included with Firefox
  * to read SQLite databases, instead of the Android SQLiteDataBase API,
  * which might use whatever outdated DB is present on the Android system.
  */
 public class SQLiteBridge {
     private static final String LOGTAG = "SQLiteBridge";
 
-    // Path to the database. We reopen it every query.
+    // Path to the database. If this database was not opened with openDatabase, we reopen it every query.
     private String mDb;
+    // pointer to the database if it was opened with openDatabase
+    protected long mDbPointer = 0;
 
     // Values remembered after a query.
     private long[] mQueryResults;
 
+    private boolean mTransactionSuccess = false;
+    private boolean mInTransaction = false;
+
     private static final int RESULT_INSERT_ROW_ID = 0;
     private static final int RESULT_ROWS_CHANGED = 1;
 
     // JNI code in $(topdir)/mozglue/android/..
     private static native MatrixBlobCursor sqliteCall(String aDb, String aQuery,
                                                       String[] aParams,
                                                       long[] aUpdateResult)
         throws SQLiteBridgeException;
+    private static native MatrixBlobCursor sqliteCallWithDb(long aDb, String aQuery,
+                                                            String[] aParams,
+                                                            long[] aUpdateResult)
+        throws SQLiteBridgeException;
+    private static native long openDatabase(String aDb)
+        throws SQLiteBridgeException;
+    private static native void closeDatabase(long aDb);
 
     // Takes the path to the database we want to access.
     public SQLiteBridge(String aDb) throws SQLiteBridgeException {
         mDb = aDb;
     }
 
     // Executes a simple line of sql.
     public void execSQL(String sql)
@@ -199,15 +214,97 @@ public class SQLiteBridge {
         return ret;
     }
 
     // Do an SQL query, substituting the parameters in the query with the passed
     // parameters. The parameters are subsituded in order, so named parameters
     // are not supported.
     private Cursor internalQuery(String aQuery, String[] aParams)
         throws SQLiteBridgeException {
+
         mQueryResults = new long[2];
+        if (isOpen()) {
+            return sqliteCallWithDb(mDbPointer, aQuery, aParams, mQueryResults);
+        }
         return sqliteCall(mDb, aQuery, aParams, mQueryResults);
     }
 
-    // nop, provided for API compatibility with SQLiteDatabase.
-    public void close() { }
+    /*
+     * The second two parameters here are just provided for compatbility with SQLiteDatabase
+     * Support for them is not currently implemented
+    */
+    public static SQLiteBridge openDatabase(String path, SQLiteDatabase.CursorFactory factory, int flags)
+        throws SQLiteException {
+        SQLiteBridge bridge = null;
+        try {
+            bridge = new SQLiteBridge(path);
+            bridge.mDbPointer = bridge.openDatabase(path);
+        } catch(SQLiteBridgeException ex) {
+            // catch and rethrow as a SQLiteException to match SQLiteDatabase
+            throw new SQLiteException(ex.getMessage());
+        }
+        return bridge;
+    }
+
+    public void close() {
+        if (isOpen()) {
+          closeDatabase(mDbPointer);
+        }
+        mDbPointer = 0;
+    }
+
+    public boolean isOpen() {
+        return mDbPointer > 0;
+    }
+
+    public void beginTransaction() throws SQLiteBridgeException {
+        if (inTransaction()) {
+            throw new SQLiteBridgeException("Nested transactions are not supported");
+        }
+        execSQL("BEGIN EXCLUSIVE");
+        mTransactionSuccess = false;
+        mInTransaction = true;
+    }
+
+    public void beginTransactionNonExclusive() throws SQLiteBridgeException {
+        if (inTransaction()) {
+            throw new SQLiteBridgeException("Nested transactions are not supported");
+        }
+        execSQL("BEGIN IMMEDIATE");
+        mTransactionSuccess = false;
+        mInTransaction = true;
+    }
+
+    public void endTransaction() {
+        if (!inTransaction())
+            return;
+
+        try {
+          if (mTransactionSuccess) {
+              execSQL("COMMIT TRANSACTION");
+          } else {
+              execSQL("ROLLBACK TRANSACTION");
+          }
+        } catch(SQLiteBridgeException ex) {
+            Log.e(LOGTAG, "Error ending transaction", ex);
+        }
+        mInTransaction = false;
+        mTransactionSuccess = false;
+    }
+
+    public void setTransactionSuccessful() throws SQLiteBridgeException {
+        if (!inTransaction()) {
+            throw new SQLiteBridgeException("setTransactionSuccessful called outside a transaction");
+        }
+        mTransactionSuccess = true;
+    }
+
+    public boolean inTransaction() {
+        return mInTransaction;
+    }
+
+    public void finalize() {
+        if (isOpen()) {
+            Log.e(LOGTAG, "Bridge finalized without closing the database");
+            close();
+        }
+    }
 }
--- a/mobile/android/base/sqlite/SQLiteBridgeException.java
+++ b/mobile/android/base/sqlite/SQLiteBridgeException.java
@@ -32,16 +32,16 @@
  * 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 ***** */
 
 package org.mozilla.gecko.sqlite;
 
-public class SQLiteBridgeException extends Exception {
+public class SQLiteBridgeException extends RuntimeException {
     static final long serialVersionUID = 1L;
 
     public SQLiteBridgeException() {}
     public SQLiteBridgeException(String msg) {
         super(msg);
     }
-}
\ No newline at end of file
+}
--- a/mozglue/android/SQLiteBridge.cpp
+++ b/mozglue/android/SQLiteBridge.cpp
@@ -163,37 +163,104 @@ extern "C" NS_EXPORT jobject JNICALL
 Java_org_mozilla_gecko_sqlite_SQLiteBridge_sqliteCall(JNIEnv* jenv, jclass,
                                                       jstring jDb,
                                                       jstring jQuery,
                                                       jobjectArray jParams,
                                                       jlongArray jQueryRes)
 {
     JNI_Setup(jenv);
 
+    int rc;
+    jobject jCursor = NULL;
+    const char* dbPath;
+    sqlite3 *db;
+    char* errorMsg;
+
+    dbPath = jenv->GetStringUTFChars(jDb, NULL);
+    rc = f_sqlite3_open(dbPath, &db);
+    jenv->ReleaseStringUTFChars(jDb, dbPath);
+    if (rc != SQLITE_OK) {
+        asprintf(&errorMsg, "Can't open database: %s\n", f_sqlite3_errmsg(db));
+        LOG("Error in SQLiteBridge: %s\n", errorMsg);
+        JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", errorMsg);
+        free(errorMsg);
+    } else {
+      jCursor = sqliteInternalCall(jenv, db, jQuery, jParams, jQueryRes);
+    }
+    f_sqlite3_close(db);
+    return jCursor;
+}
+
+extern "C" NS_EXPORT jobject JNICALL
+Java_org_mozilla_gecko_sqlite_SQLiteBridge_sqliteCallWithDb(JNIEnv* jenv, jclass,
+                                                            jlong jDb,
+                                                            jstring jQuery,
+                                                            jobjectArray jParams,
+                                                            jlongArray jQueryRes)
+{
+    JNI_Setup(jenv);
+
+    jobject jCursor = NULL;
+    sqlite3 *db = (sqlite3*)jDb;
+    jCursor = sqliteInternalCall(jenv, db, jQuery, jParams, jQueryRes);
+    return jCursor;
+}
+
+extern "C" NS_EXPORT jlong JNICALL
+Java_org_mozilla_gecko_sqlite_SQLiteBridge_openDatabase(JNIEnv* jenv, jclass,
+                                                        jstring jDb)
+{
+    JNI_Setup(jenv);
+
+    int rc;
+    const char* dbPath;
+    sqlite3 *db;
+    char* errorMsg;
+
+    dbPath = jenv->GetStringUTFChars(jDb, NULL);
+    rc = f_sqlite3_open(dbPath, &db);
+    jenv->ReleaseStringUTFChars(jDb, dbPath);
+    if (rc != SQLITE_OK) {
+        asprintf(&errorMsg, "Can't open database: %s\n", f_sqlite3_errmsg(db));
+        LOG("Error in SQLiteBridge: %s\n", errorMsg);
+        JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", errorMsg);
+        free(errorMsg);
+    }
+    return (jlong)db;
+}
+
+extern "C" NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_sqlite_SQLiteBridge_closeDatabase(JNIEnv* jenv, jclass,
+                                                        jlong jDb)
+{
+    JNI_Setup(jenv);
+
+    sqlite3 *db = (sqlite3*)jDb;
+    f_sqlite3_close(db);
+}
+
+static jobject
+sqliteInternalCall(JNIEnv* jenv,
+                   sqlite3 *db,
+                   jstring jQuery,
+                   jobjectArray jParams,
+                   jlongArray jQueryRes)
+{
+    JNI_Setup(jenv);
+
     jobject jCursor = NULL;
     char* errorMsg;
     jsize numPars = 0;
 
-    const char* queryStr;
-    queryStr = jenv->GetStringUTFChars(jQuery, NULL);
-
-    const char* dbPath;
-    dbPath = jenv->GetStringUTFChars(jDb, NULL);
-
     const char *pzTail;
     sqlite3_stmt *ppStmt;
-    sqlite3 *db;
     int rc;
-    rc = f_sqlite3_open(dbPath, &db);
-    jenv->ReleaseStringUTFChars(jDb, dbPath);
 
-    if (rc != SQLITE_OK) {
-        asprintf(&errorMsg, "Can't open database: %s\n", f_sqlite3_errmsg(db));
-        goto error_close;
-    }
+    const char* queryStr;
+    queryStr = jenv->GetStringUTFChars(jQuery, NULL);
 
     rc = f_sqlite3_prepare_v2(db, queryStr, -1, &ppStmt, &pzTail);
     if (rc != SQLITE_OK || ppStmt == NULL) {
         asprintf(&errorMsg, "Can't prepare statement: %s\n", f_sqlite3_errmsg(db));
         goto error_close;
     }
     jenv->ReleaseStringUTFChars(jQuery, queryStr);
 
@@ -348,18 +415,16 @@ Java_org_mozilla_gecko_sqlite_SQLiteBrid
     }
 
     rc = f_sqlite3_finalize(ppStmt);
     if (rc != SQLITE_OK) {
         asprintf(&errorMsg, "Can't finalize statement: %s\n", f_sqlite3_errmsg(db));
         goto error_close;
     }
 
-    f_sqlite3_close(db);
     return jCursor;
 
 error_close:
-    f_sqlite3_close(db);
     LOG("Error in SQLiteBridge: %s\n", errorMsg);
     JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", errorMsg);
     free(errorMsg);
     return jCursor;
 }
--- a/mozglue/android/SQLiteBridge.h
+++ b/mozglue/android/SQLiteBridge.h
@@ -35,16 +35,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef SQLiteBridge_h
 #define SQLiteBridge_h
 
 #include "sqlite3.h"
 
 void setup_sqlite_functions(void *sqlite_handle);
+static jobject sqliteInternalCall(JNIEnv* jenv, sqlite3 *db, jstring jQuery, jobjectArray jParams, jlongArray jQueryRes);
 
 #define SQLITE_WRAPPER(name, return_type, args...) \
 typedef return_type (*name ## _t)(args);  \
 extern name ## _t f_ ## name;
 
 SQLITE_WRAPPER(sqlite3_open, int, const char*, sqlite3**)
 SQLITE_WRAPPER(sqlite3_errmsg, const char*, sqlite3*)
 SQLITE_WRAPPER(sqlite3_prepare_v2, int, sqlite3*, const char*, int, sqlite3_stmt**, const char**)