Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 12 Feb 2014 08:39:09 -0500
changeset 168382 a523d5b5598991422510f84a10ba840668f002ed
parent 168381 74cbeb541d0319cfbbb9edd8b116241144bd4eaa (current diff)
parent 168333 3b3ac98e0dc167f98dc5e45272a13e7c2c29d0cd (diff)
child 168383 46e8d31eb342c1b30eaa451e8e9cd2a2f736f90a
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
milestone30.0a1
Merge m-c to inbound.
mobile/android/base/moz.build
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -877,8 +877,11 @@ pref("apz.pan_repaint_interval", 40);
 //
 // Using a software canvas can save memory when JS calls getImageData()
 // on the canvas frequently. See bug 884226.
 pref("gfx.canvas.willReadFrequently.enable", true);
 
 // Disable autofocus until we can have it not bring up the keyboard.
 // https://bugzilla.mozilla.org/show_bug.cgi?id=965763
 pref("browser.autofocus", false);
+
+// Enable wakelock
+pref("dom.wakelock.enabled", true);
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -7,17 +7,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="4c6b5142d3b716f1c4ea502eeb92d3119f2b01c6"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="2e546129dd7860ee3e62b3f86ab222647dfcaae4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eda08beb3ba9a159843c70ffde0f9660ec351eb9"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="87aa8679560ce09f6445621d6f370d9de722cdba"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -6,17 +6,17 @@
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="317f25e0a4cb3e8e86e2b76c37a14081372f0307">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="4c6b5142d3b716f1c4ea502eeb92d3119f2b01c6"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="2e546129dd7860ee3e62b3f86ab222647dfcaae4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="905bfa3548eb75cf1792d0d8412b92113bbd4318"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="c3d7efc45414f1b44cd9c479bb2758c91c4707c0"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -7,17 +7,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="4c6b5142d3b716f1c4ea502eeb92d3119f2b01c6"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="2e546129dd7860ee3e62b3f86ab222647dfcaae4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eda08beb3ba9a159843c70ffde0f9660ec351eb9"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="87aa8679560ce09f6445621d6f370d9de722cdba"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "01b437206d143e1632ee5a5fd49cc649aee2e970", 
+    "revision": "9da6724d2e1af0c60e5fd64c8a79c9c9c0106c9f", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -6,17 +6,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="4c6b5142d3b716f1c4ea502eeb92d3119f2b01c6"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="2e546129dd7860ee3e62b3f86ab222647dfcaae4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -5,17 +5,17 @@
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="4c6b5142d3b716f1c4ea502eeb92d3119f2b01c6"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="2e546129dd7860ee3e62b3f86ab222647dfcaae4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -7,17 +7,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="4c6b5142d3b716f1c4ea502eeb92d3119f2b01c6"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="2e546129dd7860ee3e62b3f86ab222647dfcaae4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -6,17 +6,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="4c6b5142d3b716f1c4ea502eeb92d3119f2b01c6"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="2e546129dd7860ee3e62b3f86ab222647dfcaae4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -6,17 +6,17 @@
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="317f25e0a4cb3e8e86e2b76c37a14081372f0307">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="4c6b5142d3b716f1c4ea502eeb92d3119f2b01c6"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="2e546129dd7860ee3e62b3f86ab222647dfcaae4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="905bfa3548eb75cf1792d0d8412b92113bbd4318"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="c3d7efc45414f1b44cd9c479bb2758c91c4707c0"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -6,17 +6,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="4c6b5142d3b716f1c4ea502eeb92d3119f2b01c6"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="2e546129dd7860ee3e62b3f86ab222647dfcaae4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -15,17 +15,17 @@
     <stringbundle id="bundle_preferences" src="chrome://browser/locale/preferences/preferences.properties"/>
   </stringbundleset>
 
   <commandset id="mainCommandSet">
     <command id="cmd_newNavigator"                 oncommand="OpenBrowserWindow()"/>
     <command id="cmd_handleBackspace" oncommand="BrowserHandleBackspace();" />
     <command id="cmd_handleShiftBackspace" oncommand="BrowserHandleShiftBackspace();" />
 
-    <command id="cmd_newNavigatorTab" oncommand="BrowserOpenTab();"/>
+    <command id="cmd_newNavigatorTab" oncommand="BrowserOpenNewTabOrWindow(event);"/>
     <command id="Browser:OpenFile"  oncommand="BrowserOpenFileWindow();"/>
     <command id="Browser:SavePage" oncommand="saveDocument(window.content.document);"/>
 
     <command id="Browser:SendLink"
              oncommand="MailIntegration.sendLinkForWindow(window.content);"/>
 
     <command id="cmd_pageSetup" oncommand="PrintUtils.showPageSetup();"/>
     <command id="cmd_print" oncommand="PrintUtils.print();"/>
--- a/browser/base/content/browser-tabPreviews.js
+++ b/browser/base/content/browser-tabPreviews.js
@@ -197,24 +197,19 @@ var ctrlTab = {
 
     // Rotate the list until the selected tab is first
     while (!list[0].selected)
       list.push(list.shift());
 
     list = list.filter(function (tab) !tab.closing);
 
     if (this.recentlyUsedLimit != 0) {
-      let recentlyUsedTabs = [];
-      for (let tab of this._recentlyUsedTabs) {
-        if (!tab.hidden && !tab.closing) {
-          recentlyUsedTabs.push(tab);
-          if (this.recentlyUsedLimit > 0 && recentlyUsedTabs.length >= this.recentlyUsedLimit)
-            break;
-        }
-      }
+      let recentlyUsedTabs = this._recentlyUsedTabs;
+      if (this.recentlyUsedLimit > 0)
+        recentlyUsedTabs = this._recentlyUsedTabs.slice(0, this.recentlyUsedLimit);
       for (let i = recentlyUsedTabs.length - 1; i >= 0; i--) {
         list.splice(list.indexOf(recentlyUsedTabs[i]), 1);
         list.unshift(recentlyUsedTabs[i]);
       }
     }
 
     return this._tabList = list;
   },
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7231,8 +7231,16 @@ let BrowserChromeTest = {
   },
   runWhenReady: function (cb) {
     if (this._ready)
       cb();
     else
       this._cb = cb;
   }
 };
+
+function BrowserOpenNewTabOrWindow(event) {
+  if (event.shiftKey) {
+    OpenBrowserWindow();
+  } else {
+    BrowserOpenTab();
+  }
+}
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -115,17 +115,16 @@
                        class="subviewbutton"
                        oncommand="PlacesCommandHook.showPlacesOrganizer('BookmarksToolbar'); PanelUI.hide();"/>
         <toolbarbutton id="panelMenu_unsortedBookmarks"
                        label="&unsortedBookmarksCmd.label;"
                        class="subviewbutton"
                        oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks'); PanelUI.hide();"/>
         <toolbarseparator/>
         <toolbaritem id="panelMenu_bookmarksMenu"
-                     flex="1"
                      orient="vertical"
                      smoothscroll="false"
                      onclick="if (event.button == 1) BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
                      oncommand="BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
                      flatList="true"
                      tooltip="bhTooltip">
           <!-- bookmarks menu items will go here -->
         </toolbaritem>
@@ -140,17 +139,17 @@
     <panelview id="PanelUI-socialapi" flex="1"/>
 
     <panelview id="PanelUI-feeds" flex="1" oncommand="FeedHandler.subscribeToFeed(null, event);">
       <label value="&feedsMenu.label;" class="panel-subview-header"/>
     </panelview>
 
     <panelview id="PanelUI-helpView" flex="1" class="PanelUI-subView">
       <label value="&helpMenu.label;" class="panel-subview-header"/>
-      <vbox id="PanelUI-helpItems"/>
+      <vbox id="PanelUI-helpItems" class="panel-subview-body"/>
     </panelview>
 
     <panelview id="PanelUI-developer" flex="1">
       <label value="&webDeveloperMenu.label;" class="panel-subview-header"/>
       <vbox id="PanelUI-developerItems" class="panel-subview-body"/>
     </panelview>
 
     <panelview id="PanelUI-characterEncodingView" flex="1">
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -298,16 +298,17 @@ const PanelUI = {
       tempPanel.setAttribute("context", "");
       document.getElementById(CustomizableUI.AREA_NAVBAR).appendChild(tempPanel);
       // If the view has a footer, set a convenience class on the panel.
       tempPanel.classList.toggle("cui-widget-panelWithFooter",
                                  viewNode.querySelector(".panel-subview-footer"));
 
       let multiView = document.createElement("panelmultiview");
       tempPanel.appendChild(multiView);
+      multiView.setAttribute("mainViewIsSubView", "true");
       multiView.setMainView(viewNode);
       viewNode.classList.add("cui-widget-panelview");
       CustomizableUI.addPanelCloseListeners(tempPanel);
 
       let panelRemover = function() {
         tempPanel.removeEventListener("popuphidden", panelRemover);
         viewNode.classList.remove("cui-widget-panelview");
         CustomizableUI.removePanelCloseListeners(tempPanel);
--- a/browser/components/customizableui/content/panelUI.xml
+++ b/browser/components/customizableui/content/panelUI.xml
@@ -61,16 +61,18 @@
       <field name="__transitioning">false</field>
       <field name="_ignoreMutations">false</field>
 
       <property name="showingSubView" readonly="true"
                 onget="return this._viewStack.getAttribute('viewtype') == 'subview'"/>
       <property name="_mainViewId" onget="return this.getAttribute('mainViewId');" onset="this.setAttribute('mainViewId', val); return val;"/>
       <property name="_mainView" readonly="true"
                 onget="return this._mainViewId ? document.getElementById(this._mainViewId) : null;"/>
+      <property name="showingSubViewAsMainView" readonly="true"
+                onget="return this.getAttribute('mainViewIsSubView') == 'true'"/>
 
       <property name="ignoreMutations">
         <getter>
           return this._ignoreMutations;
         </getter>
         <setter><![CDATA[
           this._ignoreMutations = val;
           if (!val && this._panel.state == "open") {
@@ -317,18 +319,23 @@
             let newHeight = this._heightOfSubview(this._currentSubView);
             this._viewContainer.style.height = newHeight + "px";
           }
         ]]></body>
       </method>
       <method name="_syncContainerWithMainView">
         <body><![CDATA[
           if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
-            this._viewContainer.style.height =
-              this._mainView.scrollHeight + "px";
+            let height;
+            if (this.showingSubViewAsMainView) {
+              height = this._heightOfSubview(this._mainView);
+            } else {
+              height = this._mainView.scrollHeight;
+            }
+            this._viewContainer.style.height = height + "px";
           }
         ]]></body>
       </method>
 
       <method name="_heightOfSubview">
         <parameter name="aSubview"/>
         <body><![CDATA[
           let body = aSubview.querySelector(".panel-subview-body");
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -3134,17 +3134,17 @@ function WidgetSingleWrapper(aWidget, aN
   this.__defineGetter__("disabled", function() aNode.disabled);
   this.__defineSetter__("disabled", function(aValue) {
     aNode.disabled = !!aValue;
   });
 
   this.__defineGetter__("anchor", function() {
     let anchorId;
     // First check for an anchor for the area:
-    let placement = CustomizableUIInternal.getPlacementOfWidget(aWidgetId);
+    let placement = CustomizableUIInternal.getPlacementOfWidget(aWidget.id);
     if (placement) {
       anchorId = gAreas.get(placement.area).get("anchor");
     }
     if (!anchorId) {
       anchorId = aNode.getAttribute("cui-anchorid");
     }
 
     return anchorId ? aNode.ownerDocument.getElementById(anchorId)
--- a/browser/components/customizableui/src/CustomizeMode.jsm
+++ b/browser/components/customizableui/src/CustomizeMode.jsm
@@ -849,16 +849,17 @@ CustomizeMode.prototype = {
       CustomizableUI.reset();
 
       yield this._wrapToolbarItems();
       yield this.populatePalette();
 
       this.persistCurrentSets(true);
 
       this._updateResetButton();
+      this._updateEmptyPaletteNotice();
       this._showPanelCustomizationPlaceholders();
       this.resetting = false;
     }.bind(this)).then(null, ERROR);
   },
 
   _onToolbarVisibilityChange: function(aEvent) {
     let toolbar = aEvent.target;
     if (aEvent.detail.visible && toolbar.getAttribute("customizable") == "true") {
@@ -950,18 +951,20 @@ CustomizeMode.prototype = {
         let widget = CustomizableUI.getWidget(aWidgetId);
         this.visiblePalette.appendChild(this.makePaletteItem(widget, "palette"));
       }
     }
   },
 
   _onUIChange: function() {
     this._changed = true;
-    this._updateResetButton();
-    this._updateEmptyPaletteNotice();
+    if (!this.resetting) {
+      this._updateResetButton();
+      this._updateEmptyPaletteNotice();
+    }
     this.dispatchToolboxEvent("customizationchange");
   },
 
   _updateEmptyPaletteNotice: function() {
     let paletteItems = this.visiblePalette.getElementsByTagName("toolbarpaletteitem");
     this.paletteEmptyNotice.hidden = !!paletteItems.length;
   },
 
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -24,16 +24,19 @@ const EVENTS = {
   // When new sources are received from the debugger server.
   NEW_SOURCE: "Debugger:NewSource",
   SOURCES_ADDED: "Debugger:SourcesAdded",
 
   // When a source is shown in the source editor.
   SOURCE_SHOWN: "Debugger:EditorSourceShown",
   SOURCE_ERROR_SHOWN: "Debugger:EditorSourceErrorShown",
 
+  // When the editor has shown a source and set the line / column position
+  EDITOR_LOCATION_SET: "Debugger:EditorLocationSet",
+
   // When scopes, variables, properties and watch expressions are fetched and
   // displayed in the variables view.
   FETCHED_SCOPES: "Debugger:FetchedScopes",
   FETCHED_VARIABLES: "Debugger:FetchedVariables",
   FETCHED_PROPERTIES: "Debugger:FetchedProperties",
   FETCHED_BUBBLE_PROPERTIES: "Debugger:FetchedBubbleProperties",
   FETCHED_WATCH_EXPRESSIONS: "Debugger:FetchedWatchExpressions",
 
@@ -1295,25 +1298,25 @@ SourceScripts.prototype = {
     if (textPromise && textPromise.pretty === wantPretty) {
       return textPromise;
     }
 
     const deferred = promise.defer();
     deferred.promise.pretty = wantPretty;
     this._cache.set(aSource.url, deferred.promise);
 
-    const afterToggle = ({ error, message, source: text }) => {
+    const afterToggle = ({ error, message, source: text, contentType }) => {
       if (error) {
         // Revert the rejected promise from the cache, so that the original
         // source's text may be shown when the source is selected.
         this._cache.set(aSource.url, textPromise);
         deferred.reject([aSource, message || error]);
         return;
       }
-      deferred.resolve([aSource, text]);
+      deferred.resolve([aSource, text, contentType]);
     };
 
     if (wantPretty) {
       sourceClient.prettyPrint(Prefs.editorTabSize, afterToggle);
     } else {
       sourceClient.disablePrettyPrint(afterToggle);
     }
 
@@ -1355,24 +1358,25 @@ SourceScripts.prototype = {
     this._cache.set(aSource.url, deferred.promise);
 
     // If the source text takes a long time to fetch, invoke a callback.
     if (aOnTimeout) {
       var fetchTimeout = window.setTimeout(() => aOnTimeout(aSource), aDelay);
     }
 
     // Get the source text from the active thread.
-    this.activeThread.source(aSource).source(({ error, message, source: text }) => {
+    this.activeThread.source(aSource)
+        .source(({ error, message, source: text, contentType }) => {
       if (aOnTimeout) {
         window.clearTimeout(fetchTimeout);
       }
       if (error) {
         deferred.reject([aSource, message || error]);
       } else {
-        deferred.resolve([aSource, text]);
+        deferred.resolve([aSource, text, contentType]);
       }
     });
 
     return deferred.promise;
   },
 
   /**
    * Starts fetching all the sources, silently.
@@ -1401,23 +1405,23 @@ SourceScripts.prototype = {
     }
 
     /* Called if fetching a source takes too long. */
     function onTimeout(aSource) {
       onError([aSource]);
     }
 
     /* Called if fetching a source finishes successfully. */
-    function onFetch([aSource, aText]) {
+    function onFetch([aSource, aText, aContentType]) {
       // If fetching the source has previously timed out, discard it this time.
       if (!pending.has(aSource.url)) {
         return;
       }
       pending.delete(aSource.url);
-      fetched.push([aSource.url, aText]);
+      fetched.push([aSource.url, aText, aContentType]);
       maybeFinish();
     }
 
     /* Called if fetching a source failed because of an error. */
     function onError([aSource, aError]) {
       pending.delete(aSource.url);
       maybeFinish();
     }
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -383,35 +383,36 @@ let DebuggerView = {
     let histogram = Services.telemetry.getHistogramById(histogramId);
     let startTime = Date.now();
 
     let deferred = promise.defer();
 
     this._setEditorText(L10N.getStr("loadingText"));
     this._editorSource = { url: aSource.url, promise: deferred.promise };
 
-    DebuggerController.SourceScripts.getText(aSource).then(([, aText]) => {
+    DebuggerController.SourceScripts.getText(aSource)
+                                    .then(([, aText, aContentType]) => {
       // Avoid setting an unexpected source. This may happen when switching
       // very fast between sources that haven't been fetched yet.
       if (this._editorSource.url != aSource.url) {
         return;
       }
 
       this._setEditorText(aText);
-      this._setEditorMode(aSource.url, aSource.contentType, aText);
+      this._setEditorMode(aSource.url, aContentType, aText);
 
       // Synchronize any other components with the currently displayed source.
       DebuggerView.Sources.selectedValue = aSource.url;
       DebuggerController.Breakpoints.updateEditorBreakpoints();
 
       histogram.add(Date.now() - startTime);
 
       // Resolve and notify that a source file was shown.
       window.emit(EVENTS.SOURCE_SHOWN, aSource);
-      deferred.resolve([aSource, aText]);
+      deferred.resolve([aSource, aText, aContentType]);
     },
     ([, aError]) => {
       let msg = L10N.getStr("errorLoadingText") + DevToolsUtils.safeErrorString(aError);
       this._setEditorText(msg);
       Cu.reportError(msg);
       dumpn(msg);
 
       // Reject and notify that there was an error showing the source file.
@@ -461,35 +462,40 @@ let DebuggerView = {
       }
     }
 
     let sourceItem = this.Sources.getItemByValue(aUrl);
     let sourceForm = sourceItem.attachment.source;
 
     // Make sure the requested source client is shown in the editor, then
     // update the source editor's caret position and debug location.
-    return this._setEditorSource(sourceForm, aFlags).then(() => {
+    return this._setEditorSource(sourceForm, aFlags)
+               .then(([,, aContentType]) => {
+      // Record the contentType learned from fetching
+      sourceForm.contentType = aContentType;
       // Line numbers in the source editor should start from 1. If invalid
       // or not specified, then don't do anything.
       if (aLine < 1) {
+        window.emit(EVENTS.EDITOR_LOCATION_SET);
         return;
       }
       if (aFlags.charOffset) {
         aLine += this.editor.getPosition(aFlags.charOffset).line;
       }
       if (aFlags.lineOffset) {
         aLine += aFlags.lineOffset;
       }
       if (!aFlags.noCaret) {
         let location = { line: aLine -1, ch: aFlags.columnOffset || 0 };
         this.editor.setCursor(location, aFlags.align);
       }
       if (!aFlags.noDebug) {
         this.editor.setDebugLocation(aLine - 1);
       }
+      window.emit(EVENTS.EDITOR_LOCATION_SET);
     }).then(null, console.error);
   },
 
   /**
    * Gets the visibility state of the instruments pane.
    * @return boolean
    */
   get instrumentsPaneHidden()
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -23,16 +23,18 @@ support-files =
   code_tracing-01.js
   code_ugly.js
   code_ugly-2.js
   code_ugly-3.js
   code_ugly-4.js
   code_ugly-5.js
   code_ugly-6.js
   code_ugly-7.js
+  code_ugly-8
+  code_ugly-8^headers^
   doc_auto-pretty-print-01.html
   doc_auto-pretty-print-02.html
   doc_binary_search.html
   doc_blackboxing.html
   doc_closures.html
   doc_cmd-break.html
   doc_cmd-dbg.html
   doc_conditional-breakpoints.html
@@ -51,16 +53,17 @@ support-files =
   doc_inline-script.html
   doc_large-array-buffer.html
   doc_minified.html
   doc_minified_bogus_map.html
   doc_no-page-sources.html
   doc_pause-exceptions.html
   doc_pretty-print.html
   doc_pretty-print-2.html
+  doc_pretty-print-3.html
   doc_random-javascript.html
   doc_recursion-stack.html
   doc_scope-variable.html
   doc_scope-variable-2.html
   doc_scope-variable-3.html
   doc_script-switching-01.html
   doc_script-switching-02.html
   doc_step-out.html
@@ -156,16 +159,17 @@ support-files =
 [browser_dbg_pretty-print-05.js]
 [browser_dbg_pretty-print-06.js]
 [browser_dbg_pretty-print-07.js]
 [browser_dbg_pretty-print-08.js]
 [browser_dbg_pretty-print-09.js]
 [browser_dbg_pretty-print-10.js]
 [browser_dbg_pretty-print-11.js]
 [browser_dbg_pretty-print-12.js]
+[browser_dbg_pretty-print-13.js]
 [browser_dbg_progress-listener-bug.js]
 [browser_dbg_reload-preferred-script-01.js]
 [browser_dbg_reload-preferred-script-02.js]
 [browser_dbg_reload-preferred-script-03.js]
 [browser_dbg_reload-same-script.js]
 [browser_dbg_scripts-switching-01.js]
 [browser_dbg_scripts-switching-02.js]
 [browser_dbg_scripts-switching-03.js]
copy from browser/devtools/debugger/test/browser_dbg_pretty-print-01.js
copy to browser/devtools/debugger/test/browser_dbg_pretty-print-13.js
--- a/browser/devtools/debugger/test/browser_dbg_pretty-print-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_pretty-print-13.js
@@ -1,33 +1,36 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
- * Make sure that clicking the pretty print button prettifies the source.
+ * Make sure that clicking the pretty print button prettifies the source, even
+ * when the source URL does not end in ".js", but the content type is
+ * JavaScript.
  */
 
-const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html";
+const TAB_URL = EXAMPLE_URL + "doc_pretty-print-3.html";
 
 let gTab, gDebuggee, gPanel, gDebugger;
 let gEditor, gSources;
 
 function test() {
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
 
-    waitForSourceShown(gPanel, "code_ugly.js")
+    promise.all([waitForSourceShown(gPanel, "code_ugly-8"),
+                 waitForEditorLocationSet(gPanel)])
       .then(testSourceIsUgly)
       .then(() => {
-        const finished = waitForSourceShown(gPanel, "code_ugly.js");
+        const finished = waitForSourceShown(gPanel, "code_ugly-8");
         clickPrettyPrintButton();
         testProgressBarShown();
         return finished;
       })
       .then(testSourceIsPretty)
       .then(testEditorShown)
       .then(testSourceIsStillPretty)
       .then(() => closeDebuggerAndFinish(gPanel))
copy from browser/devtools/debugger/test/code_ugly.js
copy to browser/devtools/debugger/test/code_ugly-8
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/code_ugly-8^headers^
@@ -0,0 +1,1 @@
+Content-Type: application/javascript
copy from browser/devtools/debugger/test/doc_pretty-print.html
copy to browser/devtools/debugger/test/doc_pretty-print-3.html
--- a/browser/devtools/debugger/test/doc_pretty-print.html
+++ b/browser/devtools/debugger/test/doc_pretty-print-3.html
@@ -1,8 +1,8 @@
 <!-- Any copyright is dedicated to the Public Domain.
      http://creativecommons.org/publicdomain/zero/1.0/ -->
 <!DOCTYPE html>
 <head>
   <meta charset="utf-8"/>
   <title>Debugger Pretty Printing Test Page</title>
 </head>
-<script src="code_ugly.js"></script>
+<script src="code_ugly-8"></script>
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -231,16 +231,21 @@ function waitForSourceShown(aPanel, aUrl
     if (!sourceUrl.contains(aUrl)) {
       return waitForSourceShown(aPanel, aUrl);
     } else {
       ok(true, "The correct source has been shown.");
     }
   });
 }
 
+function waitForEditorLocationSet(aPanel) {
+  return waitForDebuggerEvents(aPanel,
+                               aPanel.panelWin.EVENTS.EDITOR_LOCATION_SET);
+}
+
 function ensureSourceIs(aPanel, aUrl, aWaitFlag = false) {
   if (aPanel.panelWin.DebuggerView.Sources.selectedValue.contains(aUrl)) {
     ok(true, "Expected source is shown: " + aUrl);
     return promise.resolve(null);
   }
   if (aWaitFlag) {
     return waitForSourceShown(aPanel, aUrl);
   }
--- a/browser/devtools/styleeditor/StyleEditorUI.jsm
+++ b/browser/devtools/styleeditor/StyleEditorUI.jsm
@@ -200,17 +200,18 @@ StyleEditorUI.prototype = {
         line: line,
         col: ch
       };
     }
 
     // remember saved file locations
     for (let editor of this.editors) {
       if (editor.savedFile) {
-        this.savedLocations[editor.styleSheet.href] = editor.savedFile;
+        let identifier = this.getStyleSheetIdentifier(editor.styleSheet);
+        this.savedLocations[identifier] = editor.savedFile;
       }
     }
 
     this._clearStyleSheetEditors();
     this._view.removeAll();
 
     this.selectedEditor = null;
 
@@ -252,17 +253,18 @@ StyleEditorUI.prototype = {
    *        Object representing stylesheet
    * @param {nsIfile}  file
    *         Optional file object that sheet was imported from
    * @param {Boolean} isNew
    *         Optional if stylesheet is a new sheet created by user
    */
   _addStyleSheetEditor: function(styleSheet, file, isNew) {
     // recall location of saved file for this sheet after page reload
-    let savedFile = this.savedLocations[styleSheet.href];
+    let identifier = this.getStyleSheetIdentifier(styleSheet);
+    let savedFile = this.savedLocations[identifier];
     if (savedFile && !file) {
       file = savedFile;
     }
 
     let editor =
       new StyleSheetEditor(styleSheet, this._window, file, isNew, this._walker);
 
     editor.on("property-change", this._summaryChange.bind(this, editor));
@@ -522,16 +524,28 @@ StyleEditorUI.prototype = {
         deferred.resolve(editor.summary);
       }
     });
 
     return deferred.promise;
   },
 
   /**
+   * Returns an identifier for the given style sheet.
+   *
+   * @param {StyleSheet} aStyleSheet
+   *        The style sheet to be identified.
+   */
+  getStyleSheetIdentifier: function (aStyleSheet) {
+    // Identify inline style sheets by their host page URI and index at the page.
+    return aStyleSheet.href ? aStyleSheet.href :
+            "inline-" + aStyleSheet.styleSheetIndex + "-at-" + aStyleSheet.nodeHref;
+  },
+
+  /**
    * selects a stylesheet and optionally moves the cursor to a selected line
    *
    * @param {string} [href]
    *        Href of stylesheet that should be selected. If a stylesheet is not passed
    *        and the editor is not initialized we focus the first stylesheet. If
    *        a stylesheet is not passed and the editor is initialized we ignore
    *        the call.
    * @param {Number} [line]
--- a/browser/devtools/styleeditor/test/browser.ini
+++ b/browser/devtools/styleeditor/test/browser.ini
@@ -2,16 +2,18 @@
 support-files =
   autocomplete.html
   browser_styleeditor_cmd_edit.html
   four.html
   head.js
   import.css
   import.html
   import2.css
+  inline-1.html
+  inline-2.html
   longload.html
   media-small.css
   media.html
   minified.html
   nostyle.html
   resources_inpage.jsi
   resources_inpage1.css
   resources_inpage2.css
@@ -32,16 +34,17 @@ support-files =
 [browser_styleeditor_bug_851132_middle_click.js]
 [browser_styleeditor_bug_870339.js]
 [browser_styleeditor_cmd_edit.js]
 [browser_styleeditor_enabled.js]
 [browser_styleeditor_filesave.js]
 [browser_styleeditor_import.js]
 [browser_styleeditor_import_rule.js]
 [browser_styleeditor_init.js]
+[browser_styleeditor_inline_friendly_names.js]
 [browser_styleeditor_loading.js]
 [browser_styleeditor_new.js]
 [browser_styleeditor_nostyle.js]
 [browser_styleeditor_pretty.js]
 # Disabled because of intermittent failures - See Bug 942473
 skip-if = true
 [browser_styleeditor_private_perwindowpb.js]
 [browser_styleeditor_reload.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_inline_friendly_names.js
@@ -0,0 +1,150 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let gUI;
+
+const FIRST_TEST_PAGE = TEST_BASE + "inline-1.html"
+const SECOND_TEST_PAGE = TEST_BASE + "inline-2.html"
+const SAVE_PATH = "test.css";
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndOpenStyleEditor(function(panel) {
+    gUI = panel.UI;
+
+    // First test that identifiers are correcly generated. If not other tests
+    // are likely to fail.
+    testIndentifierGeneration();
+
+    waitForEditors(2)
+    .then(saveFirstInlineStyleSheet)
+    .then(testFriendlyNamesAfterSave)
+    .then(reloadPage)
+    .then(testFriendlyNamesAfterSave)
+    .then(navigateToAnotherPage)
+    .then(testFriendlyNamesAfterNavigation)
+    .then(finishTests);
+  });
+
+  content.location = FIRST_TEST_PAGE;
+}
+
+function testIndentifierGeneration() {
+  let fakeStyleSheetFile = {
+    "href": "http://example.com/test.css",
+    "nodeHref": "http://example.com/",
+    "styleSheetIndex": 1
+  }
+
+  let fakeInlineStyleSheet = {
+    "href": null,
+    "nodeHref": "http://example.com/",
+    "styleSheetIndex": 2
+  }
+
+  is(gUI.getStyleSheetIdentifier(fakeStyleSheetFile), "http://example.com/test.css",
+    "URI is the identifier of style sheet file.");
+
+  is(gUI.getStyleSheetIdentifier(fakeInlineStyleSheet), "inline-2-at-http://example.com/",
+    "Inline style sheets are identified by their page and position at that page.");
+}
+
+function saveFirstInlineStyleSheet() {
+  let deferred = promise.defer();
+  let editor = gUI.editors[0];
+
+  let destFile = FileUtils.getFile("ProfD", [SAVE_PATH]);
+
+  editor.saveToFile(destFile, function (file) {
+    ok(file, "File was correctly saved.");
+    deferred.resolve();
+  });
+
+  return deferred.promise;
+}
+
+function testFriendlyNamesAfterSave() {
+  let firstEditor = gUI.editors[0];
+  let secondEditor = gUI.editors[1];
+
+  // The friendly name of first sheet should've been remembered, the second should
+  // not be the same (bug 969900).
+  is(firstEditor.friendlyName, SAVE_PATH,
+    "Friendly name is correct for the saved inline style sheet.");
+  isnot(secondEditor.friendlyName, SAVE_PATH,
+    "Friendly name is for the second inline style sheet is not the same as first.");
+
+  return promise.resolve(null);
+}
+
+function reloadPage() {
+  info("Reloading page.");
+  content.location.reload();
+  return waitForEditors(2);
+}
+
+function navigateToAnotherPage() {
+  info("Navigating to another page.");
+  let deferred = promise.defer();
+  gBrowser.removeCurrentTab();
+
+  gUI = null;
+
+  addTabAndOpenStyleEditor(function(panel) {
+    gUI = panel.UI;
+
+    waitForEditors(2).then(deferred.resolve);
+  });
+
+  content.location = SECOND_TEST_PAGE;
+  return deferred.promise;
+}
+
+function testFriendlyNamesAfterNavigation() {
+  let firstEditor = gUI.editors[0];
+  let secondEditor = gUI.editors[1];
+
+  // Inline style sheets shouldn't have the name of previously saved file as the
+  // page is different.
+  isnot(firstEditor.friendlyName, SAVE_PATH,
+    "The first editor doesn't have the save path as a friendly name.");
+  isnot(secondEditor.friendlyName, SAVE_PATH,
+    "The second editor doesn't have the save path as a friendly name.");
+
+  return promise.resolve(null);
+}
+
+function finishTests() {
+  gUI = null;
+  finish();
+}
+
+/**
+ * Waits for all editors to be added.
+ *
+ * @param {int} aNumberOfEditors
+ *        The number of editors to wait until proceeding.
+ *
+ * Returns a promise that's resolved once all editors are added.
+ */
+function waitForEditors(aNumberOfEditors) {
+  let deferred = promise.defer();
+  let count = 0;
+
+  info("Waiting for " + aNumberOfEditors + " editors to be added");
+  gUI.on("editor-added", function editorAdded(event, editor) {
+    if (++count == aNumberOfEditors) {
+        info("All editors added. Resolving promise.");
+        gUI.off("editor-added", editorAdded);
+        gUI.editors[0].getSourceEditor().then(deferred.resolve);
+    }
+    else {
+        info ("Editor " + count + " of " + aNumberOfEditors + " added.");
+    }
+  });
+
+  return deferred.promise;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/inline-1.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html>
+ <head>
+  <title>Inline test page #1</title>
+  <style type="text/css">
+    .second {
+     font-size:2em;
+    }
+  </style>
+  <style type="text/css">
+    .first {
+     font-size:3em;
+    }
+  </style>
+ </head>
+ <body class="first">
+   Inline test page #1
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/inline-2.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html>
+ <head>
+  <title>Inline test page #2</title>
+  <style type="text/css">
+    .second {
+     font-size:2em;
+    }
+  </style>
+  <style type="text/css">
+    .first {
+     font-size:3em;
+    }
+  </style>
+ </head>
+ <body class="second">
+   Inline test page #2
+ </body>
+</html>
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_601352_scroll.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_601352_scroll.js
@@ -1,59 +1,71 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* ***** BEGIN LICENSE BLOCK *****
- * Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- *
- * Contributor(s):
- *  Mihai Șucan <mihai.sucan@gmail.com>
- *
- * ***** END LICENSE BLOCK ***** */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that the console output scrolls to JS eval results when there are many
+// messages displayed. See bug 601352.
+
+function test() {
+  Task.spawn(runner).then(finishTest);
 
-function consoleOpened(HUD) {
-  HUD.jsterm.clearOutput();
+  function* runner() {
+    let {tab} = yield loadTab("data:text/html;charset=utf-8,Web Console test for bug 601352");
+    let hud = yield openConsole(tab);
+    hud.jsterm.clearOutput();
+
+    let longMessage = "";
+    for (let i = 0; i < 50; i++) {
+      longMessage += "LongNonwrappingMessage";
+    }
+
+    for (let i = 0; i < 50; i++) {
+      content.console.log("test1 message " + i);
+    }
 
-  let longMessage = "";
-  for (let i = 0; i < 50; i++) {
-    longMessage += "LongNonwrappingMessage";
-  }
+    content.console.log(longMessage);
 
-  for (let i = 0; i < 50; i++) {
-    content.console.log("test message " + i);
-  }
+    for (let i = 0; i < 50; i++) {
+      content.console.log("test2 message " + i);
+    }
 
-  content.console.log(longMessage);
+    yield waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "test1 message 0",
+      }, {
+        text: "test1 message 49",
+      }, {
+        text: "LongNonwrappingMessage",
+      }, {
+        text: "test2 message 0",
+      }, {
+        text: "test2 message 49",
+      }],
+    });
 
-  for (let i = 0; i < 50; i++) {
-    content.console.log("test message " + i);
-  }
+    let nodeDeferred = promise.defer();
+    hud.jsterm.execute("1+1", (node) => { nodeDeferred.resolve(node); });
+    let node = yield nodeDeferred.promise;
 
-  HUD.jsterm.execute("1+1", performTest);
-
-  function performTest(node) {
-    let scrollNode = HUD.outputNode.parentNode;
+    let scrollNode = hud.outputNode.parentNode;
     let rectNode = node.getBoundingClientRect();
     let rectOutput = scrollNode.getBoundingClientRect();
+    console.debug("rectNode", rectNode, "rectOutput", rectOutput);
+    console.log("scrollNode scrollHeight", scrollNode.scrollHeight, "scrollTop", scrollNode.scrollTop, "clientHeight", scrollNode.clientHeight);
 
     isnot(scrollNode.scrollTop, 0, "scroll location is not at the top");
 
-    // Visible scroll viewport.
-    let height = scrollNode.scrollHeight - scrollNode.scrollTop;
+    // The bounding client rect .top/left coordinates are relative to the
+    // console iframe.
 
-    // Top position of the last message node, relative to the outputNode.
-    let top = rectNode.top + scrollNode.scrollTop;
-    let bottom = top + node.clientHeight;
-    info("output height " + height + " node top " + top + " node bottom " + bottom + " node height " + node.clientHeight);
+    // Visible scroll viewport.
+    let height = rectOutput.height;
+
+    // Top and bottom coordinates of the last message node, relative to the outputNode.
+    let top = rectNode.top - rectOutput.top;
+    let bottom = top + rectNode.height;
+    info("node top " + top + " node bottom " + bottom + " node clientHeight " + node.clientHeight);
 
     ok(top >= 0 && bottom <= height, "last message is visible");
-
-    finishTest();
-  };
+  }
 }
-
-function test() {
-  addTab("data:text/html;charset=utf-8,Web Console test for bug 601352");
-  browser.addEventListener("load", function tabLoad(aEvent) {
-    browser.removeEventListener(aEvent.type, tabLoad, true);
-    openConsole(null, consoleOpened);
-  }, true);
-}
-
--- a/browser/metro/base/content/bindings/bindings.xml
+++ b/browser/metro/base/content/bindings/bindings.xml
@@ -191,17 +191,17 @@
           box.showContextMenu(this, event, false);
         ]]>
       </handler>
       <handler event="click" phase="capturing">
         <![CDATA[
         if (event.mozInputSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH) {
           if (typeof SelectionHelperUI != 'undefined') {
             SelectionHelperUI.attachEditSession(ChromeSelectionHandler,
-                                                event.clientX, event.clientY);
+                event.clientX, event.clientY, this);
           } else {
             // If we don't have access to SelectionHelperUI then we are using this
             // binding for browser content (e.g. about:config)
             Services.obs.notifyObservers(event, "attach_edit_session_to_content", "");
           }
         }
       ]]>
       </handler>
@@ -290,17 +290,17 @@
       </method>
     </implementation>
     <handlers>
       <handler event="click" phase="capturing">
         <![CDATA[
           if (event.mozInputSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH) {
             if (typeof SelectionHelperUI != 'undefined') {
               SelectionHelperUI.attachEditSession(ChromeSelectionHandler,
-                                                  event.clientX, event.clientY);
+                  event.clientX, event.clientY, this);
             } else {
               // If we don't have access to SelectionHelperUI then we are using this
               // binding for browser content (e.g. about:config)
               Services.obs.notifyObservers(event, "attach_edit_session_to_content", "");
             }
           }
         ]]>
       </handler>
--- a/browser/metro/base/content/bindings/urlbar.xml
+++ b/browser/metro/base/content/bindings/urlbar.xml
@@ -287,16 +287,22 @@
             if (!this.focused)
               this.focus();
 
             this._clearFormatting();
             this.select();
 
             if (aShouldDismiss)
               ContextUI.dismissTabs();
+
+            if (!InputSourceHelper.isPrecise && this.textLength) {
+              let inputRectangle = this.inputField.getBoundingClientRect();
+              SelectionHelperUI.attachEditSession(ChromeSelectionHandler,
+                  inputRectangle.left, inputRectangle.top, this);
+            }
           ]]>
         </body>
       </method>
 
       <method name="endEditing">
         <parameter name="aShouldRevert"/>
         <body>
           <![CDATA[
@@ -443,16 +449,25 @@
           // and displays the half-height autocomplete popup.
           this.beginEditing();
           this.openPopup();
         ]]>
       </handler>
 
       <handler event="click" phase="capturing">
         <![CDATA[
+          // workaround for bug 925457: taping browser chrome resets last tap
+          // co-ordinates to 'undefined' so that we know not to shift the
+          // browser when the keyboard is up in SelectionHandler's
+          // _calcNewContentPosition().
+          Browser.selectedTab.browser.messageManager.sendAsyncMessage(
+              "Browser:ResetLastPos", {
+                xPos: null,
+                yPos: null
+              });
           this.beginEditing(true);
         ]]>
       </handler>
 
       <!-- Editing mode behaviors -->
 
       <handler event="dblclick" phase="capturing">
         <![CDATA[
--- a/browser/metro/base/content/browser.xul
+++ b/browser/metro/base/content/browser.xul
@@ -477,18 +477,17 @@ Desktop browser's sync prefs.
 
             <textbox id="urlbar-edit"
                      type="url"
                      flex="1"
                      autocompletesearch="history"
                      autocompletepopup="urlbar-autocomplete"
                      completeselectedindex="true"
                      placeholder="&urlbar.emptytext;"
-                     tabscrolling="true"
-                     onclick="SelectionHelperUI.urlbarTextboxClick(this);"/>
+                     tabscrolling="true" />
 
             <toolbarbutton id="go-button"
                            class="urlbar-button"
                            command="cmd_go"/>
             <toolbarbutton id="reload-button"
                            class="urlbar-button"
                            oncommand="CommandUpdater.doCommand(
                                         event.shiftKey ? 'cmd_forceReload'
--- a/browser/metro/base/content/helperui/ChromeSelectionHandler.js
+++ b/browser/metro/base/content/helperui/ChromeSelectionHandler.js
@@ -28,17 +28,17 @@ var ChromeSelectionHandler = {
    */
 
   /*
    * General selection start method for both caret and selection mode.
    */
   _onSelectionAttach: function _onSelectionAttach(aJson) {
     this._domWinUtils = Util.getWindowUtils(window);
     this._contentWindow = window;
-    this._targetElement = this._domWinUtils.elementFromPoint(aJson.xPos, aJson.yPos, true, false);
+    this._targetElement = aJson.target;
     this._targetIsEditable = this._targetElement instanceof Components.interfaces.nsIDOMXULTextBoxElement;
     if (!this._targetIsEditable) {
       this._onFail("not an editable?", this._targetElement);
       return;
     }
 
     let selection = this._getSelection();
     if (!selection) {
--- a/browser/metro/base/content/helperui/SelectionHelperUI.js
+++ b/browser/metro/base/content/helperui/SelectionHelperUI.js
@@ -430,60 +430,69 @@ var SelectionHelperUI = {
     });
   },
 
   /*
    * attachEditSession
    *
    * Attaches to existing selection and begins editing.
    *
-   * @param aMsgTarget - Browser or chrome message target
-   * @param aX, aY - Browser relative client coordinates.
+   * @param aMsgTarget - Browser or chrome message target.
+   * @param aX Tap browser relative client X coordinate.
+   * @param aY Tap browser relative client Y coordinate.
+   * @param aTarget Actual tap target (optional).
    */
-  attachEditSession: function attachEditSession(aMsgTarget, aX, aY) {
+  attachEditSession: function attachEditSession(aMsgTarget, aX, aY, aTarget) {
     if (!aMsgTarget || this.isActive)
       return;
     this._init(aMsgTarget);
     this._setupDebugOptions();
 
     // Send this over to SelectionHandler in content, they'll message us
     // back with information on the current selection. SelectionAttach
     // takes client coordinates.
     this._sendAsyncMessage("Browser:SelectionAttach", {
+      target: aTarget,
       xPos: aX,
       yPos: aY
     });
   },
 
   /*
    * attachToCaret
    * 
    * Initiates a touch caret selection session for a text input.
    * Can be called multiple times to move the caret marker around.
    *
    * Note the caret marker is pretty limited in functionality. The
    * only thing is can do is be displayed at the caret position.
    * Once the user starts a drag, the caret marker is hidden, and
    * the start and end markers take over.
    *
-   * @param aMsgTarget - Browser or chrome message target
-   * @param aX, aY - Browser relative client coordinates of the tap
-   * that initiated the session.
+   * @param aMsgTarget - Browser or chrome message target.
+   * @param aX Tap browser relative client X coordinate.
+   * @param aY Tap browser relative client Y coordinate.
+   * @param aTarget Actual tap target (optional).
    */
-  attachToCaret: function attachToCaret(aMsgTarget, aX, aY) {
+  attachToCaret: function attachToCaret(aMsgTarget, aX, aY, aTarget) {
     if (!this.isActive) {
       this._init(aMsgTarget);
       this._setupDebugOptions();
     } else {
       this._hideMonocles();
     }
 
-    this._lastPoint = { xPos: aX, yPos: aY };
+    this._lastCaretAttachment = {
+      target: aTarget,
+      xPos: aX,
+      yPos: aY
+    };
 
     this._sendAsyncMessage("Browser:CaretAttach", {
+      target: aTarget,
       xPos: aX,
       yPos: aY
     });
   },
 
   /*
    * canHandleContextMenuMsg
    *
@@ -512,47 +521,23 @@ var SelectionHelperUI = {
     // which we will call _shutdown().
     let clearSelection = aClearSelection || false;
     this._sendAsyncMessage("Browser:SelectionClose", {
       clearSelection: clearSelection
     });
   },
 
   /*
-   * Event handler on the navbar text input. Called from navbar bindings
-   * when focus is applied to the edit.
-   */
-  urlbarTextboxClick: function(aEdit) {
-    // workaround for bug 925457: taping browser chrome resets last tap
-    // co-ordinates to 'undefined' so that we know not to shift the browser
-    // when the keyboard is up in SelectionHandler's _calcNewContentPosition().
-    Browser.selectedTab.browser.messageManager.sendAsyncMessage("Browser:ResetLastPos", {
-      xPos: null,
-      yPos: null
-    });
-
-    if (InputSourceHelper.isPrecise || !aEdit.textLength) {
-      return;
-    }
-
-    // Enable selection when there's text in the control
-    let innerRect = aEdit.inputField.getBoundingClientRect();
-    this.attachEditSession(ChromeSelectionHandler,
-                           innerRect.left,
-                           innerRect.top);
-  },
-
-  /*
    * Click handler for chrome pages loaded into the browser (about:config).
    * Called from the text input bindings via the attach_edit_session_to_content
    * observer.
    */
   chromeTextboxClick: function (aEvent) {
-    this.attachEditSession(Browser.selectedTab.browser,
-                           aEvent.clientX, aEvent.clientY);
+    this.attachEditSession(Browser.selectedTab.browser, aEvent.clientX,
+        aEvent.clientY, aEvent.target);
   },
 
   /*
    * Handy debug routines that work independent of selection. They
    * make use of the selection overlay for drawing points.
    */
 
   debugDisplayDebugPoint: function (aLeft, aTop, aSize, aCssColorStr, aFill) {
@@ -870,22 +855,23 @@ var SelectionHelperUI = {
   },
 
   /*
    * Event handlers for document events
    */
 
   /*
    * Handles taps that move the current caret around in text edits,
-   * clear active selection and focus when neccessary, or change
-   * modes. Only active afer SelectionHandlerUI is initialized.
+   * clear active selection and focus when necessary, or change
+   * modes. Only active after SelectionHandlerUI is initialized.
    */
   _onClick: function(aEvent) {
     if (this.layerMode == kChromeLayer && this._targetIsEditable) {
-      this.attachToCaret(this._msgTarget, aEvent.clientX, aEvent.clientY);
+      this.attachToCaret(this._msgTarget, aEvent.clientX, aEvent.clientY,
+          aEvent.target);
     }
   },
 
   _onKeypress: function _onKeypress() {
     this.closeEditSession();
   },
 
   _onResize: function _onResize() {
@@ -904,17 +890,18 @@ var SelectionHelperUI = {
 
   /*
    * _onDeckOffsetChanged - fired by ContentAreaObserver after the browser
    * deck is shifted for form input access in response to a soft keyboard
    * display.
    */
   _onDeckOffsetChanged: function _onDeckOffsetChanged(aEvent) {
     // Update the monocle position and display
-    this.attachToCaret(null, this._lastPoint.xPos, this._lastPoint.yPos);
+    this.attachToCaret(null, this._lastCaretAttachment.xPos,
+        this._lastCaretAttachment.yPos, this._lastCaretAttachment.target);
   },
 
   /*
    * Detects when the nav bar transitions, so we can enable selection at the
    * appropriate location once the transition is complete, or shutdown
    * selection down when the nav bar is hidden.
    */
   _onNavBarTransitionEvent: function _onNavBarTransitionEvent(aEvent) {
--- a/browser/metro/base/tests/mochitest/browser_selection_urlbar.js
+++ b/browser/metro/base/tests/mochitest/browser_selection_urlbar.js
@@ -102,17 +102,18 @@ gTests.push({
     edit.select();
 
     let editCoords = logicalCoordsForElement(edit);
 
     // wait for popup animation to complete, it interferes with edit selection testing
     let autocompletePopup = document.getElementById("urlbar-autocomplete-scroll");
     yield waitForEvent(autocompletePopup, "transitionend");
 
-    SelectionHelperUI.attachEditSession(ChromeSelectionHandler, editCoords.x, editCoords.y);
+    SelectionHelperUI.attachEditSession(ChromeSelectionHandler, editCoords.x,
+        editCoords.y, edit);
     ok(SelectionHelperUI.isSelectionUIVisible, "selection enabled");
 
     let selection = edit.QueryInterface(Components.interfaces.nsIDOMXULTextBoxElement)
                         .editor.selection;
     let rects = selection.getRangeAt(0).getClientRects();
     let midX = Math.ceil(((rects[0].right - rects[0].left) * .5) + rects[0].left);
     let midY = Math.ceil(((rects[0].bottom - rects[0].top) * .5) + rects[0].top);
 
@@ -131,17 +132,18 @@ gTests.push({
   desc: "bug 894713 - blur shuts down selection handling",
   run: function() {
     gWindow = window;
     yield showNavBar();
     let edit = document.getElementById("urlbar-edit");
     edit.value = "wikipedia.org";
     edit.select();
     let editCoords = logicalCoordsForElement(edit);
-    SelectionHelperUI.attachEditSession(ChromeSelectionHandler, editCoords.x, editCoords.y);
+    SelectionHelperUI.attachEditSession(ChromeSelectionHandler, editCoords.x,
+        editCoords.y, edit);
     edit.blur();
     ok(!SelectionHelperUI.isSelectionUIVisible, "selection no longer enabled");
     clearSelection(edit);
     yield waitForCondition(function () {
       return !SelectionHelperUI.isSelectionUIVisible;
     });
   }
 });
@@ -223,15 +225,35 @@ gTests.push({
     edit.blur();
 
     yield waitForCondition(function () {
       return !SelectionHelperUI.isSelectionUIVisible;
     });
   }
 });
 
+gTests.push({
+  desc: "Bug 957646 - Selection monocles sometimes don't display when tapping" +
+        " text ion the nav bar.",
+  run: function() {
+    yield showNavBar();
+
+    let edit = document.getElementById("urlbar-edit");
+    edit.value = "about:mozilla";
+
+    let editRectangle = edit.getBoundingClientRect();
+
+    // Tap outside the input but close enough for fluffing to take effect.
+    sendTap(window, editRectangle.left + 50, editRectangle.top - 2);
+
+    yield waitForCondition(function () {
+      return SelectionHelperUI.isSelectionUIVisible;
+    });
+  }
+});
+
 function test() {
   if (!isLandscapeMode()) {
     todo(false, "browser_selection_tests need landscape mode to run.");
     return;
   }
   runTests();
 }
--- a/browser/metro/base/tests/mochitest/browser_ui_telemetry.js
+++ b/browser/metro/base/tests/mochitest/browser_ui_telemetry.js
@@ -21,8 +21,46 @@ gTests.push({
     is(getTelemetryPayload().info.appName, "MetroFirefox");
 
     let simpleMeasurements = getTelemetryPayload().simpleMeasurements;
     ok(simpleMeasurements, "simpleMeasurements are truthy");
     ok(simpleMeasurements.UITelemetry["metro-ui"]["window-width"], "window-width measurement was captured");
     ok(simpleMeasurements.UITelemetry["metro-ui"]["window-height"], "window-height measurement was captured");
   }
 });
+
+gTests.push({
+  desc: "Test tab count telemetry",
+  run: function() {
+    // Wait for Session Manager to be initialized.
+    yield waitForCondition(() => window.__SSID);
+
+    Services.obs.notifyObservers(null, "reset-telemetry-vars", null);
+    yield waitForCondition(function () {
+      let simpleMeasurements = getTelemetryPayload().simpleMeasurements;
+      return simpleMeasurements.UITelemetry["metro-tabs"]["currTabCount"] == 1;
+    });
+
+    let simpleMeasurements = getTelemetryPayload().simpleMeasurements;
+    is(simpleMeasurements.UITelemetry["metro-tabs"]["currTabCount"], 1);
+    is(simpleMeasurements.UITelemetry["metro-tabs"]["maxTabCount"], 1);
+
+    let tab2 = Browser.addTab("about:mozilla");
+    simpleMeasurements = getTelemetryPayload().simpleMeasurements;
+    is(simpleMeasurements.UITelemetry["metro-tabs"]["currTabCount"], 2);
+    is(simpleMeasurements.UITelemetry["metro-tabs"]["maxTabCount"], 2);
+
+    let tab3 = Browser.addTab("about:config");
+    simpleMeasurements = getTelemetryPayload().simpleMeasurements;
+    is(simpleMeasurements.UITelemetry["metro-tabs"]["currTabCount"], 3);
+    is(simpleMeasurements.UITelemetry["metro-tabs"]["maxTabCount"], 3);
+
+    Browser.closeTab(tab2, { forceClose: true } );
+    simpleMeasurements = getTelemetryPayload().simpleMeasurements;
+    is(simpleMeasurements.UITelemetry["metro-tabs"]["currTabCount"], 2);
+    is(simpleMeasurements.UITelemetry["metro-tabs"]["maxTabCount"], 3);
+
+    Browser.closeTab(tab3, { forceClose: true } );
+    simpleMeasurements = getTelemetryPayload().simpleMeasurements;
+    is(simpleMeasurements.UITelemetry["metro-tabs"]["currTabCount"], 1);
+    is(simpleMeasurements.UITelemetry["metro-tabs"]["maxTabCount"], 3);
+  }
+});
\ No newline at end of file
--- a/browser/metro/components/SessionStore.js
+++ b/browser/metro/components/SessionStore.js
@@ -19,16 +19,19 @@ XPCOMUtils.defineLazyServiceGetter(this,
 XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
   "@mozilla.org/uuid-generator;1", "nsIUUIDGenerator");
 
 XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
   Cu.import("resource://gre/modules/NetUtil.jsm");
   return NetUtil;
 });
 
+XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
+  "resource://gre/modules/UITelemetry.jsm");
+
 // -----------------------------------------------------------------------
 // Session Store
 // -----------------------------------------------------------------------
 
 const STATE_STOPPED = 0;
 const STATE_RUNNING = 1;
 const STATE_QUITTING = -1;
 
@@ -47,28 +50,38 @@ SessionStore.prototype = {
   _selectedWindow: 1,
   _orderedWindows: [],
   _lastSaveTime: 0,
   _lastSessionTime: 0,
   _interval: 10000,
   _maxTabsUndo: 1,
   _shouldRestore: false,
 
+  // Tab telemetry variables
+  _maxTabsOpen: 1,
+
   init: function ss_init() {
     // Get file references
     this._sessionFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
     this._sessionFileBackup = this._sessionFile.clone();
     this._sessionCache = this._sessionFile.clone();
     this._sessionFile.append("sessionstore.js");
     this._sessionFileBackup.append("sessionstore.bak");
     this._sessionCache.append("sessionstoreCache");
 
     this._loadState = STATE_STOPPED;
 
     try {
+      UITelemetry.addSimpleMeasureFunction("metro-tabs",
+                                          this._getTabStats.bind(this));
+    } catch (ex) {
+      // swallow exception that occurs if metro-tabs measure is already set up
+    }
+
+    try {
       let shutdownWasUnclean = false;
 
       if (this._sessionFileBackup.exists()) {
         this._sessionFileBackup.remove(false);
         shutdownWasUnclean = true;
       }
 
       if (this._sessionFile.exists()) {
@@ -179,29 +192,37 @@ SessionStore.prototype = {
 
     // Remove the stale files in a separate step to keep the enumerator from
     // messing up if we remove the files as we collect them.
     staleFiles.forEach(function(aFile) {
       aFile.remove(false);
     })
   },
 
+  _getTabStats: function() {
+    return {
+      currTabCount: this._currTabCount,
+      maxTabCount: this._maxTabsOpen
+    };
+  },
+
   observe: function ss_observe(aSubject, aTopic, aData) {
     let self = this;
     let observerService = Services.obs;
     switch (aTopic) {
       case "app-startup":
         observerService.addObserver(this, "final-ui-startup", true);
         observerService.addObserver(this, "domwindowopened", true);
         observerService.addObserver(this, "domwindowclosed", true);
         observerService.addObserver(this, "browser-lastwindow-close-granted", true);
         observerService.addObserver(this, "browser:purge-session-history", true);
         observerService.addObserver(this, "quit-application-requested", true);
         observerService.addObserver(this, "quit-application-granted", true);
         observerService.addObserver(this, "quit-application", true);
+        observerService.addObserver(this, "reset-telemetry-vars", true);
         break;
       case "final-ui-startup":
         observerService.removeObserver(this, "final-ui-startup");
         if (WindowsPrefSync) {
           // Pulls in Desktop controlled prefs and pushes out Metro controlled prefs
           WindowsPrefSync.init();
         }
         this.init();
@@ -259,16 +280,17 @@ SessionStore.prototype = {
           this._sessionFileBackup.remove(false);
 
         observerService.removeObserver(this, "domwindowopened");
         observerService.removeObserver(this, "domwindowclosed");
         observerService.removeObserver(this, "browser-lastwindow-close-granted");
         observerService.removeObserver(this, "quit-application-requested");
         observerService.removeObserver(this, "quit-application-granted");
         observerService.removeObserver(this, "quit-application");
+        observerService.removeObserver(this, "reset-telemetry-vars");
 
         // If a save has been queued, kill the timer and save state now
         if (this._saveTimer) {
           this._saveTimer.cancel();
           this._saveTimer = null;
           this.saveState();
         }
         break;
@@ -290,34 +312,48 @@ SessionStore.prototype = {
           this.saveStateNow();
         }
         break;
       case "timer-callback":
         // Timer call back for delayed saving
         this._saveTimer = null;
         this.saveState();
         break;
+      case "reset-telemetry-vars":
+        // Used in mochitests only.
+        this._maxTabsOpen = 1;
     }
   },
 
+  updateTabTelemetryVars: function(window) {
+    this._currTabCount = window.Browser.tabs.length;
+      if (this._currTabCount > this._maxTabsOpen) {
+        this._maxTabsOpen = this._currTabCount;
+      }
+  },
+
   handleEvent: function ss_handleEvent(aEvent) {
     let window = aEvent.currentTarget.ownerDocument.defaultView;
     switch (aEvent.type) {
       case "TabOpen":
+        this.updateTabTelemetryVars(window);
       case "TabClose": {
         let browser = aEvent.originalTarget.linkedBrowser;
         if (aEvent.type == "TabOpen") {
           this.onTabAdd(window, browser);
         }
         else {
           this.onTabClose(window, browser);
           this.onTabRemove(window, browser);
         }
         break;
     }
+      case "TabRemove":
+        this.updateTabTelemetryVars(window);
+        break;
       case "TabSelect": {
         let browser = aEvent.originalTarget.linkedBrowser;
         this.onTabSelect(window, browser);
         break;
       }
     }
   },
 
@@ -356,27 +392,29 @@ SessionStore.prototype = {
     let tabs = aWindow.Browser.tabs;
     for (let i = 0; i < tabs.length; i++)
       this.onTabAdd(aWindow, tabs[i].browser, true);
 
     // Notification of tab add/remove/selection
     let tabContainer = aWindow.document.getElementById("tabs");
     tabContainer.addEventListener("TabOpen", this, true);
     tabContainer.addEventListener("TabClose", this, true);
+    tabContainer.addEventListener("TabRemove", this, true);
     tabContainer.addEventListener("TabSelect", this, true);
   },
 
   onWindowClose: function ss_onWindowClose(aWindow) {
     // Ignore windows not tracked by SessionStore
     if (!aWindow.__SSID || !this._windows[aWindow.__SSID])
       return;
 
     let tabContainer = aWindow.document.getElementById("tabs");
     tabContainer.removeEventListener("TabOpen", this, true);
     tabContainer.removeEventListener("TabClose", this, true);
+    tabContainer.removeEventListener("TabRemove", this, true);
     tabContainer.removeEventListener("TabSelect", this, true);
 
     if (this._loadState == STATE_RUNNING) {
       // Update all window data for a last time
       this._collectWindowData(aWindow);
 
       // Clear this window from the list
       delete this._windows[aWindow.__SSID];
--- a/browser/metro/theme/platform.css
+++ b/browser/metro/theme/platform.css
@@ -635,20 +635,22 @@ tabmodalprompt:not([promptType="promptUs
     min-width: @touch_action_snapped_minwidth@;
   }
 }
 
 /*.meta -------------------------------------------------------------------- */
 
 .meta {
   background-color: @panel_light_color@;
+  /* bug 969354
   background-image: url("chrome://browser/skin/images/firefox-watermark.png");
   background-repeat: no-repeat;
   background-position: center center;
   background-attachment: fixed;
+  */
 }
 
 /* needs to observe the viewstate */
 .meta-section-container {
   padding: 45px 75px 0;
   -moz-box-orient: horizontal;
 }
 
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -555,16 +555,21 @@ menuitem:not([type]):not(.menuitem-toolt
 }
 
 /* Primary toolbar buttons */
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 {
   -moz-appearance: none;
 }
 
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[open="true"],
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:hover:active {
+  padding: 3px;
+}
+
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-icon {
   -moz-margin-end: 0;
   padding: 2px 6px;
   border: 1px solid transparent;
   border-radius: 2px;
   transition-property: background-color, border-color;
--- a/browser/themes/linux/devtools/computedview.css
+++ b/browser/themes/linux/devtools/computedview.css
@@ -36,26 +36,26 @@ body {
 }
 
 .property-view > * {
   display: inline-block;
   vertical-align: middle;
 }
 
 .property-name {
-  width: 50%;
+  /* -12px is so the expander triangle isn't pushed up above the property */
+  width: calc(100% - 12px);
   overflow-x: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
   outline: 0;
 }
 
 .property-value {
-  width: 50%;
-  max-width: 100%;
+  width: 100%;
   overflow-x: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
   background-image: url(arrow-e.png);
   background-repeat: no-repeat;
   background-size: 5px 8px;
   background-position: 2px center;
   padding-left: 10px;
@@ -70,17 +70,18 @@ body {
   padding-left: 8px;
 }
 
 @media (min-width: 400px) {
   .property-name {
     width: 200px;
   }
   .property-value {
-    width: auto;
+    /* -212px is accounting for the 200px property-name and the 12px triangle */
+    width: calc(100% - 212px);
   }
 }
 
 .property-content {
   padding-left: 17px;
 }
 
 /* From skin */
--- a/browser/themes/linux/downloads/indicator.css
+++ b/browser/themes/linux/downloads/indicator.css
@@ -17,16 +17,18 @@
   z-index: 5;
 }
 
 /*** Main indicator icon ***/
 
 #downloads-button[cui-areatype="toolbar"] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
                               0, 198, 18, 180) center no-repeat;
+  min-width: 18px;
+  min-height: 18px;
 }
 
 #downloads-button[cui-areatype="toolbar"][attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: url("chrome://browser/skin/downloads/download-glow.png");
 }
 
 #downloads-button[cui-areatype="menu-panel"][attention] {
   list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel.png");
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -2568,34 +2568,23 @@ toolbarbutton.chevron > .toolbarbutton-m
   /* image preloading hack from shared/tabs.inc.css */
   #TabsToolbar::before {
     background-image:
       url(chrome://browser/skin/tabbrowser/tab-background-end@2x.png),
       url(chrome://browser/skin/tabbrowser/tab-background-middle@2x.png),
       url(chrome://browser/skin/tabbrowser/tab-background-start@2x.png);
   }
 
+  .tabbrowser-tab:hover > .tab-stack > .tab-background:not([selected=true]),
   .tabs-newtab-button:hover {
     background-image: url(chrome://browser/skin/tabbrowser/tab-background-start@2x.png),
                       url(chrome://browser/skin/tabbrowser/tab-background-middle@2x.png),
                       url(chrome://browser/skin/tabbrowser/tab-background-end@2x.png);
   }
 
-  .tabbrowser-tab:hover > .tab-stack > .tab-background > .tab-background-middle:not([selected=true]) {
-    background-image: url(chrome://browser/skin/tabbrowser/tab-background-middle@2x.png);
-  }
-
-  .tabbrowser-tab:hover > .tab-stack > .tab-background > .tab-background-start:not([selected=true]) {
-    background-image: url(chrome://browser/skin/tabbrowser/tab-background-start@2x.png);
-  }
-
-  .tabbrowser-tab:hover > .tab-stack > .tab-background > .tab-background-end:not([selected=true]) {
-    background-image: url(chrome://browser/skin/tabbrowser/tab-background-end@2x.png);
-  }
-
   .tab-background-middle[selected=true] {
     background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle@2x.png),
                       @fgTabTexture@,
                       none;
   }
 
   .tab-background-start[selected=true]:-moz-locale-dir(ltr)::after,
   .tab-background-end[selected=true]:-moz-locale-dir(rtl)::after {
@@ -4120,16 +4109,20 @@ window > chatbox {
 }
 
 #main-window[tabsintitlebar][customize-entered] > #titlebar > #titlebar-content,
 #main-window:not([tabsintitlebar]):not(:-moz-lwtheme) > #titlebar > #titlebar-content {
   margin-top: 11px;
   margin-bottom: 0px;
 }
 
+#main-window:not([tabsintitlebar]):-moz-lwtheme > #titlebar {
+  margin-bottom: 5px;
+}
+
 #main-window[tabsintitlebar]:-moz-lwtheme > #titlebar > #titlebar-content {
   margin-top: 11px;
   margin-bottom: 11px;
 }
 
 #main-window[customize-entered] #tab-view-deck {
   background-image: url("chrome://browser/skin/customizableui/customizeMode-gridTexture.png"),
                     url("chrome://browser/skin/customizableui/background-noise-toolbar.png"),
--- a/browser/themes/osx/devtools/computedview.css
+++ b/browser/themes/osx/devtools/computedview.css
@@ -54,26 +54,26 @@ body {
 }
 
 .property-view > * {
   display: inline-block;
   vertical-align: middle;
 }
 
 .property-name {
-  width: 50%;
+  /* -12px is so the expander triangle isn't pushed up above the property */
+  width: calc(100% - 12px);
   overflow-x: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
   outline: 0;
 }
 
 .property-value {
-  width: 50%;
-  max-width: 100%;
+  width: 100%;
   overflow-x: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
   background-image: url(arrow-e.png);
   background-repeat: no-repeat;
   background-size: 5px 8px;
   background-position: 2px center;
   padding-left: 10px;
@@ -88,17 +88,18 @@ body {
   padding-left: 8px;
 }
 
 @media (min-width: 400px) {
   .property-name {
     width: 200px;
   }
   .property-value {
-    width: auto;
+    /* -212px is accounting for the 200px property-name and the 12px triangle */
+    width: calc(100% - 212px);
   }
 }
 
 .property-content {
   padding-left: 17px;
 }
 
 /* From skin */
--- a/browser/themes/shared/devtools/netmonitor.inc.css
+++ b/browser/themes/shared/devtools/netmonitor.inc.css
@@ -489,25 +489,25 @@ box.requests-menu-status {
 }
 
 #custom-method-value {
   width: 4.5em;
 }
 
 /* Footer */
 
-#requests-menu-footer {
-  border-top: solid 1px hsla(210,5%,5%,.3);
-}
-
 .theme-dark #requests-menu-footer {
+  border-top: 1px solid @table_itemDarkStartBorder@;
+  box-shadow: 0 1px 0 @table_itemDarkEndBorder@ inset;
   background: url(background-noise-toolbar.png), #343c45; /* Toolbars */
 }
 
 .theme-light #requests-menu-footer {
+  border-top: 1px solid @table_itemLightStartBorder@;
+  box-shadow: 0 1px 0 @table_itemLightEndBorder@ inset;
   background: url(background-noise-toolbar.png), #f0f1f2; /* Toolbars */
 }
 
 .requests-menu-footer-button,
 .requests-menu-footer-label {
   min-width: 1em;
   margin: 0;
   border: none;
@@ -525,44 +525,51 @@ box.requests-menu-status {
 }
 
 .requests-menu-footer-spacer {
   min-width: 2px;
 }
 
 .theme-dark .requests-menu-footer-spacer:not(:first-child),
 .theme-dark .requests-menu-footer-button:not(:first-child) {
-  -moz-border-start: 1px solid @table_itemDarkStartBorder@;
-  box-shadow: -1px 0 0 @table_itemDarkEndBorder@;
+  -moz-border-start: 1px solid @table_itemDarkEndBorder@;
+  box-shadow: -1px 0 0 @table_itemDarkStartBorder@;
 }
 
 .theme-light .requests-menu-footer-spacer:not(:first-child),
 .theme-light .requests-menu-footer-button:not(:first-child) {
-  -moz-border-start: 1px solid @table_itemLightStartBorder@;
-  box-shadow: -1px 0 0 @table_itemLightEndBorder@;
+  -moz-border-start: 1px solid @table_itemLightEndBorder@;
+  box-shadow: -1px 0 0 @table_itemLightStartBorder@;
 }
 
 .requests-menu-footer-button {
   -moz-appearance: none;
   background: rgba(0,0,0,0.025);
 }
 
 .requests-menu-footer-button:hover {
-  background: rgba(0,0,0,0.20);
+  background: rgba(0,0,0,0.10);
+}
+
+.theme-dark .requests-menu-footer-button:hover:active {
+  background-color: rgba(29,79,115,0.4); /* Select Highlight Blue at 40% opacity */
 }
 
-.requests-menu-footer-button:hover:active {
-  background: rgba(0,0,0,0.35);
+.theme-light .requests-menu-footer-button:hover:active {
+  background-color: rgba(76,158,217,0.4); /* Select Highlight Blue at 40% opacity */
 }
 
-.requests-menu-footer-button:not(:active)[checked] {
-  background-color: rgba(0,0,0,0.25);
-  background-image: radial-gradient(farthest-side at center top, hsla(200,100%,70%,.7), hsla(200,100%,70%,0.3));
-  background-size: 100% 1px;
-  background-repeat: no-repeat;
+.theme-dark .requests-menu-footer-button:not(:active)[checked] {
+  background-color: rgba(29,79,115,1); /* Select Highlight Blue */
+  color: rgba(245,247,250,1); /* Light foreground text */
+}
+
+.theme-light .requests-menu-footer-button:not(:active)[checked] {
+  background-color: rgba(76,158,217,1); /* Select Highlight Blue */
+  color: rgba(245,247,250,1); /* Light foreground text */
 }
 
 .requests-menu-footer-label {
   padding-top: 3px;
   font-weight: 600;
 }
 
 /* Performance analysis buttons */
--- a/browser/themes/shared/devtools/toolbars.inc.css
+++ b/browser/themes/shared/devtools/toolbars.inc.css
@@ -388,16 +388,20 @@
 .devtools-sidebar-tabs > tabs > tab {
   background-color: transparent;
 }
 
 .theme-dark .devtools-sidebar-tabs > tabs > tab {
   background-image: linear-gradient(transparent, transparent), @smallSeparatorDark@;
 }
 
+.theme-dark .devtools-sidebar-tabs > tabs > tab:hover {
+  background-image: linear-gradient(hsla(206,37%,4%,.2), hsla(206,37%,4%,.2)), @smallSeparatorDark@;
+}
+
 .theme-dark .devtools-sidebar-tabs > tabs > tab:hover:active {
   background-image: linear-gradient(hsla(206,37%,4%,.4), hsla(206,37%,4%,.4)), @smallSeparatorDark@;
 }
 
 .theme-dark .devtools-sidebar-tabs > tabs > tab[selected] + tab {
   background-image: linear-gradient(transparent, transparent), @solidSeparatorDark@;
 }
 
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -219,46 +219,26 @@
 .tab-background-end[selected=true]:-moz-lwtheme::before,
 .tab-background-middle[selected=true]:-moz-lwtheme {
   background-color: transparent;
 }
 
 /* End selected tab */
 
 /* new tab button border and gradient on hover */
+.tabbrowser-tab:hover > .tab-stack > .tab-background:not([selected=true]),
 .tabs-newtab-button:hover {
   background-image: url(chrome://browser/skin/tabbrowser/tab-background-start.png),
                     url(chrome://browser/skin/tabbrowser/tab-background-middle.png),
                     url(chrome://browser/skin/tabbrowser/tab-background-end.png);
   background-position: left bottom, @tabCurveWidth@ bottom, right bottom;
   background-repeat: no-repeat;
   background-size: @tabCurveWidth@ 100%, calc(100% - (2 * @tabCurveWidth@)) 100%, @tabCurveWidth@ 100%;
 }
 
-/* normal tab border and gradient on hover */
-.tabbrowser-tab:hover > .tab-stack > .tab-background > .tab-background-middle:not([selected=true]) {
-  background-image: url(chrome://browser/skin/tabbrowser/tab-background-middle.png);
-  background-repeat: repeat-x;
-  background-size: auto 100%;
-}
-
-.tabbrowser-tab:hover > .tab-stack > .tab-background > .tab-background-start:not([selected=true]),
-.tabbrowser-tab:hover > .tab-stack > .tab-background > .tab-background-end:not([selected=true]) {
-  background-repeat: no-repeat;
-  background-size: 100% 100%;
-}
-
-.tabbrowser-tab:hover > .tab-stack > .tab-background > .tab-background-start:not([selected=true]) {
-  background-image: url(chrome://browser/skin/tabbrowser/tab-background-start.png);
-}
-
-.tabbrowser-tab:hover > .tab-stack > .tab-background > .tab-background-end:not([selected=true]) {
-  background-image: url(chrome://browser/skin/tabbrowser/tab-background-end.png);
-}
-
 /* Tab pointer-events */
 .tabbrowser-tab {
   pointer-events: none;
 }
 
 .tab-background-middle,
 .tabs-newtab-button,
 .tab-close-button {
--- a/browser/themes/windows/devtools/computedview.css
+++ b/browser/themes/windows/devtools/computedview.css
@@ -54,26 +54,26 @@ body {
 }
 
 .property-view > * {
   display: inline-block;
   vertical-align: middle;
 }
 
 .property-name {
-  width: 50%;
+  /* -12px is so the expander triangle isn't pushed up above the property */
+  width: calc(100% - 12px);
   overflow-x: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
   outline: 0;
 }
 
 .property-value {
-  width: 50%;
-  max-width: 100%;
+  width: 100%;
   overflow-x: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
   background-image: url(arrow-e.png);
   background-repeat: no-repeat;
   background-size: 5px 8px;
   background-position: 2px center;
   padding-left: 10px;
@@ -88,17 +88,18 @@ body {
   padding-left: 8px;
 }
 
 @media (min-width: 400px) {
   .property-name {
     width: 200px;
   }
   .property-value {
-    width: auto;
+    /* -212px is accounting for the 200px property-name and the 12px triangle */
+    width: calc(100% - 212px);
   }
 }
 
 .property-content {
   padding-left: 17px;
 }
 
 /* From skin */
--- a/dom/browser-element/mochitest/priority/test_HighPriority.html
+++ b/dom/browser-element/mochitest/priority/test_HighPriority.html
@@ -114,15 +114,16 @@ function runTest() {
 
   document.body.appendChild(iframe);
 }
 
 const priorityChangeGracePeriod = 100;
 addEventListener('testready', function() {
   SpecialPowers.pushPrefEnv(
     {set: [['dom.ipc.processPriorityManager.backgroundGracePeriodMS',
-            priorityChangeGracePeriod]]},
+            priorityChangeGracePeriod],
+           ['dom.wakelock.enabled', true]]},
     runTest);
 });
 
 </script>
 </body>
 </html>
--- a/dom/nfc/MozNDEFRecord.cpp
+++ b/dom/nfc/MozNDEFRecord.cpp
@@ -16,16 +16,17 @@ namespace mozilla {
 namespace dom {
 
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(MozNDEFRecord)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MozNDEFRecord)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+  tmp->DropData();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MozNDEFRecord)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(MozNDEFRecord)
--- a/dom/power/moz.build
+++ b/dom/power/moz.build
@@ -1,16 +1,15 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
-    TEST_DIRS += ['test']
+TEST_DIRS += ['test']
 
 XPIDL_SOURCES += [
     'nsIDOMWakeLockListener.idl',
     'nsIPowerManagerService.idl',
 ]
 
 XPIDL_MODULE = 'dom_power'
 
--- a/dom/power/test/browser_wakelocks.js
+++ b/dom/power/test/browser_wakelocks.js
@@ -217,17 +217,20 @@ let gSteps = [
   },
 ];
 
 function runNextStep() {
   gCurStepIndex++;
   if (gCurStepIndex < gSteps.length) {
     gSteps[gCurStepIndex]();
   } else {
-    SpecialPowers.removePermission("power", kUrlSource);
     finish();
   }
 }
 
 function test() {
-  SpecialPowers.addPermission("power", true, kUrlSource);
-  runNextStep();
+  SpecialPowers.pushPermissions([
+    {type: "power", allow: true, context: kUrlSource}
+  ], function () {
+    SpecialPowers.pushPrefEnv({"set": [["dom.wakelock.enabled", true]]},
+                              runNextStep);
+  });
 }
--- a/dom/power/test/mochitest.ini
+++ b/dom/power/test/mochitest.ini
@@ -1,8 +1,13 @@
 [DEFAULT]
 
 [test_bug957893.html]
 [test_bug957899.html]
+[test_wakelock_not_exposed.html]
+run-if = appname != "b2g"
 [test_power_basics.html]
 [test_power_set_cpusleepallowed.html]
+skip-if = toolkit != "gonk"
 [test_power_set_screen_brightness.html]
+skip-if = toolkit != "gonk"
 [test_power_set_screen_enabled.html]
+skip-if = toolkit != "gonk"
--- a/dom/power/test/test_bug957893.html
+++ b/dom/power/test/test_bug957893.html
@@ -2,20 +2,23 @@
 <html>
 <head>
   <title>Test bug 957893 - Crash in WakeLock</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
  <script type="application/javascript">
- try {
-   var wl = navigator.requestWakeLock('');
-   ok(false, "RequestWakeLock throws an exception!");
- } catch(e) {
-   ok(true, "RequestWakeLock throws an exception!");
- }
-
- info("Still alive!");
-
+  SimpleTest.waitForExplicitFinish();
+  function test() {
+    try {
+      var wl = navigator.requestWakeLock('');
+      ok(false, "RequestWakeLock throws an exception!");
+    } catch(e) {
+      ok(true, "RequestWakeLock throws an exception!");
+    }
+    info("Still alive!");
+    SimpleTest.finish();
+  }
+  SpecialPowers.pushPrefEnv({"set": [["dom.wakelock.enabled", true]]}, test);
  </script>
 </body>
 </html>
--- a/dom/power/test/test_bug957899.html
+++ b/dom/power/test/test_bug957899.html
@@ -2,14 +2,19 @@
 <html>
 <head>
   <title>Test bug 957899 - Crash in WakeLock</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
  <script type="application/javascript">
- var wl = navigator.requestWakeLock('a');
- ok(wl, "WakeLock created!");
- ok(!(wl instanceof XPathEvaluator), "Crashing?");
+  SimpleTest.waitForExplicitFinish();
+  function test() {
+    var wl = navigator.requestWakeLock('a');
+    ok(wl, "WakeLock created!");
+    ok(!(wl instanceof XPathEvaluator), "Crashing?");
+    SimpleTest.finish();
+  }
+  SpecialPowers.pushPrefEnv({"set": [["dom.wakelock.enabled", true]]}, test);
  </script>
 </body>
 </html>
--- a/dom/power/test/test_power_basics.html
+++ b/dom/power/test/test_power_basics.html
@@ -3,25 +3,29 @@
 <head>
   <title>Test for Power API</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 <script type="application/javascript">
 
 /** Test for Power API **/
 
-ok('mozPower' in navigator, "navigator.mozPower should exist");
-
 /** Test permission **/
 // In b2g, addPermission 'power' is only working after a document reload
 // See bug 802312
 
 SimpleTest.waitForExplicitFinish();
 
 function startTest() {
+  SpecialPowers.pushPermissions([
+    {type: "power", allow: true, context: window.frames[0].document}
+  ], doTest1);
+}
+
+function doTest1() {
   window.frames[0].frameElement.setAttribute('onload', 'doTest2()');
   power = window.frames[0].navigator.mozPower;
   ok(power, "Should be able to access power manager with permission.");
 
   SpecialPowers.removePermission("power", window.frames[0].document);
   window.frames[0].location.reload();
 }
 
new file mode 100644
--- /dev/null
+++ b/dom/power/test/test_wakelock_not_exposed.html
@@ -0,0 +1,17 @@
+<!--
+    https://bugzilla.mozilla.org/show_bug.cgi?id=963366
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test navigator.requestWakeLock is not exposed to non-B2G platform</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script type="application/javascript">
+   ok(navigator.requestWakeLock === undefined,
+      "navigator.requestWakeLock is not exposed to non-B2G platform");
+ </script>
+</body>
+</html>
--- a/dom/src/storage/DOMStorage.cpp
+++ b/dom/src/storage/DOMStorage.cpp
@@ -156,33 +156,34 @@ DOMStorage::Clear()
   return NS_OK;
 }
 
 namespace {
 
 class StorageNotifierRunnable : public nsRunnable
 {
 public:
-  StorageNotifierRunnable(nsISupports* aSubject)
-    : mSubject(aSubject)
+  StorageNotifierRunnable(nsISupports* aSubject, const char16_t* aType)
+    : mSubject(aSubject), mType(aType)
   { }
 
   NS_DECL_NSIRUNNABLE
 
 private:
   nsCOMPtr<nsISupports> mSubject;
+  const char16_t* mType;
 };
 
 NS_IMETHODIMP
 StorageNotifierRunnable::Run()
 {
   nsCOMPtr<nsIObserverService> observerService =
     mozilla::services::GetObserverService();
   if (observerService) {
-    observerService->NotifyObservers(mSubject, "dom-storage2-changed", nullptr);
+    observerService->NotifyObservers(mSubject, "dom-storage2-changed", mType);
   }
   return NS_OK;
 }
 
 } // anonymous namespace
 
 void
 DOMStorage::BroadcastChangeNotification(const nsSubstring& aKey,
@@ -202,17 +203,21 @@ DOMStorage::BroadcastChangeNotification(
                                         aOldValue,
                                         aNewValue,
                                         mDocumentURI,
                                         static_cast<nsIDOMStorage*>(this));
   if (NS_FAILED(rv)) {
     return;
   }
 
-  nsRefPtr<StorageNotifierRunnable> r = new StorageNotifierRunnable(event);
+  nsRefPtr<StorageNotifierRunnable> r =
+    new StorageNotifierRunnable(event,
+                                GetType() == LocalStorage
+                                  ? MOZ_UTF16("localStorage")
+                                  : MOZ_UTF16("sessionStorage"));
   NS_DispatchToMainThread(r);
 }
 
 static const uint32_t ASK_BEFORE_ACCEPT = 1;
 static const uint32_t ACCEPT_SESSION = 2;
 static const uint32_t BEHAVIOR_REJECT = 2;
 
 static const char kPermissionType[] = "cookie";
--- a/dom/system/OSFileConstants.cpp
+++ b/dom/system/OSFileConstants.cpp
@@ -8,19 +8,23 @@
 #include "errno.h"
 
 #include "prsystem.h"
 
 #if defined(XP_UNIX)
 #include "unistd.h"
 #include "dirent.h"
 #include "sys/stat.h"
-#if !defined(ANDROID)
+#if defined(ANDROID)
+#include <sys/vfs.h>
+#define statvfs statfs
+#else
+#include "sys/statvfs.h"
 #include <spawn.h>
-#endif // !defined(ANDROID)
+#endif // defined(ANDROID)
 #endif // defined(XP_UNIX)
 
 #if defined(XP_LINUX)
 #include <linux/fadvise.h>
 #endif // defined(XP_LINUX)
 
 #if defined(XP_MACOSX)
 #include "copyfile.h"
@@ -521,16 +525,19 @@ static const dom::ConstantSpec gLibcProp
   { "OSFILE_SIZEOF_GID_T", INT_TO_JSVAL(sizeof (gid_t)) },
 
   // The size of |uid_t|.
   { "OSFILE_SIZEOF_UID_T", INT_TO_JSVAL(sizeof (uid_t)) },
 
   // The size of |time_t|.
   { "OSFILE_SIZEOF_TIME_T", INT_TO_JSVAL(sizeof (time_t)) },
 
+  // The size of |fsblkcnt_t|.
+  { "OSFILE_SIZEOF_FSBLKCNT_T", INT_TO_JSVAL(sizeof (fsblkcnt_t)) },
+
 #if !defined(ANDROID)
   // The size of |posix_spawn_file_actions_t|.
   { "OSFILE_SIZEOF_POSIX_SPAWN_FILE_ACTIONS_T", INT_TO_JSVAL(sizeof (posix_spawn_file_actions_t)) },
 #endif // !defined(ANDROID)
 
   // Defining |dirent|.
   // Size
   { "OSFILE_SIZEOF_DIRENT", INT_TO_JSVAL(sizeof (dirent)) },
@@ -580,16 +587,23 @@ static const dom::ConstantSpec gLibcProp
   { "OSFILE_OFFSETOF_STAT_ST_CTIME", INT_TO_JSVAL(offsetof (struct stat, st_ctime)) },
 #endif // defined(HAVE_ST_ATIME)
 
   // Several OSes have a birthtime field. For the moment, supporting only Darwin.
 #if defined(_DARWIN_FEATURE_64_BIT_INODE)
   { "OSFILE_OFFSETOF_STAT_ST_BIRTHTIME", INT_TO_JSVAL(offsetof (struct stat, st_birthtime)) },
 #endif // defined(_DARWIN_FEATURE_64_BIT_INODE)
 
+  // Defining |statvfs|
+
+  { "OSFILE_SIZEOF_STATVFS", INT_TO_JSVAL(sizeof (struct statvfs)) },
+
+  { "OSFILE_OFFSETOF_STATVFS_F_BSIZE", INT_TO_JSVAL(offsetof (struct statvfs, f_bsize)) },
+  { "OSFILE_OFFSETOF_STATVFS_F_BAVAIL", INT_TO_JSVAL(offsetof (struct statvfs, f_bavail)) },
+
 #endif // defined(XP_UNIX)
 
 
 
   // System configuration
 
   // Under MacOSX, to avoid using deprecated functions that do not
   // match the constants we define in this object (including
--- a/dom/system/gonk/Nfc.js
+++ b/dom/system/gonk/Nfc.js
@@ -397,17 +397,16 @@ function Nfc() {
   }
 
   Services.obs.addObserver(this, NFC.TOPIC_MOZSETTINGS_CHANGED, false);
   Services.obs.addObserver(this, NFC.TOPIC_XPCOM_SHUTDOWN, false);
   Services.obs.addObserver(this, NFC.TOPIC_HARDWARE_STATE, false);
 
   gMessageManager.init(this);
   let lock = gSettingsService.createLock();
-  lock.get(NFC.SETTING_NFC_POWER_LEVEL, this);
   lock.get(NFC.SETTING_NFC_ENABLED, this);
   // Maps sessionId (that are generated from nfcd) with a unique guid : 'SessionToken'
   this.sessionTokenMap = {};
 
   gSystemWorkerManager.registerNfcWorker(this.worker);
 }
 
 Nfc.prototype = {
@@ -518,18 +517,16 @@ Nfc.prototype = {
       default:
         throw new Error("Don't know about this message type: " + message.type);
     }
   },
 
   // nsINfcWorker
   worker: null,
 
-  powerLevel: NFC.NFC_POWER_LEVEL_DISABLED,
-
   sessionTokenMap: null,
 
   /**
    * Process a message from the content process.
    */
   receiveMessage: function receiveMessage(message) {
     debug("Received '" + JSON.stringify(message) + "' message from content process");
 
@@ -618,25 +615,16 @@ Nfc.prototype = {
    * nsISettingsServiceCallback
    */
 
   handle: function handle(aName, aResult) {
     switch(aName) {
       case NFC.SETTING_NFC_ENABLED:
         debug("'nfc.enabled' is now " + aResult);
         this._enabled = aResult;
-        // General power setting
-        let powerLevel = this._enabled ? NFC.NFC_POWER_LEVEL_ENABLED :
-                                         NFC.NFC_POWER_LEVEL_DISABLED;
-        // Only if the value changes, set the power config and persist
-        if (powerLevel !== this.powerLevel) {
-          debug("New Power Level " + powerLevel);
-          this.setConfig({powerLevel: powerLevel});
-          this.powerLevel = powerLevel;
-        }
         break;
     }
   },
 
   /**
    * nsIObserver
    */
 
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -2092,20 +2092,16 @@ RadioInterface.prototype = {
         gTelephonyProvider.notifyCallDisconnected(this.clientId, message.call);
         break;
       case "conferenceCallStateChanged":
         gTelephonyProvider.notifyConferenceCallStateChanged(message.state);
         break;
       case "cdmaCallWaiting":
         gTelephonyProvider.notifyCdmaCallWaiting(this.clientId, message.number);
         break;
-      case "callError":
-        gTelephonyProvider.notifyCallError(this.clientId, message.callIndex,
-                                           message.errorMsg);
-        break;
       case "suppSvcNotification":
         gTelephonyProvider.notifySupplementaryService(this.clientId,
                                                       message.callIndex,
                                                       message.notification);
         break;
       case "conferenceError":
         gTelephonyProvider.notifyConferenceError(message.errorName,
                                                  message.errorMsg);
--- a/dom/system/gonk/nfc_consts.js
+++ b/dom/system/gonk/nfc_consts.js
@@ -60,15 +60,14 @@ this.NFC_POWER_LEVEL_UNKNOWN        = -1
 this.NFC_POWER_LEVEL_DISABLED       = 0;
 this.NFC_POWER_LEVEL_LOW            = 1;
 this.NFC_POWER_LEVEL_ENABLED        = 2;
 
 this.TOPIC_MOZSETTINGS_CHANGED      = "mozsettings-changed";
 this.TOPIC_XPCOM_SHUTDOWN           = "xpcom-shutdown";
 this.TOPIC_HARDWARE_STATE           = "nfc-hardware-state-change";
 this.SETTING_NFC_ENABLED            = "nfc.enabled";
-this.SETTING_NFC_POWER_LEVEL        = "nfc.powerlevel";
 
 this.NFC_PEER_EVENT_READY = 0x01;
 this.NFC_PEER_EVENT_LOST  = 0x02;
 
 // Allow this file to be imported via Components.utils.import().
 this.EXPORTED_SYMBOLS = Object.keys(this);
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -1339,19 +1339,21 @@ let RIL = {
    * @param number
    *        String containing the number to dial.
    * @param clirMode
    *        Integer for showing/hidding the caller Id to the called party.
    * @param uusInfo
    *        Integer doing something XXX TODO
    */
   dial: function(options) {
-    let onerror = (function onerror(errorMsg) {
-      this._sendCallError(-1, errorMsg);
-    }).bind(this);
+    let onerror = (function onerror(options, errorMsg) {
+      options.success = false;
+      options.errorMsg = errorMsg;
+      this.sendChromeMessage(options);
+    }).bind(this, options);
 
     if (this._isEmergencyNumber(options.number)) {
       this.dialEmergencyNumber(options, onerror);
     } else {
       if (!this._isCdma) {
         // TODO: Both dial() and sendMMI() functions should be unified at some
         // point in the future. In the mean time we handle temporary CLIR MMI
         // commands through the dial() function. Please see bug 889737.
@@ -1405,17 +1407,17 @@ let RIL = {
       this.setRadioEnabled({enabled: true});
       return;
     }
 
     this.sendDialRequest(options);
   },
 
   sendDialRequest: function(options) {
-    Buf.newParcel(options.request);
+    Buf.newParcel(options.request, options);
     Buf.writeString(options.number);
     Buf.writeInt32(options.clirMode || 0);
     Buf.writeInt32(options.uusInfo || 0);
     // TODO Why do we need this extra 0? It was put it in to make this
     // match the format of the binary message.
     Buf.writeInt32(0);
     Buf.sendParcel();
   },
@@ -1921,18 +1923,18 @@ let RIL = {
                                    RIL_REQUEST_GPRS_DETACH;
     this._attachDataRegistration = options.attach;
     Buf.simpleRequest(request);
   },
 
   /**
    * Get failure casue code for the most recently failed PDP context.
    */
-  getFailCauseCode: function(options) {
-    Buf.simpleRequest(REQUEST_LAST_CALL_FAIL_CAUSE, options);
+  getFailCauseCode: function(callback) {
+    Buf.simpleRequest(REQUEST_LAST_CALL_FAIL_CAUSE, {callback: callback});
   },
 
   /**
    * Helper to parse MMI/USSD string. TS.22.030 Figure 3.5.3.2.
    */
   _parseMMI: function(mmiString) {
     if (!mmiString || !mmiString.length) {
       return null;
@@ -3477,17 +3479,20 @@ let RIL = {
           delete this.currentConference.participants[currentCall.callIndex];
           delete this.currentCalls[currentCall.callIndex];
           // We don't query the fail cause here as it triggers another asynchrouns
           // request that leads to a problem of updating all conferece participants
           // in one task.
           this._handleDisconnectedCall(currentCall);
         } else {
           delete this.currentCalls[currentCall.callIndex];
-          this.getFailCauseCode(currentCall);
+          this.getFailCauseCode((function(call, failCause) {
+            call.failCause = failCause;
+            this._handleDisconnectedCall(call);
+          }).bind(this, currentCall));
         }
         continue;
       }
 
       // Call is still valid.
       if (newCall.state == currentCall.state &&
           newCall.isMpty == currentCall.isMpty) {
         continue;
@@ -3659,22 +3664,16 @@ let RIL = {
   },
 
   _handleDisconnectedCall: function(disconnectedCall) {
     let message = {rilMessageType: "callDisconnected",
                    call: disconnectedCall};
     this.sendChromeMessage(message);
   },
 
-  _sendCallError: function(callIndex, errorMsg) {
-    this.sendChromeMessage({rilMessageType: "callError",
-                            callIndex: callIndex,
-                            errorMsg: errorMsg});
-  },
-
   _sendDataCallError: function(message, errorCode) {
     // Should not include token for unsolicited response.
     delete message.rilMessageToken;
     message.rilMessageType = "datacallerror";
     if (errorCode == ERROR_GENERIC_FAILURE) {
       message.errorMsg = RIL_ERROR_TO_GECKO_ERROR[errorCode];
     } else {
       message.errorMsg = RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[errorCode];
@@ -5098,20 +5097,24 @@ RIL[REQUEST_GET_CURRENT_CALLS] = functio
       };
     }
 
     calls[call.callIndex] = call;
   }
   this._processCalls(calls);
 };
 RIL[REQUEST_DIAL] = function REQUEST_DIAL(length, options) {
-  if (options.rilRequestError) {
-    // The connection is not established yet.
-    options.callIndex = -1;
-    this.getFailCauseCode(options);
+  options.success = (options.rilRequestError === 0);
+  if (options.success) {
+    this.sendChromeMessage(options);
+  } else {
+    this.getFailCauseCode((function(options, failCause) {
+      options.errorMsg = failCause;
+      this.sendChromeMessage(options);
+    }).bind(this, options));
   }
 };
 RIL[REQUEST_GET_IMSI] = function REQUEST_GET_IMSI(length, options) {
   if (options.rilRequestError) {
     return;
   }
 
   this.iccInfoPrivate.imsi = Buf.readString();
@@ -5168,30 +5171,21 @@ RIL[REQUEST_CONFERENCE] = function REQUE
                errorName: "addError",
                errorMsg: RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]};
     this.sendChromeMessage(options);
     return;
   }
 };
 RIL[REQUEST_UDUB] = null;
 RIL[REQUEST_LAST_CALL_FAIL_CAUSE] = function REQUEST_LAST_CALL_FAIL_CAUSE(length, options) {
-  let num = 0;
-  if (length) {
-    num = Buf.readInt32();
-  }
-  if (!num) {
-    // No response of REQUEST_LAST_CALL_FAIL_CAUSE. Change the call state into
-    // 'disconnected' directly.
-    this._handleDisconnectedCall(options);
-    return;
-  }
-
-  let failCause = Buf.readInt32();
-  options.failCause = RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[failCause];
-  this._handleDisconnectedCall(options);
+  let num = length ? Buf.readInt32() : 0;
+  let failCause = num ? RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[Buf.readInt32()] : null;
+  if (options.callback) {
+    options.callback(failCause);
+  }
 };
 RIL[REQUEST_SIGNAL_STRENGTH] = function REQUEST_SIGNAL_STRENGTH(length, options) {
   this._receivedNetworkInfo(NETWORK_INFO_SIGNAL);
 
   if (options.rilRequestError) {
     return;
   }
 
--- a/dom/telephony/gonk/TelephonyProvider.js
+++ b/dom/telephony/gonk/TelephonyProvider.js
@@ -317,33 +317,16 @@ TelephonyProvider.prototype = {
       default:
         if (DEBUG) {
           debug("Unknown rilSuppSvcNotification: " + aNotification);
         }
         return;
     }
   },
 
-  _validateNumber: function(aNumber) {
-    // note: isPlainPhoneNumber also accepts USSD and SS numbers
-    if (gPhoneNumberUtils.isPlainPhoneNumber(aNumber)) {
-      return true;
-    }
-
-    let errorMsg = RIL.RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[RIL.CALL_FAIL_UNOBTAINABLE_NUMBER];
-    let currentThread = Services.tm.currentThread;
-    currentThread.dispatch(this.notifyCallError.bind(this, -1, errorMsg),
-                           Ci.nsIThread.DISPATCH_NORMAL);
-    if (DEBUG) {
-      debug("Number '" + aNumber + "' doesn't seem to be a viable number. Drop.");
-    }
-
-    return false;
-  },
-
   _updateDebugFlag: function() {
     try {
       DEBUG = RIL.DEBUG_RIL ||
               Services.prefs.getBoolPref(kPrefRilDebuggingEnabled);
     } catch (e) {}
   },
 
   _getDefaultServiceId: function() {
@@ -414,27 +397,42 @@ TelephonyProvider.prototype = {
     }
     promise.then(function() {
       aListener.enumerateCallStateComplete();
     });
   },
 
   dial: function(aClientId, aNumber, aIsEmergency) {
     if (DEBUG) debug("Dialing " + (aIsEmergency ? "emergency " : "") + aNumber);
+
     // we don't try to be too clever here, as the phone is probably in the
     // locked state. Let's just check if it's a number without normalizing
     if (!aIsEmergency) {
       aNumber = gPhoneNumberUtils.normalize(aNumber);
     }
-    if (this._validateNumber(aNumber)) {
-      this._getClient(aClientId).sendWorkerMessage("dial", {
-        number: aNumber,
-        isDialEmergency: aIsEmergency
-      });
+
+    if (!gPhoneNumberUtils.isPlainPhoneNumber(aNumber)) {
+      // Note: isPlainPhoneNumber also accepts USSD and SS numbers
+      if (DEBUG) debug("Number '" + aNumber + "' is not viable. Drop.");
+      let errorMsg = RIL.RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[RIL.CALL_FAIL_UNOBTAINABLE_NUMBER];
+      Services.tm.currentThread.dispatch(
+        this.notifyCallError.bind(this, aClientId, -1, errorMsg),
+        Ci.nsIThread.DISPATCH_NORMAL);
+      return;
     }
+
+    this._getClient(aClientId).sendWorkerMessage("dial", {
+      number: aNumber,
+      isDialEmergency: aIsEmergency
+    }, (function(clientId, response) {
+      if (!response.success) {
+        this.notifyCallError(clientId, -1, response.errorMsg);
+      }
+      return false;
+    }).bind(this, aClientId));
   },
 
   hangUp: function(aClientId, aCallIndex) {
     this._getClient(aClientId).sendWorkerMessage("hangUp", { callIndex: aCallIndex });
   },
 
   startTone: function(aClientId, aDtmfChar) {
     this._getClient(aClientId).sendWorkerMessage("startTone", { dtmfChar: aDtmfChar });
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -672,17 +672,17 @@ var interfaceNamesInGlobalScope =
     {name: "MozStkCommandEvent", b2g: true, pref: "dom.icc.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozTimeManager", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozVoicemail", b2g: true, pref: "dom.voicemail.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozVoicemailEvent", b2g: true, pref: "dom.voicemail.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "MozWakeLock",
+    {name: "MozWakeLock", b2g: true, pref: "dom.wakelock.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWifiConnectionInfoEvent", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWifiStatusChangeEvent", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWifiP2pGroupOwner", b2g: true, permission: "wifi-manage"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWifiP2pManager", b2g: true, permission: "wifi-manage"},
--- a/dom/tests/mochitest/storageevent/mochitest.ini
+++ b/dom/tests/mochitest/storageevent/mochitest.ini
@@ -7,10 +7,11 @@ support-files =
   frameSessionStorageMasterNotEqual.html
   frameSessionStorageSlaveEqual.html
   frameSessionStorageSlaveNotEqual.html
   interOriginFrame.js
   interOriginTest2.js
 
 [test_storageLocalStorageEventCheckNoPropagation.html]
 [test_storageLocalStorageEventCheckPropagation.html]
+[test_storageNotifications.html]
 [test_storageSessionStorageEventCheckNoPropagation.html]
 [test_storageSessionStorageEventCheckPropagation.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/storageevent/test_storageNotifications.html
@@ -0,0 +1,127 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>sessionStorage basic 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="application/javascript;version=1.7">
+
+var expectedTypes = [
+  "localStorage",
+  "localStorage",
+  "sessionStorage",
+  "localStorage",
+  "sessionStorage",
+  "sessionStorage",
+  "localStorage",
+  "sessionStorage",
+  "localStorage",
+  "sessionStorage",
+  "localStorage",
+  "sessionStorage",
+  "sessionStorage",
+  "localStorage",
+  "sessionStorage",
+  "localStorage",
+];
+
+var tests = Tests();
+function setup() {
+  sessionStorage.clear();
+  SimpleTest.executeSoon(function() {
+    tests.next();
+  });
+}
+
+function Tests()
+{
+  // Initially check the both storages are empty
+  is(sessionStorage.length, 0, "Session storage is empty [1]");
+  is(localStorage.length, 0, "Local storage is empty [1]");
+
+  var onStorageChanged = {
+    observe: function(subject, topic, type) {
+      if (topic == "dom-storage2-changed") {
+        ok(expectedTypes.length > 0, "Not more then expected events encountered");
+        is(type, expectedTypes.shift(), "Expected type of the storage notificaiton");
+        tests.next();
+      }
+    }
+  }
+
+  // Listen for dom-storage2-changed notification
+  SpecialPowers.Services.obs.addObserver(onStorageChanged,
+    "dom-storage2-changed", false);
+
+  // add an empty-value key
+  localStorage.setItem("empty", "");
+  yield undefined;
+
+  localStorage.setItem("empty", "value-1");
+  yield undefined;
+
+  sessionStorage.setItem("empty", "");
+  yield undefined;
+
+  localStorage.removeItem("empty");
+  yield undefined;
+
+  sessionStorage.setItem("empty", "value-1");
+  yield undefined;
+
+  sessionStorage.removeItem("empty");
+  yield undefined;
+
+  localStorage.setItem("key1", "value-1");
+  yield undefined;
+
+  sessionStorage.setItem("key2", "value-2");
+  yield undefined;
+
+  localStorage.setItem("key1", "value-1-2");
+  yield undefined;
+
+  sessionStorage.setItem("key2", "value-2-2");
+  yield undefined;
+
+  localStorage.setItem("key3", "value-3");
+  yield undefined;
+
+  sessionStorage.setItem("key4", "value-4");
+  yield undefined;
+
+  sessionStorage.removeItem("key4");
+  yield undefined;
+
+  localStorage.setItem("key4", "value-4");
+  yield undefined;
+
+  sessionStorage.clear();
+  yield undefined;
+
+  localStorage.clear();
+  yield undefined;
+
+  SimpleTest.executeSoon(function () {
+    SpecialPowers.Services.obs.removeObserver(onStorageChanged,
+      "dom-storage2-changed", false);
+    is(expectedTypes.length, 0, "received the correct number of events");
+
+    sessionStorage.clear();
+    localStorage.clear();
+    tests = null;
+    SimpleTest.finish();
+  });
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+
+</head>
+
+<body onload="setup();">
+
+</body>
+</html>
--- a/dom/webidl/MozWakeLock.webidl
+++ b/dom/webidl/MozWakeLock.webidl
@@ -1,15 +1,15 @@
 /* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-[Func="Navigator::HasWakeLockSupport"]
+[Pref="dom.wakelock.enabled", Func="Navigator::HasWakeLockSupport"]
 interface MozWakeLock
 {
     readonly attribute DOMString topic;
 
     /**
      * Release the wake lock.
      * @throw NS_ERROR_DOM_INVALID_STATE_ERR if already unlocked.
      */
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -198,17 +198,17 @@ partial interface Navigator {
    * all locks on the topic have been released.
    *
    * The returned MozWakeLock object is a token of the lock.  You can
    * unlock the lock via the object's |unlock| method.  The lock is released
    * automatically when its associated window is unloaded.
    *
    * @param aTopic resource name
    */
-  [Throws, Func="Navigator::HasWakeLockSupport"]
+  [Throws, Pref="dom.wakelock.enabled", Func="Navigator::HasWakeLockSupport"]
   MozWakeLock requestWakeLock(DOMString aTopic);
 };
 
 // nsIDOMNavigatorDeviceStorage
 partial interface Navigator {
   [Throws, Pref="device.storage.enabled"]
   DeviceStorage? getDeviceStorage(DOMString type);
   [Throws, Pref="device.storage.enabled"]
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -812,17 +812,17 @@ pref("browser.snippets.updateInterval", 
 
 // URL used to check for user's country code
 pref("browser.snippets.geoUrl", "https://geo.mozilla.org/country.json");
 
 // URL used to ping metrics with stats about which snippets have been shown
 pref("browser.snippets.statsUrl", "https://snippets-stats.mozilla.org/mobile");
 
 // These prefs require a restart to take effect.
-pref("browser.snippets.enabled", true);
+pref("browser.snippets.enabled", false);
 pref("browser.snippets.syncPromo.enabled", false);
 
 #ifdef MOZ_ANDROID_SYNTHAPKS
 // The URL of the APK factory from which we obtain APKs for webapps.
 // This currently points to the development server.
 pref("browser.webapps.apkFactoryUrl", "http://dapk.net/application.apk");
 
 // How frequently to check for webapp updates, in seconds (86400 is daily).
--- a/mobile/android/base/db/BrowserContract.java
+++ b/mobile/android/base/db/BrowserContract.java
@@ -96,37 +96,45 @@ public class BrowserContract {
         public static final String GUID = "guid";
         public static final String TIME_DELETED = "timeDeleted";
     }
 
     @RobocopTarget
     public static final class Favicons implements CommonColumns, DateSyncColumns {
         private Favicons() {}
 
+        public static final String TABLE_NAME = "favicons";
+
         public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "favicons");
 
         public static final String URL = "url";
         public static final String DATA = "data";
         public static final String PAGE_URL = "page_url";
     }
 
     @RobocopTarget
     public static final class Thumbnails implements CommonColumns {
         private Thumbnails() {}
 
+        public static final String TABLE_NAME = "thumbnails";
+
         public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "thumbnails");
 
         public static final String URL = "url";
         public static final String DATA = "data";
     }
 
     @RobocopTarget
     public static final class Bookmarks implements CommonColumns, URLColumns, FaviconColumns, SyncColumns {
         private Bookmarks() {}
 
+        public static final String TABLE_NAME = "bookmarks";
+
+        public static final String VIEW_WITH_FAVICONS = "bookmarks_with_favicons";
+
         public static final int FIXED_ROOT_ID = 0;
         public static final int FAKE_DESKTOP_FOLDER_ID = -1;
         public static final int FIXED_READING_LIST_ID = -2;
         public static final int FIXED_PINNED_LIST_ID = -3;
 
         public static final String MOBILE_FOLDER_GUID = "mobile";
         public static final String PLACES_FOLDER_GUID = "places";
         public static final String MENU_FOLDER_GUID = "menu";
@@ -157,26 +165,36 @@ public class BrowserContract {
         public static final String TAGS = "tags";
         public static final String DESCRIPTION = "description";
         public static final String KEYWORD = "keyword";
     }
 
     @RobocopTarget
     public static final class History implements CommonColumns, URLColumns, HistoryColumns, FaviconColumns, SyncColumns {
         private History() {}
+
+        public static final String TABLE_NAME = "history";
+
+        public static final String VIEW_WITH_FAVICONS = "history_with_favicons";
+
         public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "history");
         public static final Uri CONTENT_OLD_URI = Uri.withAppendedPath(AUTHORITY_URI, "history/old");
         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/browser-history";
         public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/browser-history";
     }
 
     // Combined bookmarks and history
     @RobocopTarget
     public static final class Combined implements CommonColumns, URLColumns, HistoryColumns, FaviconColumns  {
         private Combined() {}
+
+        public static final String VIEW_NAME = "combined";
+
+        public static final String VIEW_WITH_FAVICONS = "combined_with_favicons";
+
         public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "combined");
 
         public static final int DISPLAY_NORMAL = 0;
         public static final int DISPLAY_READER = 1;
 
         public static final String BOOKMARK_ID = "bookmark_id";
         public static final String HISTORY_ID = "history_id";
         public static final String DISPLAY = "display";
@@ -300,9 +318,52 @@ public class BrowserContract {
 
         public static final String DATASET_ID = "dataset_id";
         public static final String URL = "url";
         public static final String TITLE = "title";
         public static final String DESCRIPTION = "description";
         public static final String IMAGE_URL = "image_url";
         public static final String CREATED = "created";
     }
+
+    /*
+     * Contains names and schema definitions for tables and views
+     * no longer being used by current ContentProviders. These values are used
+     * to make incremental updates to the schema during a database upgrade. Will be
+     * removed with bug 947018.
+     */
+    static final class Obsolete {
+        public static final String TABLE_IMAGES = "images";
+        public static final String VIEW_BOOKMARKS_WITH_IMAGES = "bookmarks_with_images";
+        public static final String VIEW_HISTORY_WITH_IMAGES = "history_with_images";
+        public static final String VIEW_COMBINED_WITH_IMAGES = "combined_with_images";
+
+        public static final class Images implements CommonColumns, SyncColumns {
+            private Images() {}
+
+            public static final String URL = "url_key";
+            public static final String FAVICON_URL = "favicon_url";
+            public static final String FAVICON = "favicon";
+            public static final String THUMBNAIL = "thumbnail";
+            public static final String _ID = "_id";
+            public static final String GUID = "guid";
+            public static final String DATE_CREATED = "created";
+            public static final String DATE_MODIFIED = "modified";
+            public static final String IS_DELETED = "deleted";
+        }
+
+        public static final class Combined {
+            private Combined() {}
+
+            public static final String THUMBNAIL = "thumbnail";
+        }
+
+        static final String TABLE_BOOKMARKS_JOIN_IMAGES = Bookmarks.TABLE_NAME + " LEFT OUTER JOIN " +
+                Obsolete.TABLE_IMAGES + " ON " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.URL) + " = " +
+                DBUtils.qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL);
+
+        static final String TABLE_HISTORY_JOIN_IMAGES = History.TABLE_NAME + " LEFT OUTER JOIN " +
+                Obsolete.TABLE_IMAGES + " ON " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, History.URL) + " = " +
+                DBUtils.qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL);
+
+        static final String FAVICON_DB = "favicon_urls.db";
+    }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/db/BrowserDatabaseHelper.java
@@ -0,0 +1,1748 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.db;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.Distribution;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.db.BrowserContract.Bookmarks;
+import org.mozilla.gecko.db.BrowserContract.Combined;
+import org.mozilla.gecko.db.BrowserContract.FaviconColumns;
+import org.mozilla.gecko.db.BrowserContract.Favicons;
+import org.mozilla.gecko.db.BrowserContract.History;
+import org.mozilla.gecko.db.BrowserContract.Obsolete;
+import org.mozilla.gecko.db.BrowserContract.Thumbnails;
+import org.mozilla.gecko.gfx.BitmapUtils;
+import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.util.GeckoJarReader;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
+
+
+final class BrowserDatabaseHelper extends SQLiteOpenHelper {
+
+    private static final String LOGTAG = "GeckoBrowserDBHelper";
+    public static final int DATABASE_VERSION = 17;
+    public static final String DATABASE_NAME = "browser.db";
+
+    final protected Context mContext;
+
+    static final String TABLE_BOOKMARKS = Bookmarks.TABLE_NAME;
+    static final String TABLE_HISTORY = History.TABLE_NAME;
+    static final String TABLE_FAVICONS = Favicons.TABLE_NAME;
+    static final String TABLE_THUMBNAILS = Thumbnails.TABLE_NAME;
+
+    static final String VIEW_COMBINED = Combined.VIEW_NAME;
+    static final String VIEW_BOOKMARKS_WITH_FAVICONS = Bookmarks.VIEW_WITH_FAVICONS;
+    static final String VIEW_HISTORY_WITH_FAVICONS = History.VIEW_WITH_FAVICONS;
+    static final String VIEW_COMBINED_WITH_FAVICONS = Combined.VIEW_WITH_FAVICONS;
+
+    static final String TABLE_BOOKMARKS_JOIN_FAVICONS = TABLE_BOOKMARKS + " LEFT OUTER JOIN " +
+            TABLE_FAVICONS + " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.FAVICON_ID) + " = " +
+            qualifyColumn(TABLE_FAVICONS, Favicons._ID);
+
+    static final String TABLE_HISTORY_JOIN_FAVICONS = TABLE_HISTORY + " LEFT OUTER JOIN " +
+            TABLE_FAVICONS + " ON " + qualifyColumn(TABLE_HISTORY, History.FAVICON_ID) + " = " +
+            qualifyColumn(TABLE_FAVICONS, Favicons._ID);
+
+    static final String TABLE_BOOKMARKS_TMP = TABLE_BOOKMARKS + "_tmp";
+    static final String TABLE_HISTORY_TMP = TABLE_HISTORY + "_tmp";
+    static final String TABLE_IMAGES_TMP = Obsolete.TABLE_IMAGES + "_tmp";
+
+    private static final String[] mobileIdColumns = new String[] { Bookmarks._ID };
+    private static final String[] mobileIdSelectionArgs = new String[] { Bookmarks.MOBILE_FOLDER_GUID };
+
+    public BrowserDatabaseHelper(Context context, String databasePath) {
+        super(context, databasePath, null, DATABASE_VERSION);
+        mContext = context;
+    }
+
+    private void createBookmarksTable(SQLiteDatabase db) {
+        debug("Creating " + TABLE_BOOKMARKS + " table");
+
+        // Android versions older than Froyo ship with an sqlite
+        // that doesn't support foreign keys.
+        String foreignKeyOnParent = null;
+        if (Build.VERSION.SDK_INT >= 8) {
+            foreignKeyOnParent = ", FOREIGN KEY (" + Bookmarks.PARENT +
+                ") REFERENCES " + TABLE_BOOKMARKS + "(" + Bookmarks._ID + ")";
+        }
+
+        db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" +
+                Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                Bookmarks.TITLE + " TEXT," +
+                Bookmarks.URL + " TEXT," +
+                Bookmarks.TYPE + " INTEGER NOT NULL DEFAULT " + Bookmarks.TYPE_BOOKMARK + "," +
+                Bookmarks.PARENT + " INTEGER," +
+                Bookmarks.POSITION + " INTEGER NOT NULL," +
+                Bookmarks.KEYWORD + " TEXT," +
+                Bookmarks.DESCRIPTION + " TEXT," +
+                Bookmarks.TAGS + " TEXT," +
+                Bookmarks.DATE_CREATED + " INTEGER," +
+                Bookmarks.DATE_MODIFIED + " INTEGER," +
+                Bookmarks.GUID + " TEXT NOT NULL," +
+                Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
+                (foreignKeyOnParent != null ? foreignKeyOnParent : "") +
+                ");");
+
+        db.execSQL("CREATE INDEX bookmarks_url_index ON " + TABLE_BOOKMARKS + "("
+                + Bookmarks.URL + ")");
+        db.execSQL("CREATE INDEX bookmarks_type_deleted_index ON " + TABLE_BOOKMARKS + "("
+                + Bookmarks.TYPE + ", " + Bookmarks.IS_DELETED + ")");
+        db.execSQL("CREATE UNIQUE INDEX bookmarks_guid_index ON " + TABLE_BOOKMARKS + "("
+                + Bookmarks.GUID + ")");
+        db.execSQL("CREATE INDEX bookmarks_modified_index ON " + TABLE_BOOKMARKS + "("
+                + Bookmarks.DATE_MODIFIED + ")");
+    }
+
+    private void createBookmarksTableOn13(SQLiteDatabase db) {
+        debug("Creating " + TABLE_BOOKMARKS + " table");
+
+        // Android versions older than Froyo ship with an sqlite
+        // that doesn't support foreign keys.
+        String foreignKeyOnParent = null;
+        if (Build.VERSION.SDK_INT >= 8) {
+            foreignKeyOnParent = ", FOREIGN KEY (" + Bookmarks.PARENT +
+                ") REFERENCES " + TABLE_BOOKMARKS + "(" + Bookmarks._ID + ")";
+        }
+
+        db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" +
+                Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                Bookmarks.TITLE + " TEXT," +
+                Bookmarks.URL + " TEXT," +
+                Bookmarks.TYPE + " INTEGER NOT NULL DEFAULT " + Bookmarks.TYPE_BOOKMARK + "," +
+                Bookmarks.PARENT + " INTEGER," +
+                Bookmarks.POSITION + " INTEGER NOT NULL," +
+                Bookmarks.KEYWORD + " TEXT," +
+                Bookmarks.DESCRIPTION + " TEXT," +
+                Bookmarks.TAGS + " TEXT," +
+                Bookmarks.FAVICON_ID + " INTEGER," +
+                Bookmarks.DATE_CREATED + " INTEGER," +
+                Bookmarks.DATE_MODIFIED + " INTEGER," +
+                Bookmarks.GUID + " TEXT NOT NULL," +
+                Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
+                (foreignKeyOnParent != null ? foreignKeyOnParent : "") +
+                ");");
+
+        db.execSQL("CREATE INDEX bookmarks_url_index ON " + TABLE_BOOKMARKS + "("
+                + Bookmarks.URL + ")");
+        db.execSQL("CREATE INDEX bookmarks_type_deleted_index ON " + TABLE_BOOKMARKS + "("
+                + Bookmarks.TYPE + ", " + Bookmarks.IS_DELETED + ")");
+        db.execSQL("CREATE UNIQUE INDEX bookmarks_guid_index ON " + TABLE_BOOKMARKS + "("
+                + Bookmarks.GUID + ")");
+        db.execSQL("CREATE INDEX bookmarks_modified_index ON " + TABLE_BOOKMARKS + "("
+                + Bookmarks.DATE_MODIFIED + ")");
+    }
+
+    private void createHistoryTable(SQLiteDatabase db) {
+        debug("Creating " + TABLE_HISTORY + " table");
+        db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" +
+                History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                History.TITLE + " TEXT," +
+                History.URL + " TEXT NOT NULL," +
+                History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
+                History.DATE_LAST_VISITED + " INTEGER," +
+                History.DATE_CREATED + " INTEGER," +
+                History.DATE_MODIFIED + " INTEGER," +
+                History.GUID + " TEXT NOT NULL," +
+                History.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
+                ");");
+
+        db.execSQL("CREATE INDEX history_url_index ON " + TABLE_HISTORY + "("
+                + History.URL + ")");
+        db.execSQL("CREATE UNIQUE INDEX history_guid_index ON " + TABLE_HISTORY + "("
+                + History.GUID + ")");
+        db.execSQL("CREATE INDEX history_modified_index ON " + TABLE_HISTORY + "("
+                + History.DATE_MODIFIED + ")");
+        db.execSQL("CREATE INDEX history_visited_index ON " + TABLE_HISTORY + "("
+                + History.DATE_LAST_VISITED + ")");
+    }
+
+    private void createHistoryTableOn13(SQLiteDatabase db) {
+        debug("Creating " + TABLE_HISTORY + " table");
+        db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" +
+                History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                History.TITLE + " TEXT," +
+                History.URL + " TEXT NOT NULL," +
+                History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
+                History.FAVICON_ID + " INTEGER," +
+                History.DATE_LAST_VISITED + " INTEGER," +
+                History.DATE_CREATED + " INTEGER," +
+                History.DATE_MODIFIED + " INTEGER," +
+                History.GUID + " TEXT NOT NULL," +
+                History.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
+                ");");
+
+        db.execSQL("CREATE INDEX history_url_index ON " + TABLE_HISTORY + "("
+                + History.URL + ")");
+        db.execSQL("CREATE UNIQUE INDEX history_guid_index ON " + TABLE_HISTORY + "("
+                + History.GUID + ")");
+        db.execSQL("CREATE INDEX history_modified_index ON " + TABLE_HISTORY + "("
+                + History.DATE_MODIFIED + ")");
+        db.execSQL("CREATE INDEX history_visited_index ON " + TABLE_HISTORY + "("
+                + History.DATE_LAST_VISITED + ")");
+    }
+
+    private void createImagesTable(SQLiteDatabase db) {
+        debug("Creating " + Obsolete.TABLE_IMAGES + " table");
+        db.execSQL("CREATE TABLE " + Obsolete.TABLE_IMAGES + " (" +
+                Obsolete.Images._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                Obsolete.Images.URL + " TEXT UNIQUE NOT NULL," +
+                Obsolete.Images.FAVICON + " BLOB," +
+                Obsolete.Images.FAVICON_URL + " TEXT," +
+                Obsolete.Images.THUMBNAIL + " BLOB," +
+                Obsolete.Images.DATE_CREATED + " INTEGER," +
+                Obsolete.Images.DATE_MODIFIED + " INTEGER," +
+                Obsolete.Images.GUID + " TEXT NOT NULL," +
+                Obsolete.Images.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
+                ");");
+
+        db.execSQL("CREATE INDEX images_url_index ON " + Obsolete.TABLE_IMAGES + "("
+                + Obsolete.Images.URL + ")");
+        db.execSQL("CREATE UNIQUE INDEX images_guid_index ON " + Obsolete.TABLE_IMAGES + "("
+                + Obsolete.Images.GUID + ")");
+        db.execSQL("CREATE INDEX images_modified_index ON " + Obsolete.TABLE_IMAGES + "("
+                + Obsolete.Images.DATE_MODIFIED + ")");
+    }
+
+    private void createFaviconsTable(SQLiteDatabase db) {
+        debug("Creating " + TABLE_FAVICONS + " table");
+        db.execSQL("CREATE TABLE " + TABLE_FAVICONS + " (" +
+                Favicons._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                Favicons.URL + " TEXT UNIQUE," +
+                Favicons.DATA + " BLOB," +
+                Favicons.DATE_CREATED + " INTEGER," +
+                Favicons.DATE_MODIFIED + " INTEGER" +
+                ");");
+
+        db.execSQL("CREATE INDEX favicons_url_index ON " + TABLE_FAVICONS + "("
+                + Favicons.URL + ")");
+        db.execSQL("CREATE INDEX favicons_modified_index ON " + TABLE_FAVICONS + "("
+                + Favicons.DATE_MODIFIED + ")");
+    }
+
+    private void createThumbnailsTable(SQLiteDatabase db) {
+        debug("Creating " + TABLE_THUMBNAILS + " table");
+        db.execSQL("CREATE TABLE " + TABLE_THUMBNAILS + " (" +
+                Thumbnails._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                Thumbnails.URL + " TEXT UNIQUE," +
+                Thumbnails.DATA + " BLOB" +
+                ");");
+
+        db.execSQL("CREATE INDEX thumbnails_url_index ON " + TABLE_THUMBNAILS + "("
+                + Thumbnails.URL + ")");
+    }
+
+    private void createBookmarksWithImagesView(SQLiteDatabase db) {
+        debug("Creating " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES + " view");
+
+        db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES + " AS " +
+                "SELECT " + qualifyColumn(TABLE_BOOKMARKS, "*") +
+                ", " + Obsolete.Images.FAVICON + ", " + Obsolete.Images.THUMBNAIL + " FROM " +
+                Obsolete.TABLE_BOOKMARKS_JOIN_IMAGES);
+    }
+
+    private void createBookmarksWithFaviconsView(SQLiteDatabase db) {
+        debug("Creating " + VIEW_BOOKMARKS_WITH_FAVICONS + " view");
+
+        db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_BOOKMARKS_WITH_FAVICONS + " AS " +
+                "SELECT " + qualifyColumn(TABLE_BOOKMARKS, "*") +
+                ", " + qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Bookmarks.FAVICON +
+                ", " + qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Bookmarks.FAVICON_URL +
+                " FROM " + TABLE_BOOKMARKS_JOIN_FAVICONS);
+    }
+
+    private void createHistoryWithImagesView(SQLiteDatabase db) {
+        debug("Creating " + Obsolete.VIEW_HISTORY_WITH_IMAGES + " view");
+
+        db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES + " AS " +
+                "SELECT " + qualifyColumn(TABLE_HISTORY, "*") +
+                ", " + Obsolete.Images.FAVICON + ", " + Obsolete.Images.THUMBNAIL + " FROM " +
+                Obsolete.TABLE_HISTORY_JOIN_IMAGES);
+    }
+
+    private void createHistoryWithFaviconsView(SQLiteDatabase db) {
+        debug("Creating " + VIEW_HISTORY_WITH_FAVICONS + " view");
+
+        db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_HISTORY_WITH_FAVICONS + " AS " +
+                "SELECT " + qualifyColumn(TABLE_HISTORY, "*") +
+                ", " + qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + History.FAVICON +
+                ", " + qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + History.FAVICON_URL +
+                " FROM " + TABLE_HISTORY_JOIN_FAVICONS);
+    }
+
+    private void createCombinedWithImagesView(SQLiteDatabase db) {
+        debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
+
+        db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
+                " SELECT " + Combined.BOOKMARK_ID + ", " +
+                             Combined.HISTORY_ID + ", " +
+                             // We need to return an _id column because CursorAdapter requires it for its
+                             // default implementation for the getItemId() method. However, since
+                             // we're not using this feature in the parts of the UI using this view,
+                             // we can just use 0 for all rows.
+                             "0 AS " + Combined._ID + ", " +
+                             Combined.URL + ", " +
+                             Combined.TITLE + ", " +
+                             Combined.VISITS + ", " +
+                             Combined.DATE_LAST_VISITED + ", " +
+                             qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
+                             qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
+                " FROM (" +
+                    // Bookmarks without history.
+                    " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
+                                 "-1 AS " + Combined.HISTORY_ID + ", " +
+                                 "-1 AS " + Combined.VISITS + ", " +
+                                 "-1 AS " + Combined.DATE_LAST_VISITED +
+                    " FROM " + TABLE_BOOKMARKS +
+                    " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
+                                qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
+                                qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
+                                    " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
+                    " UNION ALL" +
+                    // History with and without bookmark.
+                    " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
+                                 // Prioritze bookmark titles over history titles, since the user may have
+                                 // customized the title for a bookmark.
+                                 "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
+                                               qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
+                    " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
+                        " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
+                    " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
+                                qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ")" +
+                ") LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
+                    " ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
+    }
+
+    private void createCombinedWithImagesViewOn9(SQLiteDatabase db) {
+        debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
+
+        db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
+                " SELECT " + Combined.BOOKMARK_ID + ", " +
+                             Combined.HISTORY_ID + ", " +
+                             // We need to return an _id column because CursorAdapter requires it for its
+                             // default implementation for the getItemId() method. However, since
+                             // we're not using this feature in the parts of the UI using this view,
+                             // we can just use 0 for all rows.
+                             "0 AS " + Combined._ID + ", " +
+                             Combined.URL + ", " +
+                             Combined.TITLE + ", " +
+                             Combined.VISITS + ", " +
+                             Combined.DISPLAY + ", " +
+                             Combined.DATE_LAST_VISITED + ", " +
+                             qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
+                             qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
+                " FROM (" +
+                    // Bookmarks without history.
+                    " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
+                                 "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
+                                    Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
+                                    Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
+                                 "-1 AS " + Combined.HISTORY_ID + ", " +
+                                 "-1 AS " + Combined.VISITS + ", " +
+                                 "-1 AS " + Combined.DATE_LAST_VISITED +
+                    " FROM " + TABLE_BOOKMARKS +
+                    " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
+                                qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
+                                qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
+                                    " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
+                    " UNION ALL" +
+                    // History with and without bookmark.
+                    " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
+                                 // Prioritze bookmark titles over history titles, since the user may have
+                                 // customized the title for a bookmark.
+                                 "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
+                                               qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
+                                 "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
+                                    Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
+                                    Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
+                    " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
+                        " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
+                    " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
+                                qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ")" +
+                ") LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
+                    " ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
+    }
+
+    private void createCombinedWithImagesViewOn10(SQLiteDatabase db) {
+        debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
+
+        db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
+                " SELECT " + Combined.BOOKMARK_ID + ", " +
+                             Combined.HISTORY_ID + ", " +
+                             // We need to return an _id column because CursorAdapter requires it for its
+                             // default implementation for the getItemId() method. However, since
+                             // we're not using this feature in the parts of the UI using this view,
+                             // we can just use 0 for all rows.
+                             "0 AS " + Combined._ID + ", " +
+                             Combined.URL + ", " +
+                             Combined.TITLE + ", " +
+                             Combined.VISITS + ", " +
+                             Combined.DISPLAY + ", " +
+                             Combined.DATE_LAST_VISITED + ", " +
+                             qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
+                             qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
+                " FROM (" +
+                    // Bookmarks without history.
+                    " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
+                                 "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
+                                    Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
+                                    Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
+                                 "-1 AS " + Combined.HISTORY_ID + ", " +
+                                 "-1 AS " + Combined.VISITS + ", " +
+                                 "-1 AS " + Combined.DATE_LAST_VISITED +
+                    " FROM " + TABLE_BOOKMARKS +
+                    " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
+                                qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
+                                qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
+                                    " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
+                    " UNION ALL" +
+                    // History with and without bookmark.
+                    " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) +  " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
+                                 // Prioritze bookmark titles over history titles, since the user may have
+                                 // customized the title for a bookmark.
+                                 "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
+                                               qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
+                                 "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
+                                    Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
+                                    Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
+                    " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
+                        " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
+                    " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
+                                qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ")" +
+                ") LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
+                    " ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
+    }
+
+    private void createCombinedWithImagesViewOn11(SQLiteDatabase db) {
+        debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
+
+        db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
+                " SELECT " + Combined.BOOKMARK_ID + ", " +
+                             Combined.HISTORY_ID + ", " +
+                             // We need to return an _id column because CursorAdapter requires it for its
+                             // default implementation for the getItemId() method. However, since
+                             // we're not using this feature in the parts of the UI using this view,
+                             // we can just use 0 for all rows.
+                             "0 AS " + Combined._ID + ", " +
+                             Combined.URL + ", " +
+                             Combined.TITLE + ", " +
+                             Combined.VISITS + ", " +
+                             Combined.DISPLAY + ", " +
+                             Combined.DATE_LAST_VISITED + ", " +
+                             qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
+                             qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
+                " FROM (" +
+                    // Bookmarks without history.
+                    " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
+                                 "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
+                                    Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
+                                    Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
+                                 "-1 AS " + Combined.HISTORY_ID + ", " +
+                                 "-1 AS " + Combined.VISITS + ", " +
+                                 "-1 AS " + Combined.DATE_LAST_VISITED +
+                    " FROM " + TABLE_BOOKMARKS +
+                    " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
+                                qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
+                                qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
+                                    " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
+                    " UNION ALL" +
+                    // History with and without bookmark.
+                    " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) +  " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
+                                 // Prioritze bookmark titles over history titles, since the user may have
+                                 // customized the title for a bookmark.
+                                 "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
+                                               qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
+                                 // Only use DISPLAY_READER if the matching bookmark entry inside reading
+                                 // list folder is not marked as deleted.
+                                 "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
+                                    " THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
+                                    Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
+                    " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
+                        " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
+                    " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
+                                qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
+                ") LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
+                    " ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
+    }
+
+    private void createCombinedViewOn12(SQLiteDatabase db) {
+        debug("Creating " + VIEW_COMBINED + " view");
+
+        db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" +
+                " SELECT " + Combined.BOOKMARK_ID + ", " +
+                             Combined.HISTORY_ID + ", " +
+                             // We need to return an _id column because CursorAdapter requires it for its
+                             // default implementation for the getItemId() method. However, since
+                             // we're not using this feature in the parts of the UI using this view,
+                             // we can just use 0 for all rows.
+                             "0 AS " + Combined._ID + ", " +
+                             Combined.URL + ", " +
+                             Combined.TITLE + ", " +
+                             Combined.VISITS + ", " +
+                             Combined.DISPLAY + ", " +
+                             Combined.DATE_LAST_VISITED +
+                " FROM (" +
+                    // Bookmarks without history.
+                    " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
+                                 "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
+                                    Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
+                                    Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
+                                 "-1 AS " + Combined.HISTORY_ID + ", " +
+                                 "-1 AS " + Combined.VISITS + ", " +
+                                 "-1 AS " + Combined.DATE_LAST_VISITED +
+                    " FROM " + TABLE_BOOKMARKS +
+                    " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
+                                qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
+                                qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
+                                    " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
+                    " UNION ALL" +
+                    // History with and without bookmark.
+                    " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) +  " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
+                                 // Prioritze bookmark titles over history titles, since the user may have
+                                 // customized the title for a bookmark.
+                                 "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
+                                               qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
+                                 // Only use DISPLAY_READER if the matching bookmark entry inside reading
+                                 // list folder is not marked as deleted.
+                                 "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
+                                    " THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
+                                    Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
+                    " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
+                        " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
+                    " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
+                                qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
+                ")");
+
+        debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
+
+        db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
+                " SELECT *, " +
+                    qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
+                    qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
+                " FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
+                    " ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
+    }
+
+    private void createCombinedViewOn13(SQLiteDatabase db) {
+        debug("Creating " + VIEW_COMBINED + " view");
+
+        db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" +
+                " SELECT " + Combined.BOOKMARK_ID + ", " +
+                             Combined.HISTORY_ID + ", " +
+                             // We need to return an _id column because CursorAdapter requires it for its
+                             // default implementation for the getItemId() method. However, since
+                             // we're not using this feature in the parts of the UI using this view,
+                             // we can just use 0 for all rows.
+                             "0 AS " + Combined._ID + ", " +
+                             Combined.URL + ", " +
+                             Combined.TITLE + ", " +
+                             Combined.VISITS + ", " +
+                             Combined.DISPLAY + ", " +
+                             Combined.DATE_LAST_VISITED + ", " +
+                             Combined.FAVICON_ID +
+                " FROM (" +
+                    // Bookmarks without history.
+                    " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
+                                 "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
+                                    Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
+                                    Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
+                                 "-1 AS " + Combined.HISTORY_ID + ", " +
+                                 "-1 AS " + Combined.VISITS + ", " +
+                                 "-1 AS " + Combined.DATE_LAST_VISITED + ", " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
+                    " FROM " + TABLE_BOOKMARKS +
+                    " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
+                                qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
+                                qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
+                                    " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
+                    " UNION ALL" +
+                    // History with and without bookmark.
+                    " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) +  " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
+                                 // Prioritize bookmark titles over history titles, since the user may have
+                                 // customized the title for a bookmark.
+                                 "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
+                                               qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
+                                 // Only use DISPLAY_READER if the matching bookmark entry inside reading
+                                 // list folder is not marked as deleted.
+                                 "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
+                                    " THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
+                                    Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
+                    " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
+                        " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
+                    " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
+                                qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
+                ")");
+
+        debug("Creating " + VIEW_COMBINED_WITH_FAVICONS + " view");
+
+        db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED_WITH_FAVICONS + " AS" +
+                " SELECT " + qualifyColumn(VIEW_COMBINED, "*") + ", " +
+                    qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Combined.FAVICON_URL + ", " +
+                    qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Combined.FAVICON +
+                " FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + TABLE_FAVICONS +
+                    " ON " + Combined.FAVICON_ID + " = " + qualifyColumn(TABLE_FAVICONS, Favicons._ID));
+    }
+
+    private void createCombinedViewOn16(SQLiteDatabase db) {
+        debug("Creating " + VIEW_COMBINED + " view");
+
+        db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" +
+                " SELECT " + Combined.BOOKMARK_ID + ", " +
+                             Combined.HISTORY_ID + ", " +
+                             // We need to return an _id column because CursorAdapter requires it for its
+                             // default implementation for the getItemId() method. However, since
+                             // we're not using this feature in the parts of the UI using this view,
+                             // we can just use 0 for all rows.
+                             "0 AS " + Combined._ID + ", " +
+                             Combined.URL + ", " +
+                             Combined.TITLE + ", " +
+                             Combined.VISITS + ", " +
+                             Combined.DISPLAY + ", " +
+                             Combined.DATE_LAST_VISITED + ", " +
+                             Combined.FAVICON_ID +
+                " FROM (" +
+                    // Bookmarks without history.
+                    " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
+                                 "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
+                                    Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
+                                    Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
+                                 "-1 AS " + Combined.HISTORY_ID + ", " +
+                                 "-1 AS " + Combined.VISITS + ", " +
+                                 "-1 AS " + Combined.DATE_LAST_VISITED + ", " +
+                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
+                    " FROM " + TABLE_BOOKMARKS +
+                    " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
+                                // Ignore pinned bookmarks.
+                                qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT)  + " <> " + Bookmarks.FIXED_PINNED_LIST_ID + " AND " +
+                                qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
+                                qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
+                                    " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
+                    " UNION ALL" +
+                    // History with and without bookmark.
+                    " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
+                                    // Give pinned bookmarks a NULL ID so that they're not treated as bookmarks. We can't
+                                    // completely ignore them here because they're joined with history entries we care about.
+                                    "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
+                                    Bookmarks.FIXED_PINNED_LIST_ID + " THEN NULL ELSE " +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " END " +
+                                 "ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
+                                 // Prioritize bookmark titles over history titles, since the user may have
+                                 // customized the title for a bookmark.
+                                 "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
+                                               qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
+                                 // Only use DISPLAY_READER if the matching bookmark entry inside reading
+                                 // list folder is not marked as deleted.
+                                 "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
+                                    " THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
+                                    Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED + ", " +
+                                 qualifyColumn(TABLE_HISTORY, History.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
+                    " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
+                        " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
+                    " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
+                                qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
+                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
+                ")");
+
+        debug("Creating " + VIEW_COMBINED_WITH_FAVICONS + " view");
+
+        db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED_WITH_FAVICONS + " AS" +
+                " SELECT " + qualifyColumn(VIEW_COMBINED, "*") + ", " +
+                    qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Combined.FAVICON_URL + ", " +
+                    qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Combined.FAVICON +
+                " FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + TABLE_FAVICONS +
+                    " ON " + Combined.FAVICON_ID + " = " + qualifyColumn(TABLE_FAVICONS, Favicons._ID));
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase db) {
+        debug("Creating browser.db: " + db.getPath());
+
+        createBookmarksTableOn13(db);
+        createHistoryTableOn13(db);
+        createFaviconsTable(db);
+        createThumbnailsTable(db);
+
+        createBookmarksWithFaviconsView(db);
+        createHistoryWithFaviconsView(db);
+        createCombinedViewOn16(db);
+
+        createOrUpdateSpecialFolder(db, Bookmarks.PLACES_FOLDER_GUID,
+            R.string.bookmarks_folder_places, 0);
+
+        createOrUpdateAllSpecialFolders(db);
+
+        // Create distribution bookmarks before our own default bookmarks
+        int pos = createDistributionBookmarks(db);
+        createDefaultBookmarks(db, pos);
+    }
+
+    private String getLocalizedProperty(JSONObject bookmark, String property, Locale locale) throws JSONException {
+        // Try the full locale
+        String fullLocale = property + "." + locale.toString();
+        if (bookmark.has(fullLocale)) {
+            return bookmark.getString(fullLocale);
+        }
+        // Try without a variant
+        if (!TextUtils.isEmpty(locale.getVariant())) {
+            String noVariant = fullLocale.substring(0, fullLocale.lastIndexOf("_"));
+            if (bookmark.has(noVariant)) {
+                return bookmark.getString(noVariant);
+            }
+        }
+        // Try just the language
+        String lang = property + "." + locale.getLanguage();
+        if (bookmark.has(lang)) {
+            return bookmark.getString(lang);
+        }
+        // Default to the non-localized property name
+        return bookmark.getString(property);
+    }
+
+    // Returns the number of bookmarks inserted in the db
+    private int createDistributionBookmarks(SQLiteDatabase db) {
+        JSONArray bookmarks = Distribution.getBookmarks(mContext);
+        if (bookmarks == null) {
+            return 0;
+        }
+
+        Locale locale = Locale.getDefault();
+        int pos = 0;
+        Integer mobileFolderId = getMobileFolderId(db);
+        if (mobileFolderId == null) {
+            Log.e(LOGTAG, "Error creating distribution bookmarks: mobileFolderId is null");
+            return 0;
+        }
+
+        for (int i = 0; i < bookmarks.length(); i++) {
+            try {
+                final JSONObject bookmark = bookmarks.getJSONObject(i);
+
+                String title = getLocalizedProperty(bookmark, "title", locale);
+                final String url = getLocalizedProperty(bookmark, "url", locale);
+                createBookmark(db, title, url, pos, mobileFolderId);
+
+                if (bookmark.has("pinned")) {
+                    try {
+                        // Create a fake bookmark in the hidden pinned folder to pin bookmark
+                        // to about:home top sites. Pass pos as the pinned position to pin
+                        // sites in the order that bookmarks are specified in bookmarks.json.
+                        if (bookmark.getBoolean("pinned")) {
+                            createBookmark(db, title, url, pos, Bookmarks.FIXED_PINNED_LIST_ID);
+                        }
+                    } catch (JSONException e) {
+                        Log.e(LOGTAG, "Error pinning bookmark to top sites", e);
+                    }
+                }
+
+                pos++;
+
+                // return early if there is no icon for this bookmark
+                if (!bookmark.has("icon")) {
+                    continue;
+                }
+
+                // create icons in a separate thread to avoid blocking about:home on startup
+                ThreadUtils.postToBackgroundThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        SQLiteDatabase db = getWritableDatabase();
+                        try {
+                            String iconData = bookmark.getString("icon");
+                            Bitmap icon = BitmapUtils.getBitmapFromDataURI(iconData);
+                            if (icon != null) {
+                                createFavicon(db, url, icon);
+                            }
+                        } catch (JSONException e) {
+                            Log.e(LOGTAG, "Error creating distribution bookmark icon", e);
+                        }
+                    }
+                });
+            } catch (JSONException e) {
+                Log.e(LOGTAG, "Error creating distribution bookmark", e);
+            }
+        }
+        return pos;
+    }
+
+    // Inserts default bookmarks, starting at a specified position
+    private void createDefaultBookmarks(SQLiteDatabase db, int pos) {
+        Class<?> stringsClass = R.string.class;
+        Field[] fields = stringsClass.getFields();
+        Pattern p = Pattern.compile("^bookmarkdefaults_title_");
+
+        Integer mobileFolderId = getMobileFolderId(db);
+        if (mobileFolderId == null) {
+            Log.e(LOGTAG, "Error creating default bookmarks: mobileFolderId is null");
+            return;
+        }
+
+        for (int i = 0; i < fields.length; i++) {
+            final String name = fields[i].getName();
+            Matcher m = p.matcher(name);
+            if (!m.find()) {
+                continue;
+            }
+            try {
+                int titleid = fields[i].getInt(null);
+                String title = mContext.getString(titleid);
+
+                Field urlField = stringsClass.getField(name.replace("_title_", "_url_"));
+                int urlId = urlField.getInt(null);
+                final String url = mContext.getString(urlId);
+                createBookmark(db, title, url, pos, mobileFolderId);
+
+                // create icons in a separate thread to avoid blocking about:home on startup
+                ThreadUtils.postToBackgroundThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        SQLiteDatabase db = getWritableDatabase();
+                        Bitmap icon = getDefaultFaviconFromPath(name);
+                        if (icon == null) {
+                            icon = getDefaultFaviconFromDrawable(name);
+                        }
+                        if (icon != null) {
+                            createFavicon(db, url, icon);
+                        }
+                    }
+                });
+                pos++;
+            } catch (java.lang.IllegalAccessException ex) {
+                Log.e(LOGTAG, "Can't create bookmark " + name, ex);
+            } catch (java.lang.NoSuchFieldException ex) {
+                Log.e(LOGTAG, "Can't create bookmark " + name, ex);
+            }
+        }
+    }
+
+    private void createBookmark(SQLiteDatabase db, String title, String url, int pos, int parent) {
+        ContentValues bookmarkValues = new ContentValues();
+        bookmarkValues.put(Bookmarks.PARENT, parent);
+
+        long now = System.currentTimeMillis();
+        bookmarkValues.put(Bookmarks.DATE_CREATED, now);
+        bookmarkValues.put(Bookmarks.DATE_MODIFIED, now);
+
+        bookmarkValues.put(Bookmarks.TITLE, title);
+        bookmarkValues.put(Bookmarks.URL, url);
+        bookmarkValues.put(Bookmarks.GUID, Utils.generateGuid());
+        bookmarkValues.put(Bookmarks.POSITION, pos);
+        db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.TITLE, bookmarkValues);
+    }
+
+    private void createFavicon(SQLiteDatabase db, String url, Bitmap icon) {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        ContentValues iconValues = new ContentValues();
+        iconValues.put(Favicons.PAGE_URL, url);
+
+        byte[] data = null;
+        if (icon.compress(Bitmap.CompressFormat.PNG, 100, stream)) {
+            data = stream.toByteArray();
+        } else {
+            Log.w(LOGTAG, "Favicon compression failed.");
+        }
+        iconValues.put(Favicons.DATA, data);
+
+        insertFavicon(db, iconValues);
+    }
+
+    private Bitmap getDefaultFaviconFromPath(String name) {
+        Class<?> stringClass = R.string.class;
+        try {
+            // Look for a drawable with the id R.drawable.bookmarkdefaults_favicon_*
+            Field faviconField = stringClass.getField(name.replace("_title_", "_favicon_"));
+            if (faviconField == null) {
+                return null;
+            }
+            int faviconId = faviconField.getInt(null);
+            String path = mContext.getString(faviconId);
+
+            String apkPath = mContext.getPackageResourcePath();
+            File apkFile = new File(apkPath);
+            String bitmapPath = "jar:jar:" + apkFile.toURI() + "!/" + AppConstants.OMNIJAR_NAME + "!/" + path;
+            return GeckoJarReader.getBitmap(mContext.getResources(), bitmapPath);
+        } catch (java.lang.IllegalAccessException ex) {
+            Log.e(LOGTAG, "[Path] Can't create favicon " + name, ex);
+        } catch (java.lang.NoSuchFieldException ex) {
+            // If the field does not exist, that means we intend to load via a drawable
+        }
+        return null;
+    }
+
+    private Bitmap getDefaultFaviconFromDrawable(String name) {
+        Class<?> drawablesClass = R.drawable.class;
+        try {
+            // Look for a drawable with the id R.drawable.bookmarkdefaults_favicon_*
+            Field faviconField = drawablesClass.getField(name.replace("_title_", "_favicon_"));
+            if (faviconField == null) {
+                return null;
+            }
+            int faviconId = faviconField.getInt(null);
+            return BitmapUtils.decodeResource(mContext, faviconId);
+        } catch (java.lang.IllegalAccessException ex) {
+            Log.e(LOGTAG, "[Drawable] Can't create favicon " + name, ex);
+        } catch (java.lang.NoSuchFieldException ex) {
+            // If the field does not exist, that means we intend to load via a file path
+        }
+        return null;
+    }
+
+    private void createOrUpdateAllSpecialFolders(SQLiteDatabase db) {
+        createOrUpdateSpecialFolder(db, Bookmarks.MOBILE_FOLDER_GUID,
+            R.string.bookmarks_folder_mobile, 0);
+        createOrUpdateSpecialFolder(db, Bookmarks.TOOLBAR_FOLDER_GUID,
+            R.string.bookmarks_folder_toolbar, 1);
+        createOrUpdateSpecialFolder(db, Bookmarks.MENU_FOLDER_GUID,
+            R.string.bookmarks_folder_menu, 2);
+        createOrUpdateSpecialFolder(db, Bookmarks.TAGS_FOLDER_GUID,
+            R.string.bookmarks_folder_tags, 3);
+        createOrUpdateSpecialFolder(db, Bookmarks.UNFILED_FOLDER_GUID,
+            R.string.bookmarks_folder_unfiled, 4);
+        createOrUpdateSpecialFolder(db, Bookmarks.READING_LIST_FOLDER_GUID,
+            R.string.bookmarks_folder_reading_list, 5);
+        createOrUpdateSpecialFolder(db, Bookmarks.PINNED_FOLDER_GUID,
+            R.string.bookmarks_folder_pinned, 6);
+    }
+
+    private void createOrUpdateSpecialFolder(SQLiteDatabase db,
+            String guid, int titleId, int position) {
+        ContentValues values = new ContentValues();
+        values.put(Bookmarks.GUID, guid);
+        values.put(Bookmarks.TYPE, Bookmarks.TYPE_FOLDER);
+        values.put(Bookmarks.POSITION, position);
+
+        if (guid.equals(Bookmarks.PLACES_FOLDER_GUID))
+            values.put(Bookmarks._ID, Bookmarks.FIXED_ROOT_ID);
+        else if (guid.equals(Bookmarks.READING_LIST_FOLDER_GUID))
+            values.put(Bookmarks._ID, Bookmarks.FIXED_READING_LIST_ID);
+        else if (guid.equals(Bookmarks.PINNED_FOLDER_GUID))
+            values.put(Bookmarks._ID, Bookmarks.FIXED_PINNED_LIST_ID);
+
+        // Set the parent to 0, which sync assumes is the root
+        values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID);
+
+        String title = mContext.getResources().getString(titleId);
+        values.put(Bookmarks.TITLE, title);
+
+        long now = System.currentTimeMillis();
+        values.put(Bookmarks.DATE_CREATED, now);
+        values.put(Bookmarks.DATE_MODIFIED, now);
+
+        int updated = db.update(TABLE_BOOKMARKS, values,
+                                Bookmarks.GUID + " = ?",
+                                new String[] { guid });
+
+        if (updated == 0) {
+            db.insert(TABLE_BOOKMARKS, Bookmarks.GUID, values);
+            debug("Inserted special folder: " + guid);
+        } else {
+            debug("Updated special folder: " + guid);
+        }
+    }
+
+    private boolean isSpecialFolder(ContentValues values) {
+        String guid = values.getAsString(Bookmarks.GUID);
+        if (guid == null)
+            return false;
+
+        return guid.equals(Bookmarks.MOBILE_FOLDER_GUID) ||
+               guid.equals(Bookmarks.MENU_FOLDER_GUID) ||
+               guid.equals(Bookmarks.TOOLBAR_FOLDER_GUID) ||
+               guid.equals(Bookmarks.UNFILED_FOLDER_GUID) ||
+               guid.equals(Bookmarks.TAGS_FOLDER_GUID);
+    }
+
+    private void migrateBookmarkFolder(SQLiteDatabase db, int folderId,
+            BookmarkMigrator migrator) {
+        Cursor c = null;
+
+        debug("Migrating bookmark folder with id = " + folderId);
+
+        String selection = Bookmarks.PARENT + " = " + folderId;
+        String[] selectionArgs = null;
+
+        boolean isRootFolder = (folderId == Bookmarks.FIXED_ROOT_ID);
+
+        // If we're loading the root folder, we have to account for
+        // any previously created special folder that was created without
+        // setting a parent id (e.g. mobile folder) and making sure we're
+        // not adding any infinite recursion as root's parent is root itself.
+        if (isRootFolder) {
+            selection = Bookmarks.GUID + " != ?" + " AND (" +
+                        selection + " OR " + Bookmarks.PARENT + " = NULL)";
+            selectionArgs = new String[] { Bookmarks.PLACES_FOLDER_GUID };
+        }
+
+        List<Integer> subFolders = new ArrayList<Integer>();
+        List<ContentValues> invalidSpecialEntries = new ArrayList<ContentValues>();
+
+        try {
+            c = db.query(TABLE_BOOKMARKS_TMP,
+                         null,
+                         selection,
+                         selectionArgs,
+                         null, null, null);
+
+            // The key point here is that bookmarks should be added in
+            // parent order to avoid any problems with the foreign key
+            // in Bookmarks.PARENT.
+            while (c.moveToNext()) {
+                ContentValues values = new ContentValues();
+
+                // We're using a null projection in the query which
+                // means we're getting all columns from the table.
+                // It's safe to simply transform the row into the
+                // values to be inserted on the new table.
+                DatabaseUtils.cursorRowToContentValues(c, values);
+
+                boolean isSpecialFolder = isSpecialFolder(values);
+
+                // The mobile folder used to be created with PARENT = NULL.
+                // We want fix that here.
+                if (values.getAsLong(Bookmarks.PARENT) == null && isSpecialFolder)
+                    values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID);
+
+                if (isRootFolder && !isSpecialFolder) {
+                    invalidSpecialEntries.add(values);
+                    continue;
+                }
+
+                if (migrator != null)
+                    migrator.updateForNewTable(values);
+
+                debug("Migrating bookmark: " + values.getAsString(Bookmarks.TITLE));
+                db.insert(TABLE_BOOKMARKS, Bookmarks.URL, values);
+
+                Integer type = values.getAsInteger(Bookmarks.TYPE);
+                if (type != null && type == Bookmarks.TYPE_FOLDER)
+                    subFolders.add(values.getAsInteger(Bookmarks._ID));
+            }
+        } finally {
+            if (c != null)
+                c.close();
+        }
+
+        // At this point is safe to assume that the mobile folder is
+        // in the new table given that we've always created it on
+        // database creation time.
+        final int nInvalidSpecialEntries = invalidSpecialEntries.size();
+        if (nInvalidSpecialEntries > 0) {
+            Integer mobileFolderId = getMobileFolderId(db);
+            if (mobileFolderId == null) {
+                Log.e(LOGTAG, "Error migrating invalid special folder entries: mobile folder id is null");
+                return;
+            }
+
+            debug("Found " + nInvalidSpecialEntries + " invalid special folder entries");
+            for (int i = 0; i < nInvalidSpecialEntries; i++) {
+                ContentValues values = invalidSpecialEntries.get(i);
+                values.put(Bookmarks.PARENT, mobileFolderId);
+
+                db.insert(TABLE_BOOKMARKS, Bookmarks.URL, values);
+            }
+        }
+
+        final int nSubFolders = subFolders.size();
+        for (int i = 0; i < nSubFolders; i++) {
+            int subFolderId = subFolders.get(i);
+            migrateBookmarkFolder(db, subFolderId, migrator);
+        }
+    }
+
+    private void migrateBookmarksTable(SQLiteDatabase db) {
+        migrateBookmarksTable(db, null);
+    }
+
+    private void migrateBookmarksTable(SQLiteDatabase db, BookmarkMigrator migrator) {
+        debug("Renaming bookmarks table to " + TABLE_BOOKMARKS_TMP);
+        db.execSQL("ALTER TABLE " + TABLE_BOOKMARKS +
+                   " RENAME TO " + TABLE_BOOKMARKS_TMP);
+
+        debug("Dropping views and indexes related to " + TABLE_BOOKMARKS);
+        db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES);
+
+        db.execSQL("DROP INDEX IF EXISTS bookmarks_url_index");
+        db.execSQL("DROP INDEX IF EXISTS bookmarks_type_deleted_index");
+        db.execSQL("DROP INDEX IF EXISTS bookmarks_guid_index");
+        db.execSQL("DROP INDEX IF EXISTS bookmarks_modified_index");
+
+        createBookmarksTable(db);
+        createBookmarksWithImagesView(db);
+
+        createOrUpdateSpecialFolder(db, Bookmarks.PLACES_FOLDER_GUID,
+            R.string.bookmarks_folder_places, 0);
+
+        migrateBookmarkFolder(db, Bookmarks.FIXED_ROOT_ID, migrator);
+
+        // Ensure all special folders exist and have the
+        // right folder hierarchy.
+        createOrUpdateAllSpecialFolders(db);
+
+        debug("Dropping bookmarks temporary table");
+        db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS_TMP);
+    }
+
+
+    private void migrateHistoryTable(SQLiteDatabase db) {
+        debug("Renaming history table to " + TABLE_HISTORY_TMP);
+        db.execSQL("ALTER TABLE " + TABLE_HISTORY +
+                   " RENAME TO " + TABLE_HISTORY_TMP);
+
+        debug("Dropping views and indexes related to " + TABLE_HISTORY);
+        db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
+        db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
+
+        db.execSQL("DROP INDEX IF EXISTS history_url_index");
+        db.execSQL("DROP INDEX IF EXISTS history_guid_index");
+        db.execSQL("DROP INDEX IF EXISTS history_modified_index");
+        db.execSQL("DROP INDEX IF EXISTS history_visited_index");
+
+        createHistoryTable(db);
+        createHistoryWithImagesView(db);
+        createCombinedWithImagesView(db);
+
+        db.execSQL("INSERT INTO " + TABLE_HISTORY + " SELECT * FROM " + TABLE_HISTORY_TMP);
+
+        debug("Dropping history temporary table");
+        db.execSQL("DROP TABLE IF EXISTS " + TABLE_HISTORY_TMP);
+    }
+
+    private void migrateImagesTable(SQLiteDatabase db) {
+        debug("Renaming images table to " + TABLE_IMAGES_TMP);
+        db.execSQL("ALTER TABLE " + Obsolete.TABLE_IMAGES +
+                   " RENAME TO " + TABLE_IMAGES_TMP);
+
+        debug("Dropping views and indexes related to " + Obsolete.TABLE_IMAGES);
+        db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
+        db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
+
+        db.execSQL("DROP INDEX IF EXISTS images_url_index");
+        db.execSQL("DROP INDEX IF EXISTS images_guid_index");
+        db.execSQL("DROP INDEX IF EXISTS images_modified_index");
+
+        createImagesTable(db);
+        createHistoryWithImagesView(db);
+        createCombinedWithImagesView(db);
+
+        db.execSQL("INSERT INTO " + Obsolete.TABLE_IMAGES + " SELECT * FROM " + TABLE_IMAGES_TMP);
+
+        debug("Dropping images temporary table");
+        db.execSQL("DROP TABLE IF EXISTS " + TABLE_IMAGES_TMP);
+    }
+
+    private void upgradeDatabaseFrom1to2(SQLiteDatabase db) {
+        migrateBookmarksTable(db);
+    }
+
+    private void upgradeDatabaseFrom2to3(SQLiteDatabase db) {
+        debug("Dropping view: " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES);
+        db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES);
+
+        createBookmarksWithImagesView(db);
+
+        debug("Dropping view: " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
+        db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
+
+        createHistoryWithImagesView(db);
+    }
+
+    private void upgradeDatabaseFrom3to4(SQLiteDatabase db) {
+        migrateBookmarksTable(db, new BookmarkMigrator3to4());
+    }
+
+    private void upgradeDatabaseFrom4to5(SQLiteDatabase db) {
+        createCombinedWithImagesView(db);
+    }
+
+    private void upgradeDatabaseFrom5to6(SQLiteDatabase db) {
+        debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
+        db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
+
+        createCombinedWithImagesView(db);
+    }
+
+    private void upgradeDatabaseFrom6to7(SQLiteDatabase db) {
+        debug("Removing history visits with NULL GUIDs");
+        db.execSQL("DELETE FROM " + TABLE_HISTORY + " WHERE " + History.GUID + " IS NULL");
+
+        debug("Update images with NULL GUIDs");
+        String[] columns = new String[] { Obsolete.Images._ID };
+        Cursor cursor = null;
+        try {
+          cursor = db.query(Obsolete.TABLE_IMAGES, columns, Obsolete.Images.GUID + " IS NULL", null, null ,null, null, null);
+          ContentValues values = new ContentValues();
+          if (cursor.moveToFirst()) {
+              do {
+                  values.put(Obsolete.Images.GUID, Utils.generateGuid());
+                  db.update(Obsolete.TABLE_IMAGES, values, Obsolete.Images._ID + " = ?", new String[] {
+                    cursor.getString(cursor.getColumnIndexOrThrow(Obsolete.Images._ID))
+                  });
+              } while (cursor.moveToNext());
+          }
+        } finally {
+          if (cursor != null)
+            cursor.close();
+        }
+
+        migrateBookmarksTable(db);
+        migrateHistoryTable(db);
+        migrateImagesTable(db);
+    }
+
+    private void upgradeDatabaseFrom7to8(SQLiteDatabase db) {
+        debug("Combining history entries with the same URL");
+
+        final String TABLE_DUPES = "duped_urls";
+        final String TOTAL = "total";
+        final String LATEST = "latest";
+        final String WINNER = "winner";
+
+        db.execSQL("CREATE TEMP TABLE " + TABLE_DUPES + " AS" +
+                  " SELECT " + History.URL + ", " +
+                              "SUM(" + History.VISITS + ") AS " + TOTAL + ", " +
+                              "MAX(" + History.DATE_MODIFIED + ") AS " + LATEST + ", " +
+                              "MAX(" + History._ID + ") AS " + WINNER +
+                  " FROM " + TABLE_HISTORY +
+                  " GROUP BY " + History.URL +
+                  " HAVING count(" + History.URL + ") > 1");
+
+        db.execSQL("CREATE UNIQUE INDEX " + TABLE_DUPES + "_url_index ON " +
+                   TABLE_DUPES + " (" + History.URL + ")");
+
+        final String fromClause = " FROM " + TABLE_DUPES + " WHERE " +
+                                  qualifyColumn(TABLE_DUPES, History.URL) + " = " +
+                                  qualifyColumn(TABLE_HISTORY, History.URL);
+
+        db.execSQL("UPDATE " + TABLE_HISTORY +
+                  " SET " + History.VISITS + " = (SELECT " + TOTAL + fromClause + "), " +
+                            History.DATE_MODIFIED + " = (SELECT " + LATEST + fromClause + "), " +
+                            History.IS_DELETED + " = " +
+                                "(" + History._ID + " <> (SELECT " + WINNER + fromClause + "))" +
+                  " WHERE " + History.URL + " IN (SELECT " + History.URL + " FROM " + TABLE_DUPES + ")");
+
+        db.execSQL("DROP TABLE " + TABLE_DUPES);
+    }
+
+    private void upgradeDatabaseFrom8to9(SQLiteDatabase db) {
+        createOrUpdateSpecialFolder(db, Bookmarks.READING_LIST_FOLDER_GUID,
+            R.string.bookmarks_folder_reading_list, 5);
+
+        debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
+        db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
+
+        createCombinedWithImagesViewOn9(db);
+    }
+
+    private void upgradeDatabaseFrom9to10(SQLiteDatabase db) {
+        debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
+        db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
+
+        createCombinedWithImagesViewOn10(db);
+    }
+
+    private void upgradeDatabaseFrom10to11(SQLiteDatabase db) {
+        debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
+        db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
+
+        db.execSQL("CREATE INDEX bookmarks_type_deleted_index ON " + TABLE_BOOKMARKS + "("
+                + Bookmarks.TYPE + ", " + Bookmarks.IS_DELETED + ")");
+
+        createCombinedWithImagesViewOn11(db);
+    }
+
+    private void upgradeDatabaseFrom11to12(SQLiteDatabase db) {
+        debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
+        db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
+
+        createCombinedViewOn12(db);
+    }
+
+    private void upgradeDatabaseFrom12to13(SQLiteDatabase db) {
+        // Update images table with favicon URLs
+        SQLiteDatabase faviconsDb = null;
+        Cursor c = null;
+        try {
+            final String FAVICON_TABLE = "favicon_urls";
+            final String FAVICON_URL = "favicon_url";
+            final String FAVICON_PAGE = "page_url";
+
+            String dbPath = mContext.getDatabasePath(Obsolete.FAVICON_DB).getPath();
+            faviconsDb = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY);
+            String[] columns = new String[] { FAVICON_URL, FAVICON_PAGE };
+            c = faviconsDb.query(FAVICON_TABLE, columns, null, null, null, null, null, null);
+            int faviconIndex = c.getColumnIndexOrThrow(FAVICON_URL);
+            int pageIndex = c.getColumnIndexOrThrow(FAVICON_PAGE);
+            while (c.moveToNext()) {
+                ContentValues values = new ContentValues(1);
+                String faviconUrl = c.getString(faviconIndex);
+                String pageUrl = c.getString(pageIndex);
+                values.put(FAVICON_URL, faviconUrl);
+                db.update(Obsolete.TABLE_IMAGES, values, Obsolete.Images.URL + " = ?", new String[] { pageUrl });
+            }
+        } catch (SQLException e) {
+            // If we can't read from the database for some reason, we won't
+            // be able to import the favicon URLs. This isn't a fatal
+            // error, so continue the upgrade.
+            Log.e(LOGTAG, "Exception importing from " + Obsolete.FAVICON_DB, e);
+        } finally {
+            if (c != null)
+                c.close();
+            if (faviconsDb != null)
+                faviconsDb.close();
+        }
+
+        createFaviconsTable(db);
+
+        // Import favicons into the favicons table
+        db.execSQL("ALTER TABLE " + TABLE_HISTORY
+                + " ADD COLUMN " + History.FAVICON_ID + " INTEGER");
+        db.execSQL("ALTER TABLE " + TABLE_BOOKMARKS
+                + " ADD COLUMN " + Bookmarks.FAVICON_ID + " INTEGER");
+
+        try {
+            c = db.query(Obsolete.TABLE_IMAGES,
+                    new String[] {
+                        Obsolete.Images.URL,
+                        Obsolete.Images.FAVICON_URL,
+                        Obsolete.Images.FAVICON,
+                        Obsolete.Images.DATE_MODIFIED,
+                        Obsolete.Images.DATE_CREATED
+                    },
+                    Obsolete.Images.FAVICON + " IS NOT NULL",
+                    null, null, null, null);
+
+            while (c.moveToNext()) {
+                long faviconId = -1;
+                int faviconUrlIndex = c.getColumnIndexOrThrow(Obsolete.Images.FAVICON_URL);
+                String faviconUrl = null;
+                if (!c.isNull(faviconUrlIndex)) {
+                    faviconUrl = c.getString(faviconUrlIndex);
+                    Cursor c2 = null;
+                    try {
+                        c2 = db.query(TABLE_FAVICONS,
+                                new String[] { Favicons._ID },
+                                Favicons.URL + " = ?",
+                                new String[] { faviconUrl },
+                                null, null, null);
+                        if (c2.moveToFirst()) {
+                            faviconId = c2.getLong(c2.getColumnIndexOrThrow(Favicons._ID));
+                        }
+                    } finally {
+                        if (c2 != null)
+                            c2.close();
+                    }
+                }
+
+                if (faviconId == -1) {
+                    ContentValues values = new ContentValues(4);
+                    values.put(Favicons.URL, faviconUrl);
+                    values.put(Favicons.DATA, c.getBlob(c.getColumnIndexOrThrow(Obsolete.Images.FAVICON)));
+                    values.put(Favicons.DATE_MODIFIED, c.getLong(c.getColumnIndexOrThrow(Obsolete.Images.DATE_MODIFIED)));
+                    values.put(Favicons.DATE_CREATED, c.getLong(c.getColumnIndexOrThrow(Obsolete.Images.DATE_CREATED)));
+                    faviconId = db.insert(TABLE_FAVICONS, null, values);
+                }
+
+                ContentValues values = new ContentValues(1);
+                values.put(FaviconColumns.FAVICON_ID, faviconId);
+                db.update(TABLE_HISTORY, values, History.URL + " = ?",
+                        new String[] { c.getString(c.getColumnIndexOrThrow(Obsolete.Images.URL)) });
+                db.update(TABLE_BOOKMARKS, values, Bookmarks.URL + " = ?",
+                        new String[] { c.getString(c.getColumnIndexOrThrow(Obsolete.Images.URL)) });
+            }
+        } finally {
+            if (c != null)
+                c.close();
+        }
+
+        createThumbnailsTable(db);
+
+        // Import thumbnails into the thumbnails table
+        db.execSQL("INSERT INTO " + TABLE_THUMBNAILS + " ("
+                + Thumbnails.URL + ", "
+                + Thumbnails.DATA + ") "
+                + "SELECT " + Obsolete.Images.URL + ", " + Obsolete.Images.THUMBNAIL
+                + " FROM " + Obsolete.TABLE_IMAGES
+                + " WHERE " + Obsolete.Images.THUMBNAIL + " IS NOT NULL");
+
+        db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES);
+        db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
+        db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
+        db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
+
+        createBookmarksWithFaviconsView(db);
+        createHistoryWithFaviconsView(db);
+        createCombinedViewOn13(db);
+
+        db.execSQL("DROP TABLE IF EXISTS " + Obsolete.TABLE_IMAGES);
+    }
+
+    private void upgradeDatabaseFrom13to14(SQLiteDatabase db) {
+        createOrUpdateSpecialFolder(db, Bookmarks.PINNED_FOLDER_GUID,
+            R.string.bookmarks_folder_pinned, 6);
+    }
+
+    private void upgradeDatabaseFrom14to15(SQLiteDatabase db) {
+        Cursor c = null;
+        try {
+            // Get all the pinned bookmarks
+            c = db.query(TABLE_BOOKMARKS,
+                         new String[] { Bookmarks._ID, Bookmarks.URL },
+                         Bookmarks.PARENT + " = ?",
+                         new String[] { Integer.toString(Bookmarks.FIXED_PINNED_LIST_ID) },
+                         null, null, null);
+
+            while (c.moveToNext()) {
+                // Check if this URL can be parsed as a URI with a valid scheme.
+                String url = c.getString(c.getColumnIndexOrThrow(Bookmarks.URL));
+                if (Uri.parse(url).getScheme() != null) {
+                    continue;
+                }
+
+                // If it can't, update the URL to be an encoded "user-entered" value.
+                ContentValues values = new ContentValues(1);
+                String newUrl = Uri.fromParts("user-entered", url, null).toString();
+                values.put(Bookmarks.URL, newUrl);
+                db.update(TABLE_BOOKMARKS, values, Bookmarks._ID + " = ?",
+                          new String[] { Integer.toString(c.getInt(c.getColumnIndexOrThrow(Bookmarks._ID))) });
+            }
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+
+    private void upgradeDatabaseFrom15to16(SQLiteDatabase db) {
+        db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
+        db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED_WITH_FAVICONS);
+
+        createCombinedViewOn16(db);
+    }
+
+    private void upgradeDatabaseFrom16to17(SQLiteDatabase db) {
+        // Purge any 0-byte favicons/thumbnails
+        try {
+            db.execSQL("DELETE FROM " + TABLE_FAVICONS +
+                    " WHERE length(" + Favicons.DATA + ") = 0");
+            db.execSQL("DELETE FROM " + TABLE_THUMBNAILS +
+                    " WHERE length(" + Thumbnails.DATA + ") = 0");
+        } catch (SQLException e) {
+            Log.e(LOGTAG, "Error purging invalid favicons or thumbnails", e);
+        }
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        debug("Upgrading browser.db: " + db.getPath() + " from " +
+                oldVersion + " to " + newVersion);
+
+        // We have to do incremental upgrades until we reach the current
+        // database schema version.
+        for (int v = oldVersion + 1; v <= newVersion; v++) {
+            switch(v) {
+                case 2:
+                    upgradeDatabaseFrom1to2(db);
+                    break;
+
+                case 3:
+                    upgradeDatabaseFrom2to3(db);
+                    break;
+
+                case 4:
+                    upgradeDatabaseFrom3to4(db);
+                    break;
+
+                case 5:
+                    upgradeDatabaseFrom4to5(db);
+                    break;
+
+                case 6:
+                    upgradeDatabaseFrom5to6(db);
+                    break;
+
+                case 7:
+                    upgradeDatabaseFrom6to7(db);
+                    break;
+
+                case 8:
+                    upgradeDatabaseFrom7to8(db);
+                    break;
+
+                case 9:
+                    upgradeDatabaseFrom8to9(db);
+                    break;
+
+                case 10:
+                    upgradeDatabaseFrom9to10(db);
+                    break;
+
+                case 11:
+                    upgradeDatabaseFrom10to11(db);
+                    break;
+
+                case 12:
+                    upgradeDatabaseFrom11to12(db);
+                    break;
+
+                case 13:
+                    upgradeDatabaseFrom12to13(db);
+                    break;
+
+                case 14:
+                    upgradeDatabaseFrom13to14(db);
+                    break;
+
+                case 15:
+                    upgradeDatabaseFrom14to15(db);
+                    break;
+
+                case 16:
+                    upgradeDatabaseFrom15to16(db);
+                    break;
+
+                case 17:
+                    upgradeDatabaseFrom16to17(db);
+                    break;
+            }
+        }
+
+        // If an upgrade after 12->13 fails, the entire upgrade is rolled
+        // back, but we can't undo the deletion of favicon_urls.db if we
+        // delete this in step 13; therefore, we wait until all steps are
+        // complete before removing it.
+        if (oldVersion < 13 && newVersion >= 13
+                            && mContext.getDatabasePath(Obsolete.FAVICON_DB).exists()
+                            && !mContext.deleteDatabase(Obsolete.FAVICON_DB)) {
+            throw new SQLException("Could not delete " + Obsolete.FAVICON_DB);
+        }
+    }
+
+    @Override
+    public void onOpen(SQLiteDatabase db) {
+        debug("Opening browser.db: " + db.getPath());
+
+        Cursor cursor = null;
+        try {
+            cursor = db.rawQuery("PRAGMA foreign_keys=ON", null);
+        } finally {
+            if (cursor != null)
+                cursor.close();
+        }
+        cursor = null;
+        try {
+            cursor = db.rawQuery("PRAGMA synchronous=NORMAL", null);
+        } finally {
+            if (cursor != null)
+                cursor.close();
+        }
+
+        // From Honeycomb on, it's possible to run several db
+        // commands in parallel using multiple connections.
+        if (Build.VERSION.SDK_INT >= 11) {
+            db.enableWriteAheadLogging();
+            db.setLockingEnabled(false);
+        } else {
+            // Pre-Honeycomb, we can do some lesser optimizations.
+            cursor = null;
+            try {
+                cursor = db.rawQuery("PRAGMA journal_mode=PERSIST", null);
+            } finally {
+                if (cursor != null)
+                    cursor.close();
+            }
+        }
+    }
+
+    static final String qualifyColumn(String table, String column) {
+        return DBUtils.qualifyColumn(table, column);
+    }
+
+    // Calculate these once, at initialization. isLoggable is too expensive to
+    // have in-line in each log call.
+    private static boolean logDebug   = Log.isLoggable(LOGTAG, Log.DEBUG);
+    private static boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
+    protected static void trace(String message) {
+        if (logVerbose) {
+            Log.v(LOGTAG, message);
+        }
+    }
+
+    protected static void debug(String message) {
+        if (logDebug) {
+            Log.d(LOGTAG, message);
+        }
+    }
+
+    private Integer getMobileFolderId(SQLiteDatabase db) {
+        Cursor c = null;
+
+        try {
+            c = db.query(TABLE_BOOKMARKS,
+                         mobileIdColumns,
+                         Bookmarks.GUID + " = ?",
+                         mobileIdSelectionArgs,
+                         null, null, null);
+
+            if (c == null || !c.moveToFirst())
+                return null;
+
+            return c.getInt(c.getColumnIndex(Bookmarks._ID));
+        } finally {
+            if (c != null)
+                c.close();
+        }
+    }
+
+    private long insertFavicon(SQLiteDatabase db, ContentValues values) {
+        // This method is a dupicate of BrowserProvider.insertFavicon.
+        // If changes are needed, please update both
+        String faviconUrl = values.getAsString(Favicons.URL);
+        String pageUrl = null;
+        long faviconId;
+
+        trace("Inserting favicon for URL: " + faviconUrl);
+
+        DBUtils.stripEmptyByteArray(values, Favicons.DATA);
+
+        // Extract the page URL from the ContentValues
+        if (values.containsKey(Favicons.PAGE_URL)) {
+            pageUrl = values.getAsString(Favicons.PAGE_URL);
+            values.remove(Favicons.PAGE_URL);
+        }
+
+        // If no URL is provided, insert using the default one.
+        if (TextUtils.isEmpty(faviconUrl) && !TextUtils.isEmpty(pageUrl)) {
+            values.put(Favicons.URL, org.mozilla.gecko.favicons.Favicons.guessDefaultFaviconURL(pageUrl));
+        }
+
+        long now = System.currentTimeMillis();
+        values.put(Favicons.DATE_CREATED, now);
+        values.put(Favicons.DATE_MODIFIED, now);
+        faviconId = db.insertOrThrow(TABLE_FAVICONS, null, values);
+
+        if (pageUrl != null) {
+            ContentValues updateValues = new ContentValues(1);
+            updateValues.put(FaviconColumns.FAVICON_ID, faviconId);
+            db.update(TABLE_HISTORY,
+                      updateValues,
+                      History.URL + " = ?",
+                      new String[] { pageUrl });
+            db.update(TABLE_BOOKMARKS,
+                      updateValues,
+                      Bookmarks.URL + " = ?",
+                      new String[] { pageUrl });
+        }
+
+        return faviconId;
+    }
+
+    private interface BookmarkMigrator {
+        public void updateForNewTable(ContentValues bookmark);
+    }
+
+    private class BookmarkMigrator3to4 implements BookmarkMigrator {
+        @Override
+        public void updateForNewTable(ContentValues bookmark) {
+            Integer isFolder = bookmark.getAsInteger("folder");
+            if (isFolder == null || isFolder != 1) {
+                bookmark.put(Bookmarks.TYPE, Bookmarks.TYPE_BOOKMARK);
+            } else {
+                bookmark.put(Bookmarks.TYPE, Bookmarks.TYPE_FOLDER);
+            }
+
+            bookmark.remove("folder");
+        }
+    }
+}
\ No newline at end of file
--- a/mobile/android/base/db/BrowserProvider.java
+++ b/mobile/android/base/db/BrowserProvider.java
@@ -1,82 +1,52 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.db;
 
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.Distribution;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.R;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserContract.CommonColumns;
 import org.mozilla.gecko.db.BrowserContract.FaviconColumns;
 import org.mozilla.gecko.db.BrowserContract.Favicons;
 import org.mozilla.gecko.db.BrowserContract.History;
 import org.mozilla.gecko.db.BrowserContract.Schema;
 import org.mozilla.gecko.db.BrowserContract.SyncColumns;
 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
 import org.mozilla.gecko.db.BrowserContract.URLColumns;
-import org.mozilla.gecko.db.PerProfileDatabases.DatabaseHelperFactory;
-import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.util.GeckoJarReader;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
 
 import android.app.SearchManager;
-import android.content.ContentProvider;
 import android.content.ContentProviderOperation;
 import android.content.ContentProviderResult;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.OperationApplicationException;
 import android.content.UriMatcher;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.MatrixCursor;
 import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
-import android.graphics.Bitmap;
 import android.net.Uri;
-import android.os.Build;
 import android.text.TextUtils;
 import android.util.Log;
 
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class BrowserProvider extends ContentProvider {
+public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper> {
     private static final String LOGTAG = "GeckoBrowserProvider";
-    private Context mContext;
-
-    private PerProfileDatabases<BrowserDatabaseHelper> mDatabases;
-
-    static final String DATABASE_NAME = "browser.db";
-
-    static final int DATABASE_VERSION = 17;
 
     // Maximum age of deleted records to be cleaned up (20 days in ms)
     static final long MAX_AGE_OF_DELETED_RECORDS = 86400000 * 20;
 
     // Number of records marked as deleted to be removed
     static final long DELETED_RECORDS_PURGE_LIMIT = 5;
 
     // How many records to reposition in a single query.
@@ -89,30 +59,25 @@ public class BrowserProvider extends Con
     static final int DEFAULT_EXPIRY_RETAIN_COUNT = 2000;
     static final int AGGRESSIVE_EXPIRY_RETAIN_COUNT = 500;
 
     // Minimum duration to keep when expiring.
     static final long DEFAULT_EXPIRY_PRESERVE_WINDOW = 1000L * 60L * 60L * 24L * 28L;     // Four weeks.
     // Minimum number of thumbnails to keep around.
     static final int DEFAULT_EXPIRY_THUMBNAIL_COUNT = 15;
 
-    static final String TABLE_BOOKMARKS = "bookmarks";
-    static final String TABLE_HISTORY = "history";
-    static final String TABLE_FAVICONS = "favicons";
-    static final String TABLE_THUMBNAILS = "thumbnails";
+    static final String TABLE_BOOKMARKS = Bookmarks.TABLE_NAME;
+    static final String TABLE_HISTORY = History.TABLE_NAME;
+    static final String TABLE_FAVICONS = Favicons.TABLE_NAME;
+    static final String TABLE_THUMBNAILS = Thumbnails.TABLE_NAME;
 
-    static final String TABLE_BOOKMARKS_TMP = TABLE_BOOKMARKS + "_tmp";
-    static final String TABLE_HISTORY_TMP = TABLE_HISTORY + "_tmp";
-    static final String TABLE_IMAGES_TMP = Obsolete.TABLE_IMAGES + "_tmp";
-
-    static final String VIEW_COMBINED = "combined";
-
-    static final String VIEW_BOOKMARKS_WITH_FAVICONS = "bookmarks_with_favicons";
-    static final String VIEW_HISTORY_WITH_FAVICONS = "history_with_favicons";
-    static final String VIEW_COMBINED_WITH_FAVICONS = "combined_with_favicons";
+    static final String VIEW_COMBINED = Combined.VIEW_NAME;
+    static final String VIEW_BOOKMARKS_WITH_FAVICONS = Bookmarks.VIEW_WITH_FAVICONS;
+    static final String VIEW_HISTORY_WITH_FAVICONS = History.VIEW_WITH_FAVICONS;
+    static final String VIEW_COMBINED_WITH_FAVICONS = Combined.VIEW_WITH_FAVICONS;
 
     // Bookmark matches
     static final int BOOKMARKS = 100;
     static final int BOOKMARKS_ID = 101;
     static final int BOOKMARKS_FOLDER_ID = 102;
     static final int BOOKMARKS_PARENT = 103;
     static final int BOOKMARKS_POSITIONS = 104;
 
@@ -142,71 +107,26 @@ public class BrowserProvider extends Con
     static final int THUMBNAIL_ID = 801;
 
     static final String DEFAULT_BOOKMARKS_SORT_ORDER = Bookmarks.TYPE
             + " ASC, " + Bookmarks.POSITION + " ASC, " + Bookmarks._ID
             + " ASC";
 
     static final String DEFAULT_HISTORY_SORT_ORDER = History.DATE_LAST_VISITED + " DESC";
 
-    static final String TABLE_BOOKMARKS_JOIN_FAVICONS = TABLE_BOOKMARKS + " LEFT OUTER JOIN " +
-            TABLE_FAVICONS + " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.FAVICON_ID) + " = " +
-            qualifyColumn(TABLE_FAVICONS, Favicons._ID);
-
-    static final String TABLE_HISTORY_JOIN_FAVICONS = TABLE_HISTORY + " LEFT OUTER JOIN " +
-            TABLE_FAVICONS + " ON " + qualifyColumn(TABLE_HISTORY, History.FAVICON_ID) + " = " +
-            qualifyColumn(TABLE_FAVICONS, Favicons._ID);
-
     static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
 
     static final Map<String, String> BOOKMARKS_PROJECTION_MAP;
     static final Map<String, String> HISTORY_PROJECTION_MAP;
     static final Map<String, String> COMBINED_PROJECTION_MAP;
     static final Map<String, String> SCHEMA_PROJECTION_MAP;
     static final Map<String, String> SEARCH_SUGGEST_PROJECTION_MAP;
     static final Map<String, String> FAVICONS_PROJECTION_MAP;
     static final Map<String, String> THUMBNAILS_PROJECTION_MAP;
 
-    static final class Obsolete {
-        public static final String TABLE_IMAGES = "images";
-        public static final String VIEW_BOOKMARKS_WITH_IMAGES = "bookmarks_with_images";
-        public static final String VIEW_HISTORY_WITH_IMAGES = "history_with_images";
-        public static final String VIEW_COMBINED_WITH_IMAGES = "combined_with_images";
-
-        public static final class Images implements CommonColumns, SyncColumns {
-            private Images() {}
-
-            public static final String URL = "url_key";
-            public static final String FAVICON_URL = "favicon_url";
-            public static final String FAVICON = "favicon";
-            public static final String THUMBNAIL = "thumbnail";
-            public static final String _ID = "_id";
-            public static final String GUID = "guid";
-            public static final String DATE_CREATED = "created";
-            public static final String DATE_MODIFIED = "modified";
-            public static final String IS_DELETED = "deleted";
-        }
-
-        public static final class Combined {
-            private Combined() {}
-
-            public static final String THUMBNAIL = "thumbnail";
-        }
-
-        static final String TABLE_BOOKMARKS_JOIN_IMAGES = TABLE_BOOKMARKS + " LEFT OUTER JOIN " +
-                Obsolete.TABLE_IMAGES + " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " +
-                qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL);
-
-        static final String TABLE_HISTORY_JOIN_IMAGES = TABLE_HISTORY + " LEFT OUTER JOIN " +
-                Obsolete.TABLE_IMAGES + " ON " + qualifyColumn(TABLE_HISTORY, History.URL) + " = " +
-                qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL);
-
-        static final String FAVICON_DB = "favicon_urls.db";
-    }
-
     static {
         // We will reuse this.
         HashMap<String, String> map;
 
         // Bookmarks
         URI_MATCHER.addURI(BrowserContract.AUTHORITY, "bookmarks", BOOKMARKS);
         URI_MATCHER.addURI(BrowserContract.AUTHORITY, "bookmarks/#", BOOKMARKS_ID);
         URI_MATCHER.addURI(BrowserContract.AUTHORITY, "bookmarks/parents", BOOKMARKS_PARENT);
@@ -310,34 +230,16 @@ public class BrowserProvider extends Con
                 Combined.TITLE + " AS " + SearchManager.SUGGEST_COLUMN_TEXT_1);
         map.put(SearchManager.SUGGEST_COLUMN_TEXT_2_URL,
                 Combined.URL + " AS " + SearchManager.SUGGEST_COLUMN_TEXT_2_URL);
         map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA,
                 Combined.URL + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA);
         SEARCH_SUGGEST_PROJECTION_MAP = Collections.unmodifiableMap(map);
     }
 
-    private interface BookmarkMigrator {
-        public void updateForNewTable(ContentValues bookmark);
-    }
-
-    private class BookmarkMigrator3to4 implements BookmarkMigrator {
-        @Override
-        public void updateForNewTable(ContentValues bookmark) {
-            Integer isFolder = bookmark.getAsInteger("folder");
-            if (isFolder == null || isFolder != 1) {
-                bookmark.put(Bookmarks.TYPE, Bookmarks.TYPE_BOOKMARK);
-            } else {
-                bookmark.put(Bookmarks.TYPE, Bookmarks.TYPE_FOLDER);
-            }
-
-            bookmark.remove("folder");
-        }
-    }
-
     static final String qualifyColumn(String table, String column) {
         return table + "." + column;
     }
 
     private static boolean hasFaviconsInProjection(String[] projection) {
         if (projection == null) return true;
         for (int i = 0; i < projection.length; ++i) {
             if (projection[i].equals(FaviconColumns.FAVICON) ||
@@ -359,1628 +261,16 @@ public class BrowserProvider extends Con
     }
 
     protected static void debug(String message) {
         if (logDebug) {
             Log.d(LOGTAG, message);
         }
     }
 
-    final class BrowserDatabaseHelper extends SQLiteOpenHelper {
-        public BrowserDatabaseHelper(Context context, String databasePath) {
-            super(context, databasePath, null, DATABASE_VERSION);
-        }
-
-        private void createBookmarksTable(SQLiteDatabase db) {
-            debug("Creating " + TABLE_BOOKMARKS + " table");
-
-            // Android versions older than Froyo ship with an sqlite
-            // that doesn't support foreign keys.
-            String foreignKeyOnParent = null;
-            if (Build.VERSION.SDK_INT >= 8) {
-                foreignKeyOnParent = ", FOREIGN KEY (" + Bookmarks.PARENT +
-                    ") REFERENCES " + TABLE_BOOKMARKS + "(" + Bookmarks._ID + ")";
-            }
-
-            db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" +
-                    Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
-                    Bookmarks.TITLE + " TEXT," +
-                    Bookmarks.URL + " TEXT," +
-                    Bookmarks.TYPE + " INTEGER NOT NULL DEFAULT " + Bookmarks.TYPE_BOOKMARK + "," +
-                    Bookmarks.PARENT + " INTEGER," +
-                    Bookmarks.POSITION + " INTEGER NOT NULL," +
-                    Bookmarks.KEYWORD + " TEXT," +
-                    Bookmarks.DESCRIPTION + " TEXT," +
-                    Bookmarks.TAGS + " TEXT," +
-                    Bookmarks.DATE_CREATED + " INTEGER," +
-                    Bookmarks.DATE_MODIFIED + " INTEGER," +
-                    Bookmarks.GUID + " TEXT NOT NULL," +
-                    Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
-                    (foreignKeyOnParent != null ? foreignKeyOnParent : "") +
-                    ");");
-
-            db.execSQL("CREATE INDEX bookmarks_url_index ON " + TABLE_BOOKMARKS + "("
-                    + Bookmarks.URL + ")");
-            db.execSQL("CREATE INDEX bookmarks_type_deleted_index ON " + TABLE_BOOKMARKS + "("
-                    + Bookmarks.TYPE + ", " + Bookmarks.IS_DELETED + ")");
-            db.execSQL("CREATE UNIQUE INDEX bookmarks_guid_index ON " + TABLE_BOOKMARKS + "("
-                    + Bookmarks.GUID + ")");
-            db.execSQL("CREATE INDEX bookmarks_modified_index ON " + TABLE_BOOKMARKS + "("
-                    + Bookmarks.DATE_MODIFIED + ")");
-        }
-
-        private void createBookmarksTableOn13(SQLiteDatabase db) {
-            debug("Creating " + TABLE_BOOKMARKS + " table");
-
-            // Android versions older than Froyo ship with an sqlite
-            // that doesn't support foreign keys.
-            String foreignKeyOnParent = null;
-            if (Build.VERSION.SDK_INT >= 8) {
-                foreignKeyOnParent = ", FOREIGN KEY (" + Bookmarks.PARENT +
-                    ") REFERENCES " + TABLE_BOOKMARKS + "(" + Bookmarks._ID + ")";
-            }
-
-            db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" +
-                    Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
-                    Bookmarks.TITLE + " TEXT," +
-                    Bookmarks.URL + " TEXT," +
-                    Bookmarks.TYPE + " INTEGER NOT NULL DEFAULT " + Bookmarks.TYPE_BOOKMARK + "," +
-                    Bookmarks.PARENT + " INTEGER," +
-                    Bookmarks.POSITION + " INTEGER NOT NULL," +
-                    Bookmarks.KEYWORD + " TEXT," +
-                    Bookmarks.DESCRIPTION + " TEXT," +
-                    Bookmarks.TAGS + " TEXT," +
-                    Bookmarks.FAVICON_ID + " INTEGER," +
-                    Bookmarks.DATE_CREATED + " INTEGER," +
-                    Bookmarks.DATE_MODIFIED + " INTEGER," +
-                    Bookmarks.GUID + " TEXT NOT NULL," +
-                    Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
-                    (foreignKeyOnParent != null ? foreignKeyOnParent : "") +
-                    ");");
-
-            db.execSQL("CREATE INDEX bookmarks_url_index ON " + TABLE_BOOKMARKS + "("
-                    + Bookmarks.URL + ")");
-            db.execSQL("CREATE INDEX bookmarks_type_deleted_index ON " + TABLE_BOOKMARKS + "("
-                    + Bookmarks.TYPE + ", " + Bookmarks.IS_DELETED + ")");
-            db.execSQL("CREATE UNIQUE INDEX bookmarks_guid_index ON " + TABLE_BOOKMARKS + "("
-                    + Bookmarks.GUID + ")");
-            db.execSQL("CREATE INDEX bookmarks_modified_index ON " + TABLE_BOOKMARKS + "("
-                    + Bookmarks.DATE_MODIFIED + ")");
-        }
-
-        private void createHistoryTable(SQLiteDatabase db) {
-            debug("Creating " + TABLE_HISTORY + " table");
-            db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" +
-                    History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
-                    History.TITLE + " TEXT," +
-                    History.URL + " TEXT NOT NULL," +
-                    History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
-                    History.DATE_LAST_VISITED + " INTEGER," +
-                    History.DATE_CREATED + " INTEGER," +
-                    History.DATE_MODIFIED + " INTEGER," +
-                    History.GUID + " TEXT NOT NULL," +
-                    History.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
-                    ");");
-
-            db.execSQL("CREATE INDEX history_url_index ON " + TABLE_HISTORY + "("
-                    + History.URL + ")");
-            db.execSQL("CREATE UNIQUE INDEX history_guid_index ON " + TABLE_HISTORY + "("
-                    + History.GUID + ")");
-            db.execSQL("CREATE INDEX history_modified_index ON " + TABLE_HISTORY + "("
-                    + History.DATE_MODIFIED + ")");
-            db.execSQL("CREATE INDEX history_visited_index ON " + TABLE_HISTORY + "("
-                    + History.DATE_LAST_VISITED + ")");
-        }
-
-        private void createHistoryTableOn13(SQLiteDatabase db) {
-            debug("Creating " + TABLE_HISTORY + " table");
-            db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" +
-                    History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
-                    History.TITLE + " TEXT," +
-                    History.URL + " TEXT NOT NULL," +
-                    History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
-                    History.FAVICON_ID + " INTEGER," +
-                    History.DATE_LAST_VISITED + " INTEGER," +
-                    History.DATE_CREATED + " INTEGER," +
-                    History.DATE_MODIFIED + " INTEGER," +
-                    History.GUID + " TEXT NOT NULL," +
-                    History.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
-                    ");");
-
-            db.execSQL("CREATE INDEX history_url_index ON " + TABLE_HISTORY + "("
-                    + History.URL + ")");
-            db.execSQL("CREATE UNIQUE INDEX history_guid_index ON " + TABLE_HISTORY + "("
-                    + History.GUID + ")");
-            db.execSQL("CREATE INDEX history_modified_index ON " + TABLE_HISTORY + "("
-                    + History.DATE_MODIFIED + ")");
-            db.execSQL("CREATE INDEX history_visited_index ON " + TABLE_HISTORY + "("
-                    + History.DATE_LAST_VISITED + ")");
-        }
-
-        private void createImagesTable(SQLiteDatabase db) {
-            debug("Creating " + Obsolete.TABLE_IMAGES + " table");
-            db.execSQL("CREATE TABLE " + Obsolete.TABLE_IMAGES + " (" +
-                    Obsolete.Images._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
-                    Obsolete.Images.URL + " TEXT UNIQUE NOT NULL," +
-                    Obsolete.Images.FAVICON + " BLOB," +
-                    Obsolete.Images.FAVICON_URL + " TEXT," +
-                    Obsolete.Images.THUMBNAIL + " BLOB," +
-                    Obsolete.Images.DATE_CREATED + " INTEGER," +
-                    Obsolete.Images.DATE_MODIFIED + " INTEGER," +
-                    Obsolete.Images.GUID + " TEXT NOT NULL," +
-                    Obsolete.Images.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
-                    ");");
-
-            db.execSQL("CREATE INDEX images_url_index ON " + Obsolete.TABLE_IMAGES + "("
-                    + Obsolete.Images.URL + ")");
-            db.execSQL("CREATE UNIQUE INDEX images_guid_index ON " + Obsolete.TABLE_IMAGES + "("
-                    + Obsolete.Images.GUID + ")");
-            db.execSQL("CREATE INDEX images_modified_index ON " + Obsolete.TABLE_IMAGES + "("
-                    + Obsolete.Images.DATE_MODIFIED + ")");
-        }
-
-        private void createFaviconsTable(SQLiteDatabase db) {
-            debug("Creating " + TABLE_FAVICONS + " table");
-            db.execSQL("CREATE TABLE " + TABLE_FAVICONS + " (" +
-                    Favicons._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
-                    Favicons.URL + " TEXT UNIQUE," +
-                    Favicons.DATA + " BLOB," +
-                    Favicons.DATE_CREATED + " INTEGER," +
-                    Favicons.DATE_MODIFIED + " INTEGER" +
-                    ");");
-
-            db.execSQL("CREATE INDEX favicons_url_index ON " + TABLE_FAVICONS + "("
-                    + Favicons.URL + ")");
-            db.execSQL("CREATE INDEX favicons_modified_index ON " + TABLE_FAVICONS + "("
-                    + Favicons.DATE_MODIFIED + ")");
-        }
-
-        private void createThumbnailsTable(SQLiteDatabase db) {
-            debug("Creating " + TABLE_THUMBNAILS + " table");
-            db.execSQL("CREATE TABLE " + TABLE_THUMBNAILS + " (" +
-                    Thumbnails._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
-                    Thumbnails.URL + " TEXT UNIQUE," +
-                    Thumbnails.DATA + " BLOB" +
-                    ");");
-
-            db.execSQL("CREATE INDEX thumbnails_url_index ON " + TABLE_THUMBNAILS + "("
-                    + Thumbnails.URL + ")");
-        }
-
-        private void createBookmarksWithImagesView(SQLiteDatabase db) {
-            debug("Creating " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES + " view");
-
-            db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES + " AS " +
-                    "SELECT " + qualifyColumn(TABLE_BOOKMARKS, "*") +
-                    ", " + Obsolete.Images.FAVICON + ", " + Obsolete.Images.THUMBNAIL + " FROM " +
-                    Obsolete.TABLE_BOOKMARKS_JOIN_IMAGES);
-        }
-
-        private void createBookmarksWithFaviconsView(SQLiteDatabase db) {
-            debug("Creating " + VIEW_BOOKMARKS_WITH_FAVICONS + " view");
-
-            db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_BOOKMARKS_WITH_FAVICONS + " AS " +
-                    "SELECT " + qualifyColumn(TABLE_BOOKMARKS, "*") +
-                    ", " + qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Bookmarks.FAVICON +
-                    ", " + qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Bookmarks.FAVICON_URL +
-                    " FROM " + TABLE_BOOKMARKS_JOIN_FAVICONS);
-        }
-
-        private void createHistoryWithImagesView(SQLiteDatabase db) {
-            debug("Creating " + Obsolete.VIEW_HISTORY_WITH_IMAGES + " view");
-
-            db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES + " AS " +
-                    "SELECT " + qualifyColumn(TABLE_HISTORY, "*") +
-                    ", " + Obsolete.Images.FAVICON + ", " + Obsolete.Images.THUMBNAIL + " FROM " +
-                    Obsolete.TABLE_HISTORY_JOIN_IMAGES);
-        }
-
-        private void createHistoryWithFaviconsView(SQLiteDatabase db) {
-            debug("Creating " + VIEW_HISTORY_WITH_FAVICONS + " view");
-
-            db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_HISTORY_WITH_FAVICONS + " AS " +
-                    "SELECT " + qualifyColumn(TABLE_HISTORY, "*") +
-                    ", " + qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + History.FAVICON +
-                    ", " + qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + History.FAVICON_URL +
-                    " FROM " + TABLE_HISTORY_JOIN_FAVICONS);
-        }
-
-        private void createCombinedWithImagesView(SQLiteDatabase db) {
-            debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
-
-            db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
-                    " SELECT " + Combined.BOOKMARK_ID + ", " +
-                                 Combined.HISTORY_ID + ", " +
-                                 // We need to return an _id column because CursorAdapter requires it for its
-                                 // default implementation for the getItemId() method. However, since
-                                 // we're not using this feature in the parts of the UI using this view,
-                                 // we can just use 0 for all rows.
-                                 "0 AS " + Combined._ID + ", " +
-                                 Combined.URL + ", " +
-                                 Combined.TITLE + ", " +
-                                 Combined.VISITS + ", " +
-                                 Combined.DATE_LAST_VISITED + ", " +
-                                 qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
-                                 qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
-                    " FROM (" +
-                        // Bookmarks without history.
-                        " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
-                                     "-1 AS " + Combined.HISTORY_ID + ", " +
-                                     "-1 AS " + Combined.VISITS + ", " +
-                                     "-1 AS " + Combined.DATE_LAST_VISITED +
-                        " FROM " + TABLE_BOOKMARKS +
-                        " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
-                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
-                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
-                                        " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
-                        " UNION ALL" +
-                        // History with and without bookmark.
-                        " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
-                                     // Prioritze bookmark titles over history titles, since the user may have
-                                     // customized the title for a bookmark.
-                                     "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
-                                                   qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
-                        " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
-                            " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
-                        " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
-                                    qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ")" +
-                    ") LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
-                        " ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
-        }
-
-        private void createCombinedWithImagesViewOn9(SQLiteDatabase db) {
-            debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
-
-            db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
-                    " SELECT " + Combined.BOOKMARK_ID + ", " +
-                                 Combined.HISTORY_ID + ", " +
-                                 // We need to return an _id column because CursorAdapter requires it for its
-                                 // default implementation for the getItemId() method. However, since
-                                 // we're not using this feature in the parts of the UI using this view,
-                                 // we can just use 0 for all rows.
-                                 "0 AS " + Combined._ID + ", " +
-                                 Combined.URL + ", " +
-                                 Combined.TITLE + ", " +
-                                 Combined.VISITS + ", " +
-                                 Combined.DISPLAY + ", " +
-                                 Combined.DATE_LAST_VISITED + ", " +
-                                 qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
-                                 qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
-                    " FROM (" +
-                        // Bookmarks without history.
-                        " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
-                                     "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
-                                        Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
-                                        Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
-                                     "-1 AS " + Combined.HISTORY_ID + ", " +
-                                     "-1 AS " + Combined.VISITS + ", " +
-                                     "-1 AS " + Combined.DATE_LAST_VISITED +
-                        " FROM " + TABLE_BOOKMARKS +
-                        " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
-                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
-                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
-                                        " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
-                        " UNION ALL" +
-                        // History with and without bookmark.
-                        " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
-                                     // Prioritze bookmark titles over history titles, since the user may have
-                                     // customized the title for a bookmark.
-                                     "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
-                                                   qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
-                                     "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
-                                        Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
-                                        Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
-                        " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
-                            " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
-                        " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
-                                    qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ")" +
-                    ") LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
-                        " ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
-        }
-
-        private void createCombinedWithImagesViewOn10(SQLiteDatabase db) {
-            debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
-
-            db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
-                    " SELECT " + Combined.BOOKMARK_ID + ", " +
-                                 Combined.HISTORY_ID + ", " +
-                                 // We need to return an _id column because CursorAdapter requires it for its
-                                 // default implementation for the getItemId() method. However, since
-                                 // we're not using this feature in the parts of the UI using this view,
-                                 // we can just use 0 for all rows.
-                                 "0 AS " + Combined._ID + ", " +
-                                 Combined.URL + ", " +
-                                 Combined.TITLE + ", " +
-                                 Combined.VISITS + ", " +
-                                 Combined.DISPLAY + ", " +
-                                 Combined.DATE_LAST_VISITED + ", " +
-                                 qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
-                                 qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
-                    " FROM (" +
-                        // Bookmarks without history.
-                        " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
-                                     "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
-                                        Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
-                                        Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
-                                     "-1 AS " + Combined.HISTORY_ID + ", " +
-                                     "-1 AS " + Combined.VISITS + ", " +
-                                     "-1 AS " + Combined.DATE_LAST_VISITED +
-                        " FROM " + TABLE_BOOKMARKS +
-                        " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
-                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
-                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
-                                        " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
-                        " UNION ALL" +
-                        // History with and without bookmark.
-                        " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) +  " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
-                                     // Prioritze bookmark titles over history titles, since the user may have
-                                     // customized the title for a bookmark.
-                                     "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
-                                                   qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
-                                     "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
-                                        Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
-                                        Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
-                        " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
-                            " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
-                        " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
-                                    qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ")" +
-                    ") LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
-                        " ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
-        }
-
-        private void createCombinedWithImagesViewOn11(SQLiteDatabase db) {
-            debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
-
-            db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
-                    " SELECT " + Combined.BOOKMARK_ID + ", " +
-                                 Combined.HISTORY_ID + ", " +
-                                 // We need to return an _id column because CursorAdapter requires it for its
-                                 // default implementation for the getItemId() method. However, since
-                                 // we're not using this feature in the parts of the UI using this view,
-                                 // we can just use 0 for all rows.
-                                 "0 AS " + Combined._ID + ", " +
-                                 Combined.URL + ", " +
-                                 Combined.TITLE + ", " +
-                                 Combined.VISITS + ", " +
-                                 Combined.DISPLAY + ", " +
-                                 Combined.DATE_LAST_VISITED + ", " +
-                                 qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
-                                 qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
-                    " FROM (" +
-                        // Bookmarks without history.
-                        " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
-                                     "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
-                                        Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
-                                        Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
-                                     "-1 AS " + Combined.HISTORY_ID + ", " +
-                                     "-1 AS " + Combined.VISITS + ", " +
-                                     "-1 AS " + Combined.DATE_LAST_VISITED +
-                        " FROM " + TABLE_BOOKMARKS +
-                        " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
-                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
-                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
-                                        " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
-                        " UNION ALL" +
-                        // History with and without bookmark.
-                        " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) +  " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
-                                     // Prioritze bookmark titles over history titles, since the user may have
-                                     // customized the title for a bookmark.
-                                     "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
-                                                   qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
-                                     // Only use DISPLAY_READER if the matching bookmark entry inside reading
-                                     // list folder is not marked as deleted.
-                                     "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
-                                        " THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
-                                        Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
-                        " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
-                            " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
-                        " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
-                                    qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
-                    ") LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
-                        " ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
-        }
-
-        private void createCombinedViewOn12(SQLiteDatabase db) {
-            debug("Creating " + VIEW_COMBINED + " view");
-
-            db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" +
-                    " SELECT " + Combined.BOOKMARK_ID + ", " +
-                                 Combined.HISTORY_ID + ", " +
-                                 // We need to return an _id column because CursorAdapter requires it for its
-                                 // default implementation for the getItemId() method. However, since
-                                 // we're not using this feature in the parts of the UI using this view,
-                                 // we can just use 0 for all rows.
-                                 "0 AS " + Combined._ID + ", " +
-                                 Combined.URL + ", " +
-                                 Combined.TITLE + ", " +
-                                 Combined.VISITS + ", " +
-                                 Combined.DISPLAY + ", " +
-                                 Combined.DATE_LAST_VISITED +
-                    " FROM (" +
-                        // Bookmarks without history.
-                        " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
-                                     "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
-                                        Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
-                                        Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
-                                     "-1 AS " + Combined.HISTORY_ID + ", " +
-                                     "-1 AS " + Combined.VISITS + ", " +
-                                     "-1 AS " + Combined.DATE_LAST_VISITED +
-                        " FROM " + TABLE_BOOKMARKS +
-                        " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
-                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
-                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
-                                        " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
-                        " UNION ALL" +
-                        // History with and without bookmark.
-                        " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) +  " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
-                                     // Prioritze bookmark titles over history titles, since the user may have
-                                     // customized the title for a bookmark.
-                                     "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
-                                                   qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
-                                     // Only use DISPLAY_READER if the matching bookmark entry inside reading
-                                     // list folder is not marked as deleted.
-                                     "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
-                                        " THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
-                                        Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
-                        " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
-                            " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
-                        " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
-                                    qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
-                    ")");
-
-            debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
-
-            db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
-                    " SELECT *, " +
-                        qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
-                        qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
-                    " FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
-                        " ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
-        }
-
-        private void createCombinedViewOn13(SQLiteDatabase db) {
-            debug("Creating " + VIEW_COMBINED + " view");
-
-            db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" +
-                    " SELECT " + Combined.BOOKMARK_ID + ", " +
-                                 Combined.HISTORY_ID + ", " +
-                                 // We need to return an _id column because CursorAdapter requires it for its
-                                 // default implementation for the getItemId() method. However, since
-                                 // we're not using this feature in the parts of the UI using this view,
-                                 // we can just use 0 for all rows.
-                                 "0 AS " + Combined._ID + ", " +
-                                 Combined.URL + ", " +
-                                 Combined.TITLE + ", " +
-                                 Combined.VISITS + ", " +
-                                 Combined.DISPLAY + ", " +
-                                 Combined.DATE_LAST_VISITED + ", " +
-                                 Combined.FAVICON_ID +
-                    " FROM (" +
-                        // Bookmarks without history.
-                        " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
-                                     "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
-                                        Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
-                                        Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
-                                     "-1 AS " + Combined.HISTORY_ID + ", " +
-                                     "-1 AS " + Combined.VISITS + ", " +
-                                     "-1 AS " + Combined.DATE_LAST_VISITED + ", " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
-                        " FROM " + TABLE_BOOKMARKS +
-                        " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
-                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
-                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
-                                        " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
-                        " UNION ALL" +
-                        // History with and without bookmark.
-                        " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) +  " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
-                                     // Prioritize bookmark titles over history titles, since the user may have
-                                     // customized the title for a bookmark.
-                                     "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
-                                                   qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
-                                     // Only use DISPLAY_READER if the matching bookmark entry inside reading
-                                     // list folder is not marked as deleted.
-                                     "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
-                                        " THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
-                                        Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
-                        " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
-                            " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
-                        " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
-                                    qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
-                    ")");
-
-            debug("Creating " + VIEW_COMBINED_WITH_FAVICONS + " view");
-
-            db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED_WITH_FAVICONS + " AS" +
-                    " SELECT " + qualifyColumn(VIEW_COMBINED, "*") + ", " +
-                        qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Combined.FAVICON_URL + ", " +
-                        qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Combined.FAVICON +
-                    " FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + TABLE_FAVICONS +
-                        " ON " + Combined.FAVICON_ID + " = " + qualifyColumn(TABLE_FAVICONS, Favicons._ID));
-        }
-
-        private void createCombinedViewOn16(SQLiteDatabase db) {
-            debug("Creating " + VIEW_COMBINED + " view");
-
-            db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" +
-                    " SELECT " + Combined.BOOKMARK_ID + ", " +
-                                 Combined.HISTORY_ID + ", " +
-                                 // We need to return an _id column because CursorAdapter requires it for its
-                                 // default implementation for the getItemId() method. However, since
-                                 // we're not using this feature in the parts of the UI using this view,
-                                 // we can just use 0 for all rows.
-                                 "0 AS " + Combined._ID + ", " +
-                                 Combined.URL + ", " +
-                                 Combined.TITLE + ", " +
-                                 Combined.VISITS + ", " +
-                                 Combined.DISPLAY + ", " +
-                                 Combined.DATE_LAST_VISITED + ", " +
-                                 Combined.FAVICON_ID +
-                    " FROM (" +
-                        // Bookmarks without history.
-                        " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
-                                     "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
-                                        Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
-                                        Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
-                                     "-1 AS " + Combined.HISTORY_ID + ", " +
-                                     "-1 AS " + Combined.VISITS + ", " +
-                                     "-1 AS " + Combined.DATE_LAST_VISITED + ", " +
-                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
-                        " FROM " + TABLE_BOOKMARKS +
-                        " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
-                                    // Ignore pinned bookmarks.
-                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT)  + " <> " + Bookmarks.FIXED_PINNED_LIST_ID + " AND " +
-                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
-                                    qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
-                                        " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
-                        " UNION ALL" +
-                        // History with and without bookmark.
-                        " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
-                                        // Give pinned bookmarks a NULL ID so that they're not treated as bookmarks. We can't
-                                        // completely ignore them here because they're joined with history entries we care about.
-                                        "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
-                                        Bookmarks.FIXED_PINNED_LIST_ID + " THEN NULL ELSE " +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " END " +
-                                     "ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
-                                     // Prioritize bookmark titles over history titles, since the user may have
-                                     // customized the title for a bookmark.
-                                     "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
-                                                   qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
-                                     // Only use DISPLAY_READER if the matching bookmark entry inside reading
-                                     // list folder is not marked as deleted.
-                                     "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
-                                        " THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
-                                        Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED + ", " +
-                                     qualifyColumn(TABLE_HISTORY, History.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
-                        " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
-                            " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
-                        " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
-                                    qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
-                                        qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
-                    ")");
-
-            debug("Creating " + VIEW_COMBINED_WITH_FAVICONS + " view");
-
-            db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED_WITH_FAVICONS + " AS" +
-                    " SELECT " + qualifyColumn(VIEW_COMBINED, "*") + ", " +
-                        qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Combined.FAVICON_URL + ", " +
-                        qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Combined.FAVICON +
-                    " FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + TABLE_FAVICONS +
-                        " ON " + Combined.FAVICON_ID + " = " + qualifyColumn(TABLE_FAVICONS, Favicons._ID));
-        }
-
-        @Override
-        public void onCreate(SQLiteDatabase db) {
-            debug("Creating browser.db: " + db.getPath());
-
-            createBookmarksTableOn13(db);
-            createHistoryTableOn13(db);
-            createFaviconsTable(db);
-            createThumbnailsTable(db);
-
-            createBookmarksWithFaviconsView(db);
-            createHistoryWithFaviconsView(db);
-            createCombinedViewOn16(db);
-
-            createOrUpdateSpecialFolder(db, Bookmarks.PLACES_FOLDER_GUID,
-                R.string.bookmarks_folder_places, 0);
-
-            createOrUpdateAllSpecialFolders(db);
-
-            // Create distribution bookmarks before our own default bookmarks
-            int pos = createDistributionBookmarks(db);
-            createDefaultBookmarks(db, pos);
-        }
-
-        private String getLocalizedProperty(JSONObject bookmark, String property, Locale locale) throws JSONException {
-            // Try the full locale
-            String fullLocale = property + "." + locale.toString();
-            if (bookmark.has(fullLocale)) {
-                return bookmark.getString(fullLocale);
-            }
-            // Try without a variant
-            if (!TextUtils.isEmpty(locale.getVariant())) {
-                String noVariant = fullLocale.substring(0, fullLocale.lastIndexOf("_"));
-                if (bookmark.has(noVariant)) {
-                    return bookmark.getString(noVariant);
-                }
-            }
-            // Try just the language
-            String lang = property + "." + locale.getLanguage();
-            if (bookmark.has(lang)) {
-                return bookmark.getString(lang);
-            }
-            // Default to the non-localized property name
-            return bookmark.getString(property);
-        }
-
-        // Returns the number of bookmarks inserted in the db
-        private int createDistributionBookmarks(SQLiteDatabase db) {
-            JSONArray bookmarks = Distribution.getBookmarks(mContext);
-            if (bookmarks == null) {
-                return 0;
-            }
-
-            Locale locale = Locale.getDefault();
-            int pos = 0;
-            Integer mobileFolderId = getMobileFolderId(db);
-            if (mobileFolderId == null) {
-                Log.e(LOGTAG, "Error creating distribution bookmarks: mobileFolderId is null");
-                return 0;
-            }
-
-            for (int i = 0; i < bookmarks.length(); i++) {
-                try {
-                    final JSONObject bookmark = bookmarks.getJSONObject(i);
-
-                    String title = getLocalizedProperty(bookmark, "title", locale);
-                    final String url = getLocalizedProperty(bookmark, "url", locale);
-                    createBookmark(db, title, url, pos, mobileFolderId);
-
-                    if (bookmark.has("pinned")) {
-                        try {
-                            // Create a fake bookmark in the hidden pinned folder to pin bookmark
-                            // to about:home top sites. Pass pos as the pinned position to pin
-                            // sites in the order that bookmarks are specified in bookmarks.json.
-                            if (bookmark.getBoolean("pinned")) {
-                                createBookmark(db, title, url, pos, Bookmarks.FIXED_PINNED_LIST_ID);
-                            }
-                        } catch (JSONException e) {
-                            Log.e(LOGTAG, "Error pinning bookmark to top sites", e);
-                        }
-                    }
-
-                    pos++;
-
-                    // return early if there is no icon for this bookmark
-                    if (!bookmark.has("icon")) {
-                        continue;
-                    }
-
-                    // create icons in a separate thread to avoid blocking about:home on startup
-                    ThreadUtils.postToBackgroundThread(new Runnable() {
-                        @Override
-                        public void run() {
-                            SQLiteDatabase db = getWritableDatabase();
-                            try {
-                                String iconData = bookmark.getString("icon");
-                                Bitmap icon = BitmapUtils.getBitmapFromDataURI(iconData);
-                                if (icon != null) {
-                                    createFavicon(db, url, icon);
-                                }
-                            } catch (JSONException e) {
-                                Log.e(LOGTAG, "Error creating distribution bookmark icon", e);
-                            }
-                        }
-                    });
-                } catch (JSONException e) {
-                    Log.e(LOGTAG, "Error creating distribution bookmark", e);
-                }
-            }
-            return pos;
-        }
-
-        // Inserts default bookmarks, starting at a specified position
-        private void createDefaultBookmarks(SQLiteDatabase db, int pos) {
-            Class<?> stringsClass = R.string.class;
-            Field[] fields = stringsClass.getFields();
-            Pattern p = Pattern.compile("^bookmarkdefaults_title_");
-
-            Integer mobileFolderId = getMobileFolderId(db);
-            if (mobileFolderId == null) {
-                Log.e(LOGTAG, "Error creating default bookmarks: mobileFolderId is null");
-                return;
-            }
-
-            for (int i = 0; i < fields.length; i++) {
-                final String name = fields[i].getName();
-                Matcher m = p.matcher(name);
-                if (!m.find()) {
-                    continue;
-                }
-                try {
-                    int titleid = fields[i].getInt(null);
-                    String title = mContext.getString(titleid);
-
-                    Field urlField = stringsClass.getField(name.replace("_title_", "_url_"));
-                    int urlId = urlField.getInt(null);
-                    final String url = mContext.getString(urlId);
-                    createBookmark(db, title, url, pos, mobileFolderId);
-
-                    // create icons in a separate thread to avoid blocking about:home on startup
-                    ThreadUtils.postToBackgroundThread(new Runnable() {
-                        @Override
-                        public void run() {
-                            SQLiteDatabase db = getWritableDatabase();
-                            Bitmap icon = getDefaultFaviconFromPath(name);
-                            if (icon == null) {
-                                icon = getDefaultFaviconFromDrawable(name);
-                            }
-                            if (icon != null) {
-                                createFavicon(db, url, icon);
-                            }
-                        }
-                    });
-                    pos++;
-                } catch (java.lang.IllegalAccessException ex) {
-                    Log.e(LOGTAG, "Can't create bookmark " + name, ex);
-                } catch (java.lang.NoSuchFieldException ex) {
-                    Log.e(LOGTAG, "Can't create bookmark " + name, ex);
-                }
-            }
-        }
-
-        private void createBookmark(SQLiteDatabase db, String title, String url, int pos, int parent) {
-            ContentValues bookmarkValues = new ContentValues();
-            bookmarkValues.put(Bookmarks.PARENT, parent);
-
-            long now = System.currentTimeMillis();
-            bookmarkValues.put(Bookmarks.DATE_CREATED, now);
-            bookmarkValues.put(Bookmarks.DATE_MODIFIED, now);
-
-            bookmarkValues.put(Bookmarks.TITLE, title);
-            bookmarkValues.put(Bookmarks.URL, url);
-            bookmarkValues.put(Bookmarks.GUID, Utils.generateGuid());
-            bookmarkValues.put(Bookmarks.POSITION, pos);
-            db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.TITLE, bookmarkValues);
-        }
-
-        private void createFavicon(SQLiteDatabase db, String url, Bitmap icon) {
-            ByteArrayOutputStream stream = new ByteArrayOutputStream();
-
-            ContentValues iconValues = new ContentValues();
-            iconValues.put(Favicons.PAGE_URL, url);
-
-            byte[] data = null;
-            if (icon.compress(Bitmap.CompressFormat.PNG, 100, stream)) {
-                data = stream.toByteArray();
-            } else {
-                Log.w(LOGTAG, "Favicon compression failed.");
-            }
-            iconValues.put(Favicons.DATA, data);
-
-            insertFavicon(db, iconValues);
-        }
-
-        private Bitmap getDefaultFaviconFromPath(String name) {
-            Class<?> stringClass = R.string.class;
-            try {
-                // Look for a drawable with the id R.drawable.bookmarkdefaults_favicon_*
-                Field faviconField = stringClass.getField(name.replace("_title_", "_favicon_"));
-                if (faviconField == null) {
-                    return null;
-                }
-                int faviconId = faviconField.getInt(null);
-                String path = mContext.getString(faviconId);
-
-                String apkPath = mContext.getPackageResourcePath();
-                File apkFile = new File(apkPath);
-                String bitmapPath = "jar:jar:" + apkFile.toURI() + "!/" + AppConstants.OMNIJAR_NAME + "!/" + path;
-                return GeckoJarReader.getBitmap(mContext.getResources(), bitmapPath);
-            } catch (java.lang.IllegalAccessException ex) {
-                Log.e(LOGTAG, "[Path] Can't create favicon " + name, ex);
-            } catch (java.lang.NoSuchFieldException ex) {
-                // If the field does not exist, that means we intend to load via a drawable
-            }
-            return null;
-        }
-
-        private Bitmap getDefaultFaviconFromDrawable(String name) {
-            Class<?> drawablesClass = R.drawable.class;
-            try {
-                // Look for a drawable with the id R.drawable.bookmarkdefaults_favicon_*
-                Field faviconField = drawablesClass.getField(name.replace("_title_", "_favicon_"));
-                if (faviconField == null) {
-                    return null;
-                }
-                int faviconId = faviconField.getInt(null);
-                return BitmapUtils.decodeResource(mContext, faviconId);
-            } catch (java.lang.IllegalAccessException ex) {
-                Log.e(LOGTAG, "[Drawable] Can't create favicon " + name, ex);
-            } catch (java.lang.NoSuchFieldException ex) {
-                // If the field does not exist, that means we intend to load via a file path
-            }
-            return null;
-        }
-
-        private void createOrUpdateAllSpecialFolders(SQLiteDatabase db) {
-            createOrUpdateSpecialFolder(db, Bookmarks.MOBILE_FOLDER_GUID,
-                R.string.bookmarks_folder_mobile, 0);
-            createOrUpdateSpecialFolder(db, Bookmarks.TOOLBAR_FOLDER_GUID,
-                R.string.bookmarks_folder_toolbar, 1);
-            createOrUpdateSpecialFolder(db, Bookmarks.MENU_FOLDER_GUID,
-                R.string.bookmarks_folder_menu, 2);
-            createOrUpdateSpecialFolder(db, Bookmarks.TAGS_FOLDER_GUID,
-                R.string.bookmarks_folder_tags, 3);
-            createOrUpdateSpecialFolder(db, Bookmarks.UNFILED_FOLDER_GUID,
-                R.string.bookmarks_folder_unfiled, 4);
-            createOrUpdateSpecialFolder(db, Bookmarks.READING_LIST_FOLDER_GUID,
-                R.string.bookmarks_folder_reading_list, 5);
-            createOrUpdateSpecialFolder(db, Bookmarks.PINNED_FOLDER_GUID,
-                R.string.bookmarks_folder_pinned, 6);
-        }
-
-        private void createOrUpdateSpecialFolder(SQLiteDatabase db,
-                String guid, int titleId, int position) {
-            ContentValues values = new ContentValues();
-            values.put(Bookmarks.GUID, guid);
-            values.put(Bookmarks.TYPE, Bookmarks.TYPE_FOLDER);
-            values.put(Bookmarks.POSITION, position);
-
-            if (guid.equals(Bookmarks.PLACES_FOLDER_GUID))
-                values.put(Bookmarks._ID, Bookmarks.FIXED_ROOT_ID);
-            else if (guid.equals(Bookmarks.READING_LIST_FOLDER_GUID))
-                values.put(Bookmarks._ID, Bookmarks.FIXED_READING_LIST_ID);
-            else if (guid.equals(Bookmarks.PINNED_FOLDER_GUID))
-                values.put(Bookmarks._ID, Bookmarks.FIXED_PINNED_LIST_ID);
-
-            // Set the parent to 0, which sync assumes is the root
-            values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID);
-
-            String title = mContext.getResources().getString(titleId);
-            values.put(Bookmarks.TITLE, title);
-
-            long now = System.currentTimeMillis();
-            values.put(Bookmarks.DATE_CREATED, now);
-            values.put(Bookmarks.DATE_MODIFIED, now);
-
-            int updated = db.update(TABLE_BOOKMARKS, values,
-                                    Bookmarks.GUID + " = ?",
-                                    new String[] { guid });
-
-            if (updated == 0) {
-                db.insert(TABLE_BOOKMARKS, Bookmarks.GUID, values);
-                debug("Inserted special folder: " + guid);
-            } else {
-                debug("Updated special folder: " + guid);
-            }
-        }
-
-        private boolean isSpecialFolder(ContentValues values) {
-            String guid = values.getAsString(Bookmarks.GUID);
-            if (guid == null)
-                return false;
-
-            return guid.equals(Bookmarks.MOBILE_FOLDER_GUID) ||
-                   guid.equals(Bookmarks.MENU_FOLDER_GUID) ||
-                   guid.equals(Bookmarks.TOOLBAR_FOLDER_GUID) ||
-                   guid.equals(Bookmarks.UNFILED_FOLDER_GUID) ||
-                   guid.equals(Bookmarks.TAGS_FOLDER_GUID);
-        }
-
-        private void migrateBookmarkFolder(SQLiteDatabase db, int folderId,
-                BookmarkMigrator migrator) {
-            Cursor c = null;
-
-            debug("Migrating bookmark folder with id = " + folderId);
-
-            String selection = Bookmarks.PARENT + " = " + folderId;
-            String[] selectionArgs = null;
-
-            boolean isRootFolder = (folderId == Bookmarks.FIXED_ROOT_ID);
-
-            // If we're loading the root folder, we have to account for
-            // any previously created special folder that was created without
-            // setting a parent id (e.g. mobile folder) and making sure we're
-            // not adding any infinite recursion as root's parent is root itself.
-            if (isRootFolder) {
-                selection = Bookmarks.GUID + " != ?" + " AND (" +
-                            selection + " OR " + Bookmarks.PARENT + " = NULL)";
-                selectionArgs = new String[] { Bookmarks.PLACES_FOLDER_GUID };
-            }
-
-            List<Integer> subFolders = new ArrayList<Integer>();
-            List<ContentValues> invalidSpecialEntries = new ArrayList<ContentValues>();
-
-            try {
-                c = db.query(TABLE_BOOKMARKS_TMP,
-                             null,
-                             selection,
-                             selectionArgs,
-                             null, null, null);
-
-                // The key point here is that bookmarks should be added in
-                // parent order to avoid any problems with the foreign key
-                // in Bookmarks.PARENT.
-                while (c.moveToNext()) {
-                    ContentValues values = new ContentValues();
-
-                    // We're using a null projection in the query which
-                    // means we're getting all columns from the table.
-                    // It's safe to simply transform the row into the
-                    // values to be inserted on the new table.
-                    DatabaseUtils.cursorRowToContentValues(c, values);
-
-                    boolean isSpecialFolder = isSpecialFolder(values);
-
-                    // The mobile folder used to be created with PARENT = NULL.
-                    // We want fix that here.
-                    if (values.getAsLong(Bookmarks.PARENT) == null && isSpecialFolder)
-                        values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID);
-
-                    if (isRootFolder && !isSpecialFolder) {
-                        invalidSpecialEntries.add(values);
-                        continue;
-                    }
-
-                    if (migrator != null)
-                        migrator.updateForNewTable(values);
-
-                    debug("Migrating bookmark: " + values.getAsString(Bookmarks.TITLE));
-                    db.insert(TABLE_BOOKMARKS, Bookmarks.URL, values);
-
-                    Integer type = values.getAsInteger(Bookmarks.TYPE);
-                    if (type != null && type == Bookmarks.TYPE_FOLDER)
-                        subFolders.add(values.getAsInteger(Bookmarks._ID));
-                }
-            } finally {
-                if (c != null)
-                    c.close();
-            }
-
-            // At this point is safe to assume that the mobile folder is
-            // in the new table given that we've always created it on
-            // database creation time.
-            final int nInvalidSpecialEntries = invalidSpecialEntries.size();
-            if (nInvalidSpecialEntries > 0) {
-                Integer mobileFolderId = getMobileFolderId(db);
-                if (mobileFolderId == null) {
-                    Log.e(LOGTAG, "Error migrating invalid special folder entries: mobile folder id is null");
-                    return;
-                }
-
-                debug("Found " + nInvalidSpecialEntries + " invalid special folder entries");
-                for (int i = 0; i < nInvalidSpecialEntries; i++) {
-                    ContentValues values = invalidSpecialEntries.get(i);
-                    values.put(Bookmarks.PARENT, mobileFolderId);
-
-                    db.insert(TABLE_BOOKMARKS, Bookmarks.URL, values);
-                }
-            }
-
-            final int nSubFolders = subFolders.size();
-            for (int i = 0; i < nSubFolders; i++) {
-                int subFolderId = subFolders.get(i);
-                migrateBookmarkFolder(db, subFolderId, migrator);
-            }
-        }
-
-        private void migrateBookmarksTable(SQLiteDatabase db) {
-            migrateBookmarksTable(db, null);
-        }
-
-        private void migrateBookmarksTable(SQLiteDatabase db, BookmarkMigrator migrator) {
-            debug("Renaming bookmarks table to " + TABLE_BOOKMARKS_TMP);
-            db.execSQL("ALTER TABLE " + TABLE_BOOKMARKS +
-                       " RENAME TO " + TABLE_BOOKMARKS_TMP);
-
-            debug("Dropping views and indexes related to " + TABLE_BOOKMARKS);
-            db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES);
-
-            db.execSQL("DROP INDEX IF EXISTS bookmarks_url_index");
-            db.execSQL("DROP INDEX IF EXISTS bookmarks_type_deleted_index");
-            db.execSQL("DROP INDEX IF EXISTS bookmarks_guid_index");
-            db.execSQL("DROP INDEX IF EXISTS bookmarks_modified_index");
-
-            createBookmarksTable(db);
-            createBookmarksWithImagesView(db);
-
-            createOrUpdateSpecialFolder(db, Bookmarks.PLACES_FOLDER_GUID,
-                R.string.bookmarks_folder_places, 0);
-
-            migrateBookmarkFolder(db, Bookmarks.FIXED_ROOT_ID, migrator);
-
-            // Ensure all special folders exist and have the
-            // right folder hierarchy.
-            createOrUpdateAllSpecialFolders(db);
-
-            debug("Dropping bookmarks temporary table");
-            db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS_TMP);
-        }
-
-
-        private void migrateHistoryTable(SQLiteDatabase db) {
-            debug("Renaming history table to " + TABLE_HISTORY_TMP);
-            db.execSQL("ALTER TABLE " + TABLE_HISTORY +
-                       " RENAME TO " + TABLE_HISTORY_TMP);
-
-            debug("Dropping views and indexes related to " + TABLE_HISTORY);
-            db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
-            db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
-
-            db.execSQL("DROP INDEX IF EXISTS history_url_index");
-            db.execSQL("DROP INDEX IF EXISTS history_guid_index");
-            db.execSQL("DROP INDEX IF EXISTS history_modified_index");
-            db.execSQL("DROP INDEX IF EXISTS history_visited_index");
-
-            createHistoryTable(db);
-            createHistoryWithImagesView(db);
-            createCombinedWithImagesView(db);
-
-            db.execSQL("INSERT INTO " + TABLE_HISTORY + " SELECT * FROM " + TABLE_HISTORY_TMP);
-
-            debug("Dropping history temporary table");
-            db.execSQL("DROP TABLE IF EXISTS " + TABLE_HISTORY_TMP);
-        }
-
-        private void migrateImagesTable(SQLiteDatabase db) {
-            debug("Renaming images table to " + TABLE_IMAGES_TMP);
-            db.execSQL("ALTER TABLE " + Obsolete.TABLE_IMAGES +
-                       " RENAME TO " + TABLE_IMAGES_TMP);
-
-            debug("Dropping views and indexes related to " + Obsolete.TABLE_IMAGES);
-            db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
-            db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
-
-            db.execSQL("DROP INDEX IF EXISTS images_url_index");
-            db.execSQL("DROP INDEX IF EXISTS images_guid_index");
-            db.execSQL("DROP INDEX IF EXISTS images_modified_index");
-
-            createImagesTable(db);
-            createHistoryWithImagesView(db);
-            createCombinedWithImagesView(db);
-
-            db.execSQL("INSERT INTO " + Obsolete.TABLE_IMAGES + " SELECT * FROM " + TABLE_IMAGES_TMP);
-
-            debug("Dropping images temporary table");
-            db.execSQL("DROP TABLE IF EXISTS " + TABLE_IMAGES_TMP);
-        }
-
-        private void upgradeDatabaseFrom1to2(SQLiteDatabase db) {
-            migrateBookmarksTable(db);
-        }
-
-        private void upgradeDatabaseFrom2to3(SQLiteDatabase db) {
-            debug("Dropping view: " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES);
-            db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES);
-
-            createBookmarksWithImagesView(db);
-
-            debug("Dropping view: " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
-            db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
-
-            createHistoryWithImagesView(db);
-        }
-
-        private void upgradeDatabaseFrom3to4(SQLiteDatabase db) {
-            migrateBookmarksTable(db, new BookmarkMigrator3to4());
-        }
-
-        private void upgradeDatabaseFrom4to5(SQLiteDatabase db) {
-            createCombinedWithImagesView(db);
-        }
-
-        private void upgradeDatabaseFrom5to6(SQLiteDatabase db) {
-            debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
-            db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
-
-            createCombinedWithImagesView(db);
-        }
-
-        private void upgradeDatabaseFrom6to7(SQLiteDatabase db) {
-            debug("Removing history visits with NULL GUIDs");
-            db.execSQL("DELETE FROM " + TABLE_HISTORY + " WHERE " + History.GUID + " IS NULL");
-
-            debug("Update images with NULL GUIDs");
-            String[] columns = new String[] { Obsolete.Images._ID };
-            Cursor cursor = null;
-            try {
-              cursor = db.query(Obsolete.TABLE_IMAGES, columns, Obsolete.Images.GUID + " IS NULL", null, null ,null, null, null);
-              ContentValues values = new ContentValues();
-              if (cursor.moveToFirst()) {
-                  do {
-                      values.put(Obsolete.Images.GUID, Utils.generateGuid());
-                      db.update(Obsolete.TABLE_IMAGES, values, Obsolete.Images._ID + " = ?", new String[] {
-                        cursor.getString(cursor.getColumnIndexOrThrow(Obsolete.Images._ID))
-                      });
-                  } while (cursor.moveToNext());
-              }
-            } finally {
-              if (cursor != null)
-                cursor.close();
-            }
-
-            migrateBookmarksTable(db);
-            migrateHistoryTable(db);
-            migrateImagesTable(db);
-        }
-
-        private void upgradeDatabaseFrom7to8(SQLiteDatabase db) {
-            debug("Combining history entries with the same URL");
-
-            final String TABLE_DUPES = "duped_urls";
-            final String TOTAL = "total";
-            final String LATEST = "latest";
-            final String WINNER = "winner";
-
-            db.execSQL("CREATE TEMP TABLE " + TABLE_DUPES + " AS" +
-                      " SELECT " + History.URL + ", " +
-                                  "SUM(" + History.VISITS + ") AS " + TOTAL + ", " +
-                                  "MAX(" + History.DATE_MODIFIED + ") AS " + LATEST + ", " +
-                                  "MAX(" + History._ID + ") AS " + WINNER +
-                      " FROM " + TABLE_HISTORY +
-                      " GROUP BY " + History.URL +
-                      " HAVING count(" + History.URL + ") > 1");
-
-            db.execSQL("CREATE UNIQUE INDEX " + TABLE_DUPES + "_url_index ON " +
-                       TABLE_DUPES + " (" + History.URL + ")");
-
-            final String fromClause = " FROM " + TABLE_DUPES + " WHERE " +
-                                      qualifyColumn(TABLE_DUPES, History.URL) + " = " +
-                                      qualifyColumn(TABLE_HISTORY, History.URL);
-
-            db.execSQL("UPDATE " + TABLE_HISTORY +
-                      " SET " + History.VISITS + " = (SELECT " + TOTAL + fromClause + "), " +
-                                History.DATE_MODIFIED + " = (SELECT " + LATEST + fromClause + "), " +
-                                History.IS_DELETED + " = " +
-                                    "(" + History._ID + " <> (SELECT " + WINNER + fromClause + "))" +
-                      " WHERE " + History.URL + " IN (SELECT " + History.URL + " FROM " + TABLE_DUPES + ")");
-
-            db.execSQL("DROP TABLE " + TABLE_DUPES);
-        }
-
-        private void upgradeDatabaseFrom8to9(SQLiteDatabase db) {
-            createOrUpdateSpecialFolder(db, Bookmarks.READING_LIST_FOLDER_GUID,
-                R.string.bookmarks_folder_reading_list, 5);
-
-            debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
-            db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
-
-            createCombinedWithImagesViewOn9(db);
-        }
-
-        private void upgradeDatabaseFrom9to10(SQLiteDatabase db) {
-            debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
-            db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
-
-            createCombinedWithImagesViewOn10(db);
-        }
-
-        private void upgradeDatabaseFrom10to11(SQLiteDatabase db) {
-            debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
-            db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
-
-            db.execSQL("CREATE INDEX bookmarks_type_deleted_index ON " + TABLE_BOOKMARKS + "("
-                    + Bookmarks.TYPE + ", " + Bookmarks.IS_DELETED + ")");
-
-            createCombinedWithImagesViewOn11(db);
-        }
-
-        private void upgradeDatabaseFrom11to12(SQLiteDatabase db) {
-            debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
-            db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
-
-            createCombinedViewOn12(db);
-        }
-
-        private void upgradeDatabaseFrom12to13(SQLiteDatabase db) {
-            // Update images table with favicon URLs
-            SQLiteDatabase faviconsDb = null;
-            Cursor c = null;
-            try {
-                final String FAVICON_TABLE = "favicon_urls";
-                final String FAVICON_URL = "favicon_url";
-                final String FAVICON_PAGE = "page_url";
-
-                String dbPath = mContext.getDatabasePath(Obsolete.FAVICON_DB).getPath();
-                faviconsDb = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY);
-                String[] columns = new String[] { FAVICON_URL, FAVICON_PAGE };
-                c = faviconsDb.query(FAVICON_TABLE, columns, null, null, null, null, null, null);
-                int faviconIndex = c.getColumnIndexOrThrow(FAVICON_URL);
-                int pageIndex = c.getColumnIndexOrThrow(FAVICON_PAGE);
-                while (c.moveToNext()) {
-                    ContentValues values = new ContentValues(1);
-                    String faviconUrl = c.getString(faviconIndex);
-                    String pageUrl = c.getString(pageIndex);
-                    values.put(FAVICON_URL, faviconUrl);
-                    db.update(Obsolete.TABLE_IMAGES, values, Obsolete.Images.URL + " = ?", new String[] { pageUrl });
-                }
-            } catch (SQLException e) {
-                // If we can't read from the database for some reason, we won't
-                // be able to import the favicon URLs. This isn't a fatal
-                // error, so continue the upgrade.
-                Log.e(LOGTAG, "Exception importing from " + Obsolete.FAVICON_DB, e);
-            } finally {
-                if (c != null)
-                    c.close();
-                if (faviconsDb != null)
-                    faviconsDb.close();
-            }
-
-            createFaviconsTable(db);
-
-            // Import favicons into the favicons table
-            db.execSQL("ALTER TABLE " + TABLE_HISTORY
-                    + " ADD COLUMN " + History.FAVICON_ID + " INTEGER");
-            db.execSQL("ALTER TABLE " + TABLE_BOOKMARKS
-                    + " ADD COLUMN " + Bookmarks.FAVICON_ID + " INTEGER");
-
-            try {
-                c = db.query(Obsolete.TABLE_IMAGES,
-                        new String[] {
-                            Obsolete.Images.URL,
-                            Obsolete.Images.FAVICON_URL,
-                            Obsolete.Images.FAVICON,
-                            Obsolete.Images.DATE_MODIFIED,
-                            Obsolete.Images.DATE_CREATED
-                        },
-                        Obsolete.Images.FAVICON + " IS NOT NULL",
-                        null, null, null, null);
-
-                while (c.moveToNext()) {
-                    long faviconId = -1;
-                    int faviconUrlIndex = c.getColumnIndexOrThrow(Obsolete.Images.FAVICON_URL);
-                    String faviconUrl = null;
-                    if (!c.isNull(faviconUrlIndex)) {
-                        faviconUrl = c.getString(faviconUrlIndex);
-                        Cursor c2 = null;
-                        try {
-                            c2 = db.query(TABLE_FAVICONS,
-                                    new String[] { Favicons._ID },
-                                    Favicons.URL + " = ?",
-                                    new String[] { faviconUrl },
-                                    null, null, null);
-                            if (c2.moveToFirst()) {
-                                faviconId = c2.getLong(c2.getColumnIndexOrThrow(Favicons._ID));
-                            }
-                        } finally {
-                            if (c2 != null)
-                                c2.close();
-                        }
-                    }
-
-                    if (faviconId == -1) {
-                        ContentValues values = new ContentValues(4);
-                        values.put(Favicons.URL, faviconUrl);
-                        values.put(Favicons.DATA, c.getBlob(c.getColumnIndexOrThrow(Obsolete.Images.FAVICON)));
-                        values.put(Favicons.DATE_MODIFIED, c.getLong(c.getColumnIndexOrThrow(Obsolete.Images.DATE_MODIFIED)));
-                        values.put(Favicons.DATE_CREATED, c.getLong(c.getColumnIndexOrThrow(Obsolete.Images.DATE_CREATED)));
-                        faviconId = db.insert(TABLE_FAVICONS, null, values);
-                    }
-
-                    ContentValues values = new ContentValues(1);
-                    values.put(FaviconColumns.FAVICON_ID, faviconId);
-                    db.update(TABLE_HISTORY, values, History.URL + " = ?",
-                            new String[] { c.getString(c.getColumnIndexOrThrow(Obsolete.Images.URL)) });
-                    db.update(TABLE_BOOKMARKS, values, Bookmarks.URL + " = ?",
-                            new String[] { c.getString(c.getColumnIndexOrThrow(Obsolete.Images.URL)) });
-                }
-            } finally {
-                if (c != null)
-                    c.close();
-            }
-
-            createThumbnailsTable(db);
-
-            // Import thumbnails into the thumbnails table
-            db.execSQL("INSERT INTO " + TABLE_THUMBNAILS + " ("
-                    + Thumbnails.URL + ", "
-                    + Thumbnails.DATA + ") "
-                    + "SELECT " + Obsolete.Images.URL + ", " + Obsolete.Images.THUMBNAIL
-                    + " FROM " + Obsolete.TABLE_IMAGES
-                    + " WHERE " + Obsolete.Images.THUMBNAIL + " IS NOT NULL");
-
-            db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES);
-            db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
-            db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
-            db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
-
-            createBookmarksWithFaviconsView(db);
-            createHistoryWithFaviconsView(db);
-            createCombinedViewOn13(db);
-
-            db.execSQL("DROP TABLE IF EXISTS " + Obsolete.TABLE_IMAGES);
-        }
-
-        private void upgradeDatabaseFrom13to14(SQLiteDatabase db) {
-            createOrUpdateSpecialFolder(db, Bookmarks.PINNED_FOLDER_GUID,
-                R.string.bookmarks_folder_pinned, 6);
-        }
-
-        private void upgradeDatabaseFrom14to15(SQLiteDatabase db) {
-            Cursor c = null;
-            try {
-                // Get all the pinned bookmarks
-                c = db.query(TABLE_BOOKMARKS,
-                             new String[] { Bookmarks._ID, Bookmarks.URL },
-                             Bookmarks.PARENT + " = ?",
-                             new String[] { Integer.toString(Bookmarks.FIXED_PINNED_LIST_ID) },
-                             null, null, null);
-
-                while (c.moveToNext()) {
-                    // Check if this URL can be parsed as a URI with a valid scheme.
-                    String url = c.getString(c.getColumnIndexOrThrow(Bookmarks.URL));
-                    if (Uri.parse(url).getScheme() != null) {
-                        continue;
-                    }
-
-                    // If it can't, update the URL to be an encoded "user-entered" value.
-                    ContentValues values = new ContentValues(1);
-                    String newUrl = Uri.fromParts("user-entered", url, null).toString();
-                    values.put(Bookmarks.URL, newUrl);
-                    db.update(TABLE_BOOKMARKS, values, Bookmarks._ID + " = ?",
-                              new String[] { Integer.toString(c.getInt(c.getColumnIndexOrThrow(Bookmarks._ID))) });
-                }
-            } finally {
-                if (c != null) {
-                    c.close();
-                }
-            }
-        }
-
-        private void upgradeDatabaseFrom15to16(SQLiteDatabase db) {
-            db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
-            db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED_WITH_FAVICONS);
-
-            createCombinedViewOn16(db);
-        }
-
-        private void upgradeDatabaseFrom16to17(SQLiteDatabase db) {
-            // Purge any 0-byte favicons/thumbnails
-            try {
-                db.execSQL("DELETE FROM " + TABLE_FAVICONS +
-                        " WHERE length(" + Favicons.DATA + ") = 0");
-                db.execSQL("DELETE FROM " + TABLE_THUMBNAILS +
-                        " WHERE length(" + Thumbnails.DATA + ") = 0");
-            } catch (SQLException e) {
-                Log.e(LOGTAG, "Error purging invalid favicons or thumbnails", e);
-            }
-        }
-
-        @Override
-        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-            debug("Upgrading browser.db: " + db.getPath() + " from " +
-                    oldVersion + " to " + newVersion);
-
-            // We have to do incremental upgrades until we reach the current
-            // database schema version.
-            for (int v = oldVersion + 1; v <= newVersion; v++) {
-                switch(v) {
-                    case 2:
-                        upgradeDatabaseFrom1to2(db);
-                        break;
-
-                    case 3:
-                        upgradeDatabaseFrom2to3(db);
-                        break;
-
-                    case 4:
-                        upgradeDatabaseFrom3to4(db);
-                        break;
-
-                    case 5:
-                        upgradeDatabaseFrom4to5(db);
-                        break;
-
-                    case 6:
-                        upgradeDatabaseFrom5to6(db);
-                        break;
-
-                    case 7:
-                        upgradeDatabaseFrom6to7(db);
-                        break;
-
-                    case 8:
-                        upgradeDatabaseFrom7to8(db);
-                        break;
-
-                    case 9:
-                        upgradeDatabaseFrom8to9(db);
-                        break;
-
-                    case 10:
-                        upgradeDatabaseFrom9to10(db);
-                        break;
-
-                    case 11:
-                        upgradeDatabaseFrom10to11(db);
-                        break;
-
-                    case 12:
-                        upgradeDatabaseFrom11to12(db);
-                        break;
-
-                    case 13:
-                        upgradeDatabaseFrom12to13(db);
-                        break;
-
-                    case 14:
-                        upgradeDatabaseFrom13to14(db);
-                        break;
-
-                    case 15:
-                        upgradeDatabaseFrom14to15(db);
-                        break;
-
-                    case 16:
-                        upgradeDatabaseFrom15to16(db);
-                        break;
-
-                    case 17:
-                        upgradeDatabaseFrom16to17(db);
-                        break;
-                }
-            }
-
-            // If an upgrade after 12->13 fails, the entire upgrade is rolled
-            // back, but we can't undo the deletion of favicon_urls.db if we
-            // delete this in step 13; therefore, we wait until all steps are
-            // complete before removing it.
-            if (oldVersion < 13 && newVersion >= 13
-                                && mContext.getDatabasePath(Obsolete.FAVICON_DB).exists()
-                                && !mContext.deleteDatabase(Obsolete.FAVICON_DB)) {
-                throw new SQLException("Could not delete " + Obsolete.FAVICON_DB);
-            }
-        }
-
-        @Override
-        public void onOpen(SQLiteDatabase db) {
-            debug("Opening browser.db: " + db.getPath());
-
-            Cursor cursor = null;
-            try {
-                cursor = db.rawQuery("PRAGMA foreign_keys=ON", null);
-            } finally {
-                if (cursor != null)
-                    cursor.close();
-            }
-            cursor = null;
-            try {
-                cursor = db.rawQuery("PRAGMA synchronous=NORMAL", null);
-            } finally {
-                if (cursor != null)
-                    cursor.close();
-            }
-
-            // From Honeycomb on, it's possible to run several db
-            // commands in parallel using multiple connections.
-            if (Build.VERSION.SDK_INT >= 11) {
-                db.enableWriteAheadLogging();
-                db.setLockingEnabled(false);
-            } else {
-                // Pre-Honeycomb, we can do some lesser optimizations.
-                cursor = null;
-                try {
-                    cursor = db.rawQuery("PRAGMA journal_mode=PERSIST", null);
-                } finally {
-                    if (cursor != null)
-                        cursor.close();
-                }
-            }
-        }
-    }
-
-    private static final String[] mobileIdColumns = new String[] { Bookmarks._ID };
-    private static final String[] mobileIdSelectionArgs = new String[] { Bookmarks.MOBILE_FOLDER_GUID };
-
-    private Integer getMobileFolderId(SQLiteDatabase db) {
-        Cursor c = null;
-
-        try {
-            c = db.query(TABLE_BOOKMARKS,
-                         mobileIdColumns,
-                         Bookmarks.GUID + " = ?",
-                         mobileIdSelectionArgs,
-                         null, null, null);
-
-            if (c == null || !c.moveToFirst())
-                return null;
-
-            return c.getInt(c.getColumnIndex(Bookmarks._ID));
-        } finally {
-            if (c != null)
-                c.close();
-        }
-    }
-
-    private SQLiteDatabase getReadableDatabase(Uri uri) {
-        trace("Getting readable database for URI: " + uri);
-
-        String profile = null;
-
-        if (uri != null)
-            profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
-
-        return mDatabases.getDatabaseHelperForProfile(profile, isTest(uri)).getReadableDatabase();
-    }
-
-    private SQLiteDatabase getWritableDatabase(Uri uri) {
-        trace("Getting writable database for URI: " + uri);
-
-        String profile = null;
-
-        if (uri != null)
-            profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
-
-        return mDatabases.getDatabaseHelperForProfile(profile, isTest(uri)).getWritableDatabase();
-    }
-
     private void cleanupSomeDeletedRecords(Uri fromUri, Uri targetUri, String tableName) {
         Log.d(LOGTAG, "Cleaning up deleted records from " + tableName);
 
         // we cleanup records marked as deleted that are older than a
         // predefined max age. It's important not be too greedy here and
         // remove only a few old deleted records at a time.
 
         // The PARAM_SHOW_DELETED argument is necessary to return the records
@@ -2072,72 +362,49 @@ public class BrowserProvider extends Con
      * Call this method within a transaction.
      */
     private void expireThumbnails(final SQLiteDatabase db) {
         Log.d(LOGTAG, "Expiring thumbnails.");
         final String sortOrder = BrowserContract.getFrecencySortOrder(true, false);
         final String sql = "DELETE FROM " + TABLE_THUMBNAILS +
                            " WHERE " + Thumbnails.URL + " NOT IN ( " +
                              " SELECT " + Combined.URL +
-                             " FROM " + VIEW_COMBINED +
+                             " FROM " + Combined.VIEW_NAME +
                              " ORDER BY " + sortOrder +
                              " LIMIT " + DEFAULT_EXPIRY_THUMBNAIL_COUNT +
                            ") AND " + Thumbnails.URL + " NOT IN ( " +
                              " SELECT " + Bookmarks.URL +
                              " FROM " + TABLE_BOOKMARKS +
                              " WHERE " + Bookmarks.PARENT + " = " + Bookmarks.FIXED_PINNED_LIST_ID +
                            ")";
         trace("Clear thumbs using query: " + sql);
         db.execSQL(sql);
     }
 
     private boolean isCallerSync(Uri uri) {
         String isSync = uri.getQueryParameter(BrowserContract.PARAM_IS_SYNC);
         return !TextUtils.isEmpty(isSync);
     }
 
-    private boolean isTest(Uri uri) {
-        String isTest = uri.getQueryParameter(BrowserContract.PARAM_IS_TEST);
-        return !TextUtils.isEmpty(isTest);
-    }
-
     private boolean shouldShowDeleted(Uri uri) {
         String showDeleted = uri.getQueryParameter(BrowserContract.PARAM_SHOW_DELETED);
         return !TextUtils.isEmpty(showDeleted);
     }
 
     private boolean shouldUpdateOrInsert(Uri uri) {
         String insertIfNeeded = uri.getQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED);
         return Boolean.parseBoolean(insertIfNeeded);
     }
 
     private boolean shouldIncrementVisits(Uri uri) {
         String incrementVisits = uri.getQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS);
         return Boolean.parseBoolean(incrementVisits);
     }
 
     @Override
-    public boolean onCreate() {
-        debug("Creating BrowserProvider");
-
-        synchronized (this) {
-            mContext = getContext();
-            mDatabases = new PerProfileDatabases<BrowserDatabaseHelper>(
-                getContext(), DATABASE_NAME, new DatabaseHelperFactory<BrowserDatabaseHelper>() {
-                    @Override
-                    public BrowserDatabaseHelper makeDatabaseHelper(Context context, String databasePath) {
-                        return new BrowserDatabaseHelper(context, databasePath);
-                    }
-                });
-        }
-
-        return true;
-    }
-
-    @Override
     public String getType(Uri uri) {
         final int match = URI_MATCHER.match(uri);
 
         trace("Getting URI type: " + uri);
 
         switch (match) {
             case BOOKMARKS:
                 trace("URI is BOOKMARKS: " + uri);
@@ -2156,47 +423,21 @@ public class BrowserProvider extends Con
                 return SearchManager.SUGGEST_MIME_TYPE;
         }
 
         debug("URI has unrecognized type: " + uri);
 
         return null;
     }
 
+    @SuppressWarnings("fallthrough")
     @Override
-    public int delete(Uri uri, String selection, String[] selectionArgs) {
-        trace("Calling delete on URI: " + uri);
-
+    public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
+        trace("Calling delete in transaction on URI: " + uri);
         final SQLiteDatabase db = getWritableDatabase(uri);
-        int deleted = 0;
-
-        if (Build.VERSION.SDK_INT >= 11) {
-            trace("Beginning delete transaction: " + uri);
-            db.beginTransaction();
-            try {
-                deleted = deleteInTransaction(db, uri, selection, selectionArgs);
-                db.setTransactionSuccessful();
-                trace("Successful delete transaction: " + uri);
-            } finally {
-                db.endTransaction();
-            }
-        } else {
-            deleted = deleteInTransaction(db, uri, selection, selectionArgs);
-        }
-
-        if (deleted > 0)
-            getContext().getContentResolver().notifyChange(uri, null);
-
-        return deleted;
-    }
-
-    @SuppressWarnings("fallthrough")
-    public int deleteInTransaction(SQLiteDatabase db, Uri uri, String selection, String[] selectionArgs) {
-        trace("Calling delete in transaction on URI: " + uri);
-
         final int match = URI_MATCHER.match(uri);
         int deleted = 0;
 
         switch (match) {
             case BOOKMARKS_ID:
                 trace("Delete on BOOKMARKS_ID: " + uri);
 
                 selection = DBUtils.concatenateWhere(selection, TABLE_BOOKMARKS + "._id = ?");
@@ -2270,47 +511,16 @@ public class BrowserProvider extends Con
         }
 
         debug("Deleted " + deleted + " rows for URI: " + uri);
 
         return deleted;
     }
 
     @Override
-    public Uri insert(Uri uri, ContentValues values) {
-        trace("Calling insert on URI: " + uri);
-
-        final SQLiteDatabase db = getWritableDatabase(uri);
-        Uri result = null;
-        try {
-            if (Build.VERSION.SDK_INT >= 11) {
-                trace("Beginning insert transaction: " + uri);
-                db.beginTransaction();
-                try {
-                    result = insertInTransaction(uri, values);
-                    db.setTransactionSuccessful();
-                    trace("Successful insert transaction: " + uri);
-                } finally {
-                    db.endTransaction();
-                }
-            } else {
-                result = insertInTransaction(uri, values);
-            }
-        } catch (SQLException sqle) {
-            Log.e(LOGTAG, "exception in DB operation", sqle);
-        } catch (UnsupportedOperationException uoe) {
-            Log.e(LOGTAG, "don't know how to perform that insert", uoe);
-        }
-
-        if (result != null)
-            getContext().getContentResolver().notifyChange(uri, null);
-
-        return result;
-    }
-
     public Uri insertInTransaction(Uri uri, ContentValues values) {
         trace("Calling insert in transaction on URI: " + uri);
 
         int match = URI_MATCHER.match(uri);
         long id = -1;
 
         switch (match) {
             case BOOKMARKS: {
@@ -2344,45 +554,18 @@ public class BrowserProvider extends Con
         debug("Inserted ID in database: " + id);
 
         if (id >= 0)
             return ContentUris.withAppendedId(uri, id);
 
         return null;
     }
 
+    @SuppressWarnings("fallthrough")
     @Override
-    public int update(Uri uri, ContentValues values, String selection,
-            String[] selectionArgs) {
-        trace("Calling update on URI: " + uri);
-
-        final SQLiteDatabase db = getWritableDatabase(uri);
-        int updated = 0;
-
-        if (Build.VERSION.SDK_INT >= 11) {
-            trace("Beginning update transaction: " + uri);
-            db.beginTransaction();
-            try {
-                updated = updateInTransaction(uri, values, selection, selectionArgs);
-                db.setTransactionSuccessful();
-                trace("Successful update transaction: " + uri);
-            } finally {
-                db.endTransaction();
-            }
-        } else {
-            updated = updateInTransaction(uri, values, selection, selectionArgs);
-        }
-
-        if (updated > 0)
-            getContext().getContentResolver().notifyChange(uri, null);
-
-        return updated;
-    }
-
-    @SuppressWarnings("fallthrough")
     public int updateInTransaction(Uri uri, ContentValues values, String selection,
             String[] selectionArgs) {
         trace("Calling update in transaction on URI: " + uri);
 
         int match = URI_MATCHER.match(uri);
         int updated = 0;
 
         switch (match) {
@@ -2581,17 +764,17 @@ public class BrowserProvider extends Con
                 qb.setTables(TABLE_THUMBNAILS);
 
                 break;
             }
 
             case SCHEMA: {
                 debug("Query is on schema.");
                 MatrixCursor schemaCursor = new MatrixCursor(new String[] { Schema.VERSION });
-                schemaCursor.newRow().add(DATABASE_VERSION);
+                schemaCursor.newRow().add(BrowserDatabaseHelper.DATABASE_VERSION);
 
                 return schemaCursor;
             }
 
             case COMBINED: {
                 debug("Query is on combined: " + uri);
 
                 if (TextUtils.isEmpty(sortOrder))
@@ -2601,17 +784,17 @@ public class BrowserProvider extends Con
                 // results when a history entry has multiple bookmarks.
                 groupBy = Combined.URL;
 
                 qb.setProjectionMap(COMBINED_PROJECTION_MAP);
 
                 if (hasFaviconsInProjection(projection))
                     qb.setTables(VIEW_COMBINED_WITH_FAVICONS);
                 else
-                    qb.setTables(VIEW_COMBINED);
+                    qb.setTables(Combined.VIEW_NAME);
 
                 break;
             }
 
             case SEARCH_SUGGEST: {
                 debug("Query is on search suggest: " + uri);
                 selection = DBUtils.concatenateWhere(selection, "(" + Combined.URL + " LIKE ? OR " +
                                                                       Combined.TITLE + " LIKE ?)");
@@ -2954,23 +1137,25 @@ public class BrowserProvider extends Con
                   new String[] { pageUrl });
     }
 
     long insertFavicon(Uri uri, ContentValues values) {
         return insertFavicon(getWritableDatabase(uri), values);
     }
 
     long insertFavicon(SQLiteDatabase db, ContentValues values) {
+        // This method is a dupicate of BrowserDatabaseHelper.insertFavicon.
+        // If changes are needed, please update both
         String faviconUrl = values.getAsString(Favicons.URL);
         String pageUrl = null;
         long faviconId;
 
         trace("Inserting favicon for URL: " + faviconUrl);
 
-        stripEmptyByteArray(values, Favicons.DATA);
+        DBUtils.stripEmptyByteArray(values, Favicons.DATA);
 
         // Extract the page URL from the ContentValues
         if (values.containsKey(Favicons.PAGE_URL)) {
             pageUrl = values.getAsString(Favicons.PAGE_URL);
             values.remove(Favicons.PAGE_URL);
         }
 
         // If no URL is provided, insert using the default one.
@@ -3009,17 +1194,17 @@ public class BrowserProvider extends Con
         int updated = 0;
         final SQLiteDatabase db = getWritableDatabase(uri);
         Cursor cursor = null;
         Long faviconId = null;
         long now = System.currentTimeMillis();
 
         trace("Updating favicon for URL: " + faviconUrl);
 
-        stripEmptyByteArray(values, Favicons.DATA);
+        DBUtils.stripEmptyByteArray(values, Favicons.DATA);
 
         // Extract the page URL from the ContentValues
         if (values.containsKey(Favicons.PAGE_URL)) {
             pageUrl = values.getAsString(Favicons.PAGE_URL);
             values.remove(Favicons.PAGE_URL);
         }
 
         values.put(Favicons.DATE_MODIFIED, now);
@@ -3063,17 +1248,17 @@ public class BrowserProvider extends Con
     }
 
     long insertThumbnail(Uri uri, ContentValues values) {
         String url = values.getAsString(Thumbnails.URL);
         final SQLiteDatabase db = getWritableDatabase(uri);
 
         trace("Inserting thumbnail for URL: " + url);
 
-        stripEmptyByteArray(values, Thumbnails.DATA);
+        DBUtils.stripEmptyByteArray(values, Thumbnails.DATA);
 
         return db.insertOrThrow(TABLE_THUMBNAILS, null, values);
     }
 
     int updateOrInsertThumbnail(Uri uri, ContentValues values, String selection,
             String[] selectionArgs) {
         return updateThumbnail(uri, values, selection, selectionArgs,
                 true /* insert if needed */);
@@ -3086,46 +1271,31 @@ public class BrowserProvider extends Con
     }
 
     int updateThumbnail(Uri uri, ContentValues values, String selection,
             String[] selectionArgs, boolean insertIfNeeded) {
         String url = values.getAsString(Thumbnails.URL);
         int updated = 0;
         final SQLiteDatabase db = getWritableDatabase(uri);
 
-        stripEmptyByteArray(values, Thumbnails.DATA);
+        DBUtils.stripEmptyByteArray(values, Thumbnails.DATA);
 
         trace("Updating thumbnail for URL: " + url);
 
         updated = db.update(TABLE_THUMBNAILS, values, selection, selectionArgs);
 
         if (updated == 0 && insertIfNeeded) {
             trace("No update, inserting thumbnail for URL: " + url);
             db.insert(TABLE_THUMBNAILS, null, values);
             updated = 1;
         }
 
         return updated;
     }
 
-    /**
-     * Verifies that 0-byte arrays aren't added as favicon or thumbnail data.
-     * @param values        ContentValues of query
-     * @param columnName    Name of data column to verify
-     */
-    private void stripEmptyByteArray(ContentValues values, String columnName) {
-        if (values.containsKey(columnName)) {
-            byte[] data = values.getAsByteArray(columnName);
-            if (data == null || data.length == 0) {
-                Log.w(LOGTAG, "Tried to insert an empty or non-byte-array image. Ignoring.");
-                values.putNull(columnName);
-            }
-        }
-    }
-
     int deleteHistory(Uri uri, String selection, String[] selectionArgs) {
         debug("Deleting history entry for URI: " + uri);
 
         final SQLiteDatabase db = getWritableDatabase(uri);
 
         if (isCallerSync(uri)) {
             return db.delete(TABLE_HISTORY, selection, selectionArgs);
         }
@@ -3274,36 +1444,18 @@ public class BrowserProvider extends Con
         if (failures) {
             throw new OperationApplicationException();
         }
 
         return results;
     }
 
     @Override
-    public int bulkInsert(Uri uri, ContentValues[] values) {
-        if (values == null)
-            return 0;
-
-        int numValues = values.length;
-        int successes = 0;
-
-        final SQLiteDatabase db = getWritableDatabase(uri);
-
-        db.beginTransaction();
+    protected BrowserDatabaseHelper createDatabaseHelper(
+            Context context, String databasePath) {
+         return new BrowserDatabaseHelper(context, databasePath);
+    }
 
-        try {
-            for (int i = 0; i < numValues; i++) {
-                insertInTransaction(uri, values[i]);
-                successes++;
-            }
-            trace("Flushing DB bulkinsert...");
-            db.setTransactionSuccessful();
-        } finally {
-            db.endTransaction();
-        }
-
-        if (successes > 0)
-            mContext.getContentResolver().notifyChange(uri, null);
-
-        return successes;
+    @Override
+    protected String getDatabaseName() {
+        return BrowserDatabaseHelper.DATABASE_NAME;
     }
 }
--- a/mobile/android/base/db/DBUtils.java
+++ b/mobile/android/base/db/DBUtils.java
@@ -76,9 +76,24 @@ public class DBUtils {
                 try {
                     Thread.sleep(retries * 100);
                 } catch (InterruptedException ie) { }
             }
         }
         Log.d(LOGTAG, "Failed to unlock database");
         GeckoAppShell.listOfOpenFiles();
     }
+
+    /**
+     * Verifies that 0-byte arrays aren't added as favicon or thumbnail data.
+     * @param values        ContentValues of query
+     * @param columnName    Name of data column to verify
+     */
+    public static void stripEmptyByteArray(ContentValues values, String columnName) {
+        if (values.containsKey(columnName)) {
+            byte[] data = values.getAsByteArray(columnName);
+            if (data == null || data.length == 0) {
+                Log.w(LOGTAG, "Tried to insert an empty or non-byte-array image. Ignoring.");
+                values.putNull(columnName);
+            }
+        }
+    }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/db/TransactionalProvider.java
@@ -0,0 +1,277 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.db;
+
+import org.mozilla.gecko.db.PerProfileDatabases.DatabaseHelperFactory;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
+
+/*
+ * Abstract class containing methods needed to make a SQLite-based content provider with a
+ * database helper of type T. Abstract methods insertInTransaction, deleteInTransaction and
+ * updateInTransaction all called within a DB transaction so failed modifications can be rolled-back.
+ */
+public abstract class TransactionalProvider<T extends SQLiteOpenHelper> extends ContentProvider {
+    private static final String LOGTAG = "GeckoTransProvider";
+    protected Context mContext;
+    protected PerProfileDatabases<T> mDatabases;
+
+    /*
+     * Returns the name of the database file. Used to get a path
+     * to the DB file.
+     *
+     * @return name of the database file
+     */
+    abstract protected String getDatabaseName();
+
+    /*
+     * Creates and returns an instance of a DB helper. Given a
+     * context and a path to the DB file
+     *
+     * @param  context       to use to create the database helper
+     * @param  databasePath  path to the DB file
+     * @return               instance of the database helper
+     */
+    abstract protected T createDatabaseHelper(Context context, String databasePath);
+
+    /*
+     * Inserts an item into the database within a DB transaction.
+     *
+     * @param uri    query URI
+     * @param values column values to be inserted
+     * @return       a URI for the newly inserted item
+     */
+    abstract protected Uri insertInTransaction(Uri uri, ContentValues values);
+
+    /*
+     * Deletes items from the database within a DB transaction.
+     *
+     * @param uri            query URI
+     * @param selection      An optional filter to match rows to update.
+     * @param selectionArgs  arguments for the selection
+     * @return               number of rows impacted by the deletion
+     */
+    abstract protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs);
+
+    /*
+     * Updates the database within a DB transaction.
+     *
+     * @param uri            Query URI
+     * @param values         A set of column_name/value pairs to add to the database.
+     * @param selection      An optional filter to match rows to update.
+     * @param selectionArgs  Arguments for the selection
+     * @return               number of rows impacted by the update
+     */
+    abstract protected int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs);
+
+    /*
+     * Fetches a readable database based on the profile indicated in the
+     * passed URI. If the URI does not contain a profile param, the default profile
+     * is used.
+     *
+     * @param uri content URI optionally indicating the profile of the user
+     * @return    instance of a readable SQLiteDatabase
+     */
+    protected SQLiteDatabase getReadableDatabase(Uri uri) {
+        String profile = null;
+        if (uri != null) {
+            profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
+        }
+
+        return mDatabases.getDatabaseHelperForProfile(profile, isTest(uri)).getReadableDatabase();
+    }
+
+    /*
+     * Fetches a writeable database based on the profile indicated in the
+     * passed URI. If the URI does not contain a profile param, the default profile
+     * is used
+     *
+     * @param uri content URI optionally indicating the profile of the user
+     * @return    instance of a writeable SQLiteDatabase
+     */
+    protected SQLiteDatabase getWritableDatabase(Uri uri) {
+        String profile = null;
+        if (uri != null) {
+            profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
+        }
+
+        return mDatabases.getDatabaseHelperForProfile(profile, isTest(uri)).getWritableDatabase();
+    }
+
+    @Override
+    public boolean onCreate() {
+        synchronized (this) {
+            mContext = getContext();
+            mDatabases = new PerProfileDatabases<T>(
+                getContext(), getDatabaseName(), new DatabaseHelperFactory<T>() {
+                    @Override
+                    public T makeDatabaseHelper(Context context, String databasePath) {
+                        return createDatabaseHelper(context, databasePath);
+                    }
+                });
+        }
+
+        return true;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        trace("Calling delete on URI: " + uri);
+
+        final SQLiteDatabase db = getWritableDatabase(uri);
+        int deleted = 0;
+
+        if (Build.VERSION.SDK_INT >= 11) {
+            trace("Beginning delete transaction: " + uri);
+            db.beginTransaction();
+            try {
+                deleted = deleteInTransaction(uri, selection, selectionArgs);
+                db.setTransactionSuccessful();
+                trace("Successful delete transaction: " + uri);
+            } finally {
+                db.endTransaction();
+            }
+        } else {
+            deleted = deleteInTransaction(uri, selection, selectionArgs);
+        }
+
+        if (deleted > 0) {
+            getContext().getContentResolver().notifyChange(uri, null);
+        }
+
+        return deleted;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        trace("Calling insert on URI: " + uri);
+
+        final SQLiteDatabase db = getWritableDatabase(uri);
+        Uri result = null;
+        try {
+            if (Build.VERSION.SDK_INT >= 11) {
+                trace("Beginning insert transaction: " + uri);
+                db.beginTransaction();
+                try {
+                    result = insertInTransaction(uri, values);
+                    db.setTransactionSuccessful();
+                    trace("Successful insert transaction: " + uri);
+                } finally {
+                    db.endTransaction();
+                }
+            } else {
+                result = insertInTransaction(uri, values);
+            }
+        } catch (SQLException sqle) {
+            Log.e(LOGTAG, "exception in DB operation", sqle);
+        } catch (UnsupportedOperationException uoe) {
+            Log.e(LOGTAG, "don't know how to perform that insert", uoe);
+        }
+
+        if (result != null) {
+            getContext().getContentResolver().notifyChange(uri, null);
+        }
+
+        return result;
+    }
+
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        trace("Calling update on URI: " + uri);
+
+        final SQLiteDatabase db = getWritableDatabase(uri);
+        int updated = 0;
+
+        if (Build.VERSION.SDK_INT >= 11) {
+            trace("Beginning update transaction: " + uri);
+            db.beginTransaction();
+            try {
+                updated = updateInTransaction(uri, values, selection, selectionArgs);
+                db.setTransactionSuccessful();
+                trace("Successful update transaction: " + uri);
+            } finally {
+                db.endTransaction();
+            }
+        } else {
+            updated = updateInTransaction(uri, values, selection, selectionArgs);
+        }
+
+        if (updated > 0) {
+            getContext().getContentResolver().notifyChange(uri, null);
+        }
+
+        return updated;
+    }
+
+    @Override
+    public int bulkInsert(Uri uri, ContentValues[] values) {
+        if (values == null) {
+            return 0;
+        }
+
+        int numValues = values.length;
+        int successes = 0;
+
+        final boolean hasTransactions = Build.VERSION.SDK_INT >= 11;
+        final SQLiteDatabase db = getWritableDatabase(uri);
+
+        if (hasTransactions) {
+            db.beginTransaction();
+        }
+
+        try {
+            for (int i = 0; i < numValues; i++) {
+                insertInTransaction(uri, values[i]);
+                successes++;
+            }
+            if (hasTransactions) {
+                trace("Flushing DB bulkinsert...");
+                db.setTransactionSuccessful();
+            }
+        } finally {
+            if (hasTransactions) {
+                db.endTransaction();
+            }
+        }
+
+        if (successes > 0) {
+            mContext.getContentResolver().notifyChange(uri, null);
+        }
+
+        return successes;
+    }
+
+    protected boolean isTest(Uri uri) {
+        String isTest = uri.getQueryParameter(BrowserContract.PARAM_IS_TEST);
+        return !TextUtils.isEmpty(isTest);
+    }
+
+    // Calculate these once, at initialization. isLoggable is too expensive to
+    // have in-line in each log call.
+    private static boolean logDebug   = Log.isLoggable(LOGTAG, Log.DEBUG);
+    private static boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
+    protected static void trace(String message) {
+        if (logVerbose) {
+            Log.v(LOGTAG, message);
+        }
+    }
+
+    protected static void debug(String message) {
+        if (logDebug) {
+            Log.d(LOGTAG, message);
+        }
+    }
+}
--- a/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
+++ b/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
@@ -260,16 +260,20 @@ public class FxAccountSyncAdapter extend
         handleError(e);
       }
 
       @Override
       public void handleError(Exception e) {
         Logger.error(LOG_TAG, "Failed to get token.", e);
         callback.handleError(null, e);
       }
+
+      @Override
+      public void handleBackoff(int backoffSeconds) {
+      }
     });
   }
 
   protected static void showNotification(Context context, State state) {
     final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
 
     final Action action = state.getNeededAction();
     if (action == Action.None) {
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -107,26 +107,28 @@ gbjar.sources += [
     'BrowserApp.java',
     'CameraImageResultHandler.java',
     'CameraVideoResultHandler.java',
     'ContactService.java',
     'ContextGetter.java',
     'CustomEditText.java',
     'DataReportingNotification.java',
     'db/BrowserContract.java',
+    'db/BrowserDatabaseHelper.java',
     'db/BrowserDB.java',
     'db/BrowserProvider.java',
     'db/DBUtils.java',
     'db/FormHistoryProvider.java',
     'db/HomeProvider.java',
     'db/LocalBrowserDB.java',
     'db/PasswordsProvider.java',
     'db/PerProfileDatabases.java',
     'db/SQLiteBridgeContentProvider.java',
     'db/TabsProvider.java',
+    'db/TransactionalProvider.java',
     'Distribution.java',
     'DoorHangerPopup.java',
     'EditBookmarkDialog.java',
     'favicons/cache/FaviconCache.java',
     'favicons/cache/FaviconCacheElement.java',
     'favicons/cache/FaviconsForURL.java',
     'favicons/decoders/FaviconDecoder.java',
     'favicons/decoders/ICODecoder.java',
index 11e89d34aba2222cc1deaca5b6137bdfa730727b..f6ef37ba9405fa3b66ec402ad46ed45fa7315ce7
GIT binary patch
literal 2200
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
index 4abb71ee3cdb8c9c33617bffa9bffad6e5fb46d7..a8782e2d66561e93a04a582a8c7fde57de949717
GIT binary patch
literal 1591
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
index 204562d39f09be565fbe3df4a04b157dd94222c9..8314fc45b04f9969a20990fd0194e2ca9c0d8c6c
GIT binary patch
literal 1531
zc$@+I1qAwuP)<h;3K|Lk000e1NJLTq000jF000^Y1^@s6Xw3#<000CeX+uL$Nkc;*
zP;zf(X>4Tx05}naRo`#hR1`jmZ&IWdKOk5~hl<6oRa0BJ8yc;~21%2p?MfD<>DVeH
z<T^KrsT&8|>9(p*dx19w`~g7O0}n_%Aq@s%d)fBDv`JHkDym6Hd+5XuAtvnwRpGmK
zVkc9?T=n|PIo~<wJLg{8L_J?=wVD}Kh?c9aozEndlcyGxo=u9<v(!ri)T`-EEs@L3
z5-!0N_s;9#9f}Cc?UC;OPWB_edW+oAi6T$HZWSGU8TbrQ%+zbPOBBBc`}k?M2Hf);
z@Y6N~0;>X-eVh__(Z?q}P9Z-Dj?gOW6|D%o20XmjW-qs4UjrD(li^iv8@eK9k+ZFm
zVRFymFOPAzG5-%Pn|1W;U4vNroTa&AxDScmEA~{ri9gr1^c?U@uwSpaNnw8l_>cP1
zd;)kMQS_;jeRSUEM_*s96y65j1$)tOrwdK{YIQMt92l|D^(E_=$Rjw{b!QT@q!)ni
zR`|5oW9X5n$Wv+HVc@|^eX5yXnsHX<gx$-tTA9oOBadXir_JPm2Y^4ct-PoO&C)tI
zGolvqOIK@duBk!Vu9{g<3;i;gJ6?~-DQ&xz!jvD&4!U-s8Os(*#?k2}f30SEXA#=i
z1-qUX+K`{!((H5w7<t$~ygD!D1{~X6)KX%$qrgY#L_{M_7A<1csY*MfP@XcB#Jxr~
zJS8&7goVS)VKE|4(h_Xlc{z{c$ApZs7riZ_QKdV_uW-M~u~<J-*#Z0?VzcZp8)p-w
zus7J7><CN2I>8PF3UX~a6)MwxDE0HaPjyrlI!;jX{6Kvuh*8ej?;85ekN$?5uuCiS
zBTvvVG+XTxAO{m@bvM#Jr)z6J><&E22D|vq?Y?Vkbo_DijopiF$2PET#<s%v*srlI
z{B2SKJ79W>mZ8e<cESmGBON_l0n;T7>u=y$(ArYkv7@Ex`GL?QCc!_*KFrd&;n1r7
zqW-CFs9&fT)ZaU5gc&=gBz-D<EBz>aCw(vdOp0__x+47~U6sC(E(JNe@4cTT*n6*E
zVH4eoU1-&7pEV~_PRe`a7v+@vy!^5}8?Y3)UmlaE<h}6h3HHql{T;m+bPBU-O|^S1
z@dOw&4<!bj2G_<^#e}PL7FpY$lcrKO$i~?8Bd2y;oaL5^csibnCrF9!i%-PI;xhub
zp1k;8_$IKX1NHus6EHeD;B72SCCD@4ojP$=Mf3`Eo6yZ&eg@wTqDiZE);7u&SJ|(s
zuPF(9%D6IJ)klXF%`_Fy<tR3HxV^%Qqa?nAB97=m-uu2qcHInZ?ps8M|H3=#R%lzO
z6MgLv^}ib0hVV{&<};#;2lcwW;^(7C<OY#bI<VjS9qCKr-E_Cnc!2j+&nHAXA2%BR
zt~VMxUn2h&(Pi^LSpac(Y#S>R0004!Nkl<ZI1%lXJxjw-6ozwd0`;R(SNjh-3brDY
zPGVaG7Z(Rb7jf5LA>z`}Nzp+H;s;8<T#N;6D(?P)x+)4PHs_i*n2@Hatpzvla_)KG
z=kzo;xkphttbD!_DOc;Kh#e3`bTC9DI(kS%bBGob$OVce-PjaF8T>FJ@}MT<7t6Y_
zNUGTP>(b<<nN;GytG5&vE{(VvBAo;ccp>a1%Xjlbrn1wCy5qTj`>3dosRMKpR0tS4
zd~hwrxm+$39v(Wj4k+pvK@mPU@bK-<vGJ)LA(Y9W0>1_hJaj5t*VZ+mZu_Bbd6Ufz
z&15bxo0V>`-<JwC{n~&2mnT5yS2r<Ir1sk;cvYGzM7H0L#J*Lfs<A6bEce=-CGVw^
zi52_Xez?C~V^(Ut6oUf~*1gZTY9rynyPs~Bl(O;i`Zh72Nj<g{Bk6k>pxB-gQm$g(
zwk+K{Mc})W#DOuV_|x2Sazy0*`32p5W+a{^O4a%WCMQc-Oef>Bmd767-nMoA2Kc%0
hv|t_38DdZ&xG!%1onhWR4`ToT002ovPDHLkV1ks3;hO*e
index 131f05dd288a4af90f7f0f99decea7de30ab9974..03a16d427eaed2b195664a088851eaaa6828b153
GIT binary patch
literal 4067
zc$@*_4;=7`P)<h;3K|Lk000e1NJLTq002Ay002D*1^@s6@t_LV000CeX+uL$Nkc;*
zP;zf(X>4Tx05}naRo`#hR1`jmZ&IWdKOk5~hl<6oRa0BJ8yc;~21%2p?MfD<>DVeH
z<T^KrsT&8|>9(p*dx19w`~g7O0}n_%Aq@s%d)fBDv`JHkDym6Hd+5XuAtvnwRpGmK
zVkc9?T=n|PIo~<wJLg{8L_J?=wVD}Kh?c9aozEndlcyGxo=u9<v(!ri)T`-EEs@L3
z5-!0N_s;9#9f}Cc?UC;OPWB_edW+oAi6T$HZWSGU8TbrQ%+zbPOBBBc`}k?M2Hf);
z@Y6N~0;>X-eVh__(Z?q}P9Z-Dj?gOW6|D%o20XmjW-qs4UjrD(li^iv8@eK9k+ZFm
zVRFymFOPAzG5-%Pn|1W;U4vNroTa&AxDScmEA~{ri9gr1^c?U@uwSpaNnw8l_>cP1
zd;)kMQS_;jeRSUEM_*s96y65j1$)tOrwdK{YIQMt92l|D^(E_=$Rjw{b!QT@q!)ni
zR`|5oW9X5n$Wv+HVc@|^eX5yXnsHX<gx$-tTA9oOBadXir_JPm2Y^4ct-PoO&C)tI
zGolvqOIK@duBk!Vu9{g<3;i;gJ6?~-DQ&xz!jvD&4!U-s8Os(*#?k2}f30SEXA#=i
z1-qUX+K`{!((H5w7<t$~ygD!D1{~X6)KX%$qrgY#L_{M_7A<1csY*MfP@XcB#Jxr~
zJS8&7goVS)VKE|4(h_Xlc{z{c$ApZs7riZ_QKdV_uW-M~u~<J-*#Z0?VzcZp8)p-w
zus7J7><CN2I>8PF3UX~a6)MwxDE0HaPjyrlI!;jX{6Kvuh*8ej?;85ekN$?5uuCiS
zBTvvVG+XTxAO{m@bvM#Jr)z6J><&E22D|vq?Y?Vkbo_DijopiF$2PET#<s%v*srlI
z{B2SKJ79W>mZ8e<cESmGBON_l0n;T7>u=y$(ArYkv7@Ex`GL?QCc!_*KFrd&;n1r7
zqW-CFs9&fT)ZaU5gc&=gBz-D<EBz>aCw(vdOp0__x+47~U6sC(E(JNe@4cTT*n6*E
zVH4eoU1-&7pEV~_PRe`a7v+@vy!^5}8?Y3)UmlaE<h}6h3HHql{T;m+bPBU-O|^S1
z@dOw&4<!bj2G_<^#e}PL7FpY$lcrKO$i~?8Bd2y;oaL5^csibnCrF9!i%-PI;xhub
zp1k;8_$IKX1NHus6EHeD;B72SCCD@4ojP$=Mf3`Eo6yZ&eg@wTqDiZE);7u&SJ|(s
zuPF(9%D6IJ)klXF%`_Fy<tR3HxV^%Qqa?nAB97=m-uu2qcHInZ?ps8M|H3=#R%lzO
z6MgLv^}ib0hVV{&<};#;2lcwW;^(7C<OY#bI<VjS9qCKr-E_Cnc!2j+&nHAXA2%BR
zt~VMxUn2h&(Pi^LSpac(Y#S>R000YmNkl<ZXa(I{3ve678Q$GHS&rg7ur1jpa%7X3
z04`2wJPZkKhfvzk(D3SnCY=s5rOc#I+9c4@&`ueiEiI3hp;JhPLV;2sfwYAtP+pma
zOlTTUAxWJCoWu{w4@pRlo%ki+?e<%d^T^Ukr<2bCsu|DT?f(Dw|NrjZ?*IS0reyMd
zEYuolARcuQp<*7yoJk0+5`>fxK}skUqe9T*l!!h-=n=w1i_k~+9*>Q_Q(s>lpDeYg
zlfc`yZF8TyVBs>V7OjGjmrz1aD*#_`F7~4XwwxH@&z<e}epmoTRu)GL-g)TIv_P5n
zW*E2@e>1a6E1-cBv7HO;_JGf~vjFxCmI)cWYuBzR{@QupX9`_UDJ{!Dcd$YT^2R9F
ze_mTzzI!6D1smMi9s7<@$i0-Zs$2?SgyNKwoq~xEi4Y&@oJJWLjZscjN|kApF)yL?
zOd<F=cs?IkYA!q>#4G)S{j1NZsp-x|m#r<A!3<11oiRD8bIy89Vf5PcHshTO-b9Fa
zig4|p)s>b1MLpIk>tAzouyjUw$z_6)E2-d1(E(G<xG>sbEupQd@q4$MwdbnKYH%nT
z->`Md=9^ZpUOj36w6}-PV8yO2jM4c<ol!!G5%kY<Bf9wgoT~CYMtz~>_U+q?>&`lN
zB~j?zm_>dQ99<Xs?IW6a%}rSO?9<C$H=)U>GBx;MD0=2}w|ZzWF}kpBmhU~u)fS2_
zQQgW*W-BkI<E#3Rc2giwaWq511S{l>nmu>!T0xZcjI!zJa9GjzVF1qMgx^u^ExRkd
zJ^fuy+l*gH5qkuc)UBv1j7CmLm#iaPbU?@fLHGyBQX5_2_mwR(jNPzdgDVt{t`3Fc
ze+xxo!C*N0>m;86WV2knc=3?m=lkVwUwkh1tEbZ0VQhoMXN29^9**Ca-k$!RF~TjK
zk@Gx7uK!3vIZe67ufTNqM>?a)J8kcZUau<38p3FOlArKBv98dP`Sa%wn9vtg)zKAO
zr?}VyU^X2rbjm|IPdpK*s%kb9kik@3q^>jwDJ2+W_RP_)u7FWzR_@)qcPhU5$5fZP
z(J+<^adfz^_v=m?`(A77Or6o5gf6}f!%mp#f`#s8MRo6N@9s`^dOA(U2zN$%vBHvt
zsf2=yc8vB<;SMgu7BUZ8Lph^#gcBMil<&hDycplt((ycr1^$YPy{UChJhyj-FHu}Y
zf5P<J0&nihY}+C_{BUp2@$(xR0==nn(+#G<aC`vY#gtScyU*yHH~hZSzG{~*U;2)o
z*f$l0J&%r$<#Kp9=g(DFlzl4|cifad(AHKtQDY^zri+JDg*g2@yRz&BLBz&f#!8ut
zu~lv1*y2?A;|zAGs*>7R=<_Jo<cdD2ztFMRj|C?mSqXvLc7H0(ID^q+=~FzFHt%PT
zsy@@w(&EjVp#Z`Q>gxIh)$g)m#Ui#?I!p%LI0KIaBXz~aE=R&KI3q!9k56O2?vcW%
zU^@I%p;7I?89v_*LxG8u&CSgvGrg5v*oCL%#Np!BSrw(pksfDoXQcOWxE9~d$?QO*
zHlkk=@Rz^k02IM?_$a);@8!aRZ~uC=uPnKXj7@3jaKPB>axps8CK6=$1BbAPrjb{y
zu+Sm14Vp27r?`~$s2n%q29c8`R`B1|`TZfMFbCq@I}nPE1vsX-LR1^|NrOKK24`Ud
zzuKsrtWxfafy&agPU6?s*PnnBV(La3a%7mfZ(n;d-IO$Vit3T>WX{0bU@oLYka0vK
zV);oY0Be`Czhn~70<vi4$IevJV1ybgEa(eu)-*1Db@;&E&jq}te=igy8=o<1So<x*
zQUo>1k*;W?DjjloB#qfX3$2&VeS+%C&ZzLcQz(WNUung(d)nH19uSoFThSJ)o{MC<
zn3cIb7h%dJtQc&!Q`Un0cG8TM`e3mVE>F=P8R5E>en=Z#C8$`WGNRTX2i$=LS|-aK
z`vE*>Na=_mCL-{dza)fGWVO98N-js@zl8~v9HNBPIikSFAjw4u3RvkdVUb*rJ_&-*
z^nyIV6lhezw2{QH<jI4<nnaMthZ2J|*lRD>g}Bq2VPX`jPAtFOR0#+t6_vQ$IrGY?
z47r|VI;i9D!5_*T6zJebPyUZ131MfXFq4Ov!bPPMsv$lNo;218xV$j5<hqp?=}98q
z$Eb^Bq(1FH7_^8i19FQq-cLp865MS^4p#M6hGUQ!c9^o!Vuf;fr%t~kTl-|xb7Avz
zQqaRpFoHN$#GOzG{`i{qNNkNwtb$i_rV?9QALYDBC7i#H0QxAhk)<{jH6>Rm`)x4N
z`-S1ez&f+22<g*v6jpRSyrbpt9A+^pG&lvjHI4qJrmA$CDX2+hTPU^&7!7uM>$om<
zQK<k^i|@s1cclq#!BzNP5^yhHf@8mf3eqnauR&aT752=mZY<KlkNPW0Z?qCBqdSkg
z<Srbztwbc`v7%45<HQ76j{tk^<(FHi5C`&NvO$ENFOpe9oH9O&^W$d~s@$8)SP4xT
zy+JzoR$`{A?nMX*SLJOi05Jf`;DyQ4K}{#Stb|Nvy9_jB8b;^h>E5CK7E4*G`enQ6
z<WZ(?lDNqjcpyIT?o3~arJPA25abVi9}V7`??sjsD^@5N-7B!><gE@)AzEvF-W`%*
zj4Kq4?S>~JPvB3<Pv(^2eqM(Rh-FSHRiU=-_%fGDx2D!vJ?o?V790pZB<aRHGhL8B
zTQNG(4v*{@Qn>d-2ana+HCD=36nec~Jv3F{WAKcnb@)L43#4S`gA&tdEVP7^-iJtL
zJ<gJ+!UHQuZu*2mN$-#@cHg$<#`iaG-fS7an#sn!uLYRNws#X3VGDqljLpXkuC1-@
z2}R=Dpx|;dVRq_(F#{LQZqbGlU#YFFYO}+egO-UB<w1OdW1nFT+Hp;|NHbtmj7@1-
zkr?6k7!4CGahy8W<tg6N+7n;7Y15{nLh$58H{2w+LGt1z!F@R8zqk+_RU>2DZbokf
zBe7SpUtU@Wa5f(7fw*SK-$vGaW9LBsoAO*J8?8|<0}-!hx?G$Z;E5fw+QSXL^iqCa
zTmQ1zYh2FBFgO&7T|nLJ-E>9=-jO;Zf5p|CC;t7;n{Qlm%{8WZH0k*X;tnA9^fNdj
z7fw_zgg#SUQF^U`lVPxI=m^K3%5@LRU@o-8zBr6;=>U3vKQQ9>Dxn8OB0_u~0Z1L(
zBHSz>-a_$H__Y2Jtv>a>rm4XP4~EL7l}<f`e06yW^Jnf6pV7m(VYD_?@o|fm(Qx+J
z{+Q4ctBv>1Vu@p&-TRt$|0G>^(-Ag^g3;LhSQ&q0ynj|oL|yv_jzuq?H*a3X4S}3a
zMyRdz?WM~uYs9|lWEyu;`wdRcTRq2*UD{ArH}3Y1A=GqA8;#N<YH8m04GCh@eVR+$
zy?k=(mKVR)IA=~WUu~vq&M9p$G&MDODgtw!Meo>l>kN#^EV1?UP_F&JU*&tzN={w|
zp9G=7p7<~1WpFFdQ`wI1b!T+2Z~1w(wa4<2YscUuUa%{CHBr?k;rTd{-pxnJTr^^Y
zFY^1#{*{X+wJqm2oJwdsSD#+7MH@)WM-aZlsQaW!I54-_F;?nndxTSrV;$YGb#VXg
zwwrEA!6@iadTO|gervU_{1LkVwnsQc0H0k|_TY#%gv+M<`IP!eeU$&@k{Hn%3NcnH
zHjxn?XIOhUd?915yBVQN#x*$bf+XJ{G~G1H#bck?f3;+@BNPtLM{aN(Zg#Fh#-kff
zi^pytfEg1+NO=%3#U3re-)?Q$ziY{oCGuEz!av7GI9-#bBS&U<r=Rx4L_+ISICHs+
zNF$-jdHCOi1u&!*k<lPQZj0+Yf({zuob)rUMIw=4cVlDYU^?Fa#k>5^l~ZlV{{Y^V
VU@R4MDlGs2002ovPDHLkV1kfu<TL;P
index 3a2e33adfcc36adccc3262af0bec5aa0f3cb9fc3..23ebe23741ed00da21613a532e9cee05198e61ff
GIT binary patch
literal 9730
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
index 1b6e67572fb215857031bc88474009299016ce99..fe2e174886c9f4864029dcde4d5634554e4050e8
GIT binary patch
literal 3204
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
index 1a50daf6de8daa4bc0efbc80c57a9bd7406f1a03..1c1d14c0c9fe12ac64a7e11398f96e4472692bb9
GIT binary patch
literal 3581
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
EKY#qeivR!s
index bb978372f0eaa8809130f20a41387f0530703d2a..d1fd254cd5f29da2be41454691777ad78b64ab56
GIT binary patch
literal 1383
zc$@)e1(^DYP)<h;3K|Lk000e1NJLTq000UA000pP1^@s6<Rd-h000CeX+uL$Nkc;*
zP;zf(X>4Tx05}naRo`#hR1`jmZ&IWdKOk5~hl<6oRa0BJ8yc;~21%2p?MfD<>DVeH
z<T^KrsT&8|>9(p*dx19w`~g7O0}n_%Aq@s%d)fBDv`JHkDym6Hd+5XuAtvnwRpGmK
zVkc9?T=n|PIo~<wJLg{8L_J?=wVD}Kh?c9aozEndlcyGxo=u9<v(!ri)T`-EEs@L3
z5-!0N_s;9#9f}Cc?UC;OPWB_edW+oAi6T$HZWSGU8TbrQ%+zbPOBBBc`}k?M2Hf);
z@Y6N~0;>X-eVh__(Z?q}P9Z-Dj?gOW6|D%o20XmjW-qs4UjrD(li^iv8@eK9k+ZFm
zVRFymFOPAzG5-%Pn|1W;U4vNroTa&AxDScmEA~{ri9gr1^c?U@uwSpaNnw8l_>cP1
zd;)kMQS_;jeRSUEM_*s96y65j1$)tOrwdK{YIQMt92l|D^(E_=$Rjw{b!QT@q!)ni
zR`|5oW9X5n$Wv+HVc@|^eX5yXnsHX<gx$-tTA9oOBadXir_JPm2Y^4ct-PoO&C)tI
zGolvqOIK@duBk!Vu9{g<3;i;gJ6?~-DQ&xz!jvD&4!U-s8Os(*#?k2}f30SEXA#=i
z1-qUX+K`{!((H5w7<t$~ygD!D1{~X6)KX%$qrgY#L_{M_7A<1csY*MfP@XcB#Jxr~
zJS8&7goVS)VKE|4(h_Xlc{z{c$ApZs7riZ_QKdV_uW-M~u~<J-*#Z0?VzcZp8)p-w
zus7J7><CN2I>8PF3UX~a6)MwxDE0HaPjyrlI!;jX{6Kvuh*8ej?;85ekN$?5uuCiS
zBTvvVG+XTxAO{m@bvM#Jr)z6J><&E22D|vq?Y?Vkbo_DijopiF$2PET#<s%v*srlI
z{B2SKJ79W>mZ8e<cESmGBON_l0n;T7>u=y$(ArYkv7@Ex`GL?QCc!_*KFrd&;n1r7
zqW-CFs9&fT)ZaU5gc&=gBz-D<EBz>aCw(vdOp0__x+47~U6sC(E(JNe@4cTT*n6*E
zVH4eoU1-&7pEV~_PRe`a7v+@vy!^5}8?Y3)UmlaE<h}6h3HHql{T;m+bPBU-O|^S1
z@dOw&4<!bj2G_<^#e}PL7FpY$lcrKO$i~?8Bd2y;oaL5^csibnCrF9!i%-PI;xhub
zp1k;8_$IKX1NHus6EHeD;B72SCCD@4ojP$=Mf3`Eo6yZ&eg@wTqDiZE);7u&SJ|(s
zuPF(9%D6IJ)klXF%`_Fy<tR3HxV^%Qqa?nAB97=m-uu2qcHInZ?ps8M|H3=#R%lzO
z6MgLv^}ib0hVV{&<};#;2lcwW;^(7C<OY#bI<VjS9qCKr-E_Cnc!2j+&nHAXA2%BR
zt~VMxUn2h&(Pi^LSpac(Y#S>R0002~Nkl<ZC>7OGKMw&h9BuE82o^CLEEkKIB*<;L
zAj0HB_&$6GHW7b@vt5Fi#71JYi3JgN*YZv(=g(mEOWMBQd#_2`S3=&ZP^>jE-e^Mo
zM3z-pfNCRfws1-db6`sSY0Ma9JrWMiD5WuoLWslZe9bMb;TpYyDQy_BXe8K|CUC&u
zprMCRi-zLeyQG77Evi2>qR||yw^WsW9Nxddbw%6X|2_Ws)<{Onv)V{e7544g+~wxf
z?3M7~&hfHu+(YxCdNqsnLnDqC;mWcO$J4cne5DZ>95isA5dh)a+-?ZY9SmUkA)u?6
pWbL+_Z6cZ46G6uFNxvg`tT#*2ZF*JeLbd<^002ovPDHLkV1j4ZpQiu-
index 7d82a89cb183bbd9cb6fc6edc414691769dc93cb..82304783977b499a023c562b09f478b6d83409be
GIT binary patch
literal 1357
zc$@)E1+w~yP)<h;3K|Lk000e1NJLTq000UA000pP1^@s6<Rd-h000CeX+uL$Nkc;*
zP;zf(X>4Tx05}naRo`#hR1`jmZ&IWdKOk5~hl<6oRa0BJ8yc;~21%2p?MfD<>DVeH
z<T^KrsT&8|>9(p*dx19w`~g7O0}n_%Aq@s%d)fBDv`JHkDym6Hd+5XuAtvnwRpGmK
zVkc9?T=n|PIo~<wJLg{8L_J?=wVD}Kh?c9aozEndlcyGxo=u9<v(!ri)T`-EEs@L3
z5-!0N_s;9#9f}Cc?UC;OPWB_edW+oAi6T$HZWSGU8TbrQ%+zbPOBBBc`}k?M2Hf);
z@Y6N~0;>X-eVh__(Z?q}P9Z-Dj?gOW6|D%o20XmjW-qs4UjrD(li^iv8@eK9k+ZFm
zVRFymFOPAzG5-%Pn|1W;U4vNroTa&AxDScmEA~{ri9gr1^c?U@uwSpaNnw8l_>cP1
zd;)kMQS_;jeRSUEM_*s96y65j1$)tOrwdK{YIQMt92l|D^(E_=$Rjw{b!QT@q!)ni
zR`|5oW9X5n$Wv+HVc@|^eX5yXnsHX<gx$-tTA9oOBadXir_JPm2Y^4ct-PoO&C)tI
zGolvqOIK@duBk!Vu9{g<3;i;gJ6?~-DQ&xz!jvD&4!U-s8Os(*#?k2}f30SEXA#=i
z1-qUX+K`{!((H5w7<t$~ygD!D1{~X6)KX%$qrgY#L_{M_7A<1csY*MfP@XcB#Jxr~
zJS8&7goVS)VKE|4(h_Xlc{z{c$ApZs7riZ_QKdV_uW-M~u~<J-*#Z0?VzcZp8)p-w
zus7J7><CN2I>8PF3UX~a6)MwxDE0HaPjyrlI!;jX{6Kvuh*8ej?;85ekN$?5uuCiS
zBTvvVG+XTxAO{m@bvM#Jr)z6J><&E22D|vq?Y?Vkbo_DijopiF$2PET#<s%v*srlI
z{B2SKJ79W>mZ8e<cESmGBON_l0n;T7>u=y$(ArYkv7@Ex`GL?QCc!_*KFrd&;n1r7
zqW-CFs9&fT)ZaU5gc&=gBz-D<EBz>aCw(vdOp0__x+47~U6sC(E(JNe@4cTT*n6*E
zVH4eoU1-&7pEV~_PRe`a7v+@vy!^5}8?Y3)UmlaE<h}6h3HHql{T;m+bPBU-O|^S1
z@dOw&4<!bj2G_<^#e}PL7FpY$lcrKO$i~?8Bd2y;oaL5^csibnCrF9!i%-PI;xhub
zp1k;8_$IKX1NHus6EHeD;B72SCCD@4ojP$=Mf3`Eo6yZ&eg@wTqDiZE);7u&SJ|(s
zuPF(9%D6IJ)klXF%`_Fy<tR3HxV^%Qqa?nAB97=m-uu2qcHInZ?ps8M|H3=#R%lzO
z6MgLv^}ib0hVV{&<};#;2lcwW;^(7C<OY#bI<VjS9qCKr-E_Cnc!2j+&nHAXA2%BR
zt~VMxUn2h&(Pi^LSpac(Y#S>R0002wNkl<ZC>3L1Km*$kpUMJZG+_otWPu&~kGn9j
zFmDHm{rmIR)5Fnn4@@4eaO<9vYOL(cCm5L+1VNJj|1rG({qwJdw~P5L5T6M|EZwq2
zkd1?BGfWXk48#VBf#g7Z=7xp_7IjU7jf_l8rXT?nz{n`f$<Af0A}_t1arfa<QyG|;
zFdUDf;orY$DEy($A==l>%JR~?WE54b7#SIru=)Dmzkhp<?cEjwjzUoAiwTIIU|?ia
zLs9(y|E*UaURp%^_`D}Au0f`NBLCl?-(mm%{rdyLAT}&Ppx6TA;7X4Ge+^@)_@E5?
P00000NkvXXu0mjf_p6dm
index b6e7595e0bf7d65c88a5dc18f1d4c201ea22b273..b328c7b44144c68fa6d908cdb0b663b1b02d757e
GIT binary patch
literal 2446
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
M07*qoM6N<$f~=CL8~^|S
index bcb5f1596358515e25405d43f56c6f0b4169341c..2f7d92b706c5f98ca1b0c4efe7eb5fa6e1dff1c4
GIT binary patch
literal 4934
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
Ef^MZoNB{r;
index 71f60fb224ee8b5a81eada857423af784c253e6d..5b084016df1357f609908b3485a5070be4f75dba
GIT binary patch
literal 603
zc$@)S0;K(kP)<h;3K|Lk000e1NJLTq001Na001Kh1^@s6`~|2W0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz{YgYYRCwCVn6XX*K@f)b$k9MzG_f|=
zDHU%ZZva#_YUBxk(ni6;8lFInu`n?Kp8$w2a248l*g`=}z!*WCSq1{Qr{3<uGRb5&
zA(?#l?=Z8o7V~NC!~G;I3Gf*Cymi1G@H%*9^XAU+6W{tLL<E)$_%Zmp&u>7W5`bN4
z0Rosj!T1H4Hy1n&U^OWqQl;#1EGkw6sGKh#fI*0=235Qak1q#co%tUE24EQF;^m(T
z0SrRCEEr}+DNrf>n?mI!!f>UijQmsxe(;oW!G?LXJ<g|Th0bK6S)nis?nb%WTM?NM
zcp}*ug;dl*khPOjzW;n~u(ykIC6&esWtdq*m3S@%r~t;8c}t9QD9%>HIEM`$QHj!T
zHYfYR)L!P;uqdNKdn!?ey{>RC3l(51W?F!Ty&hk`XjRE0EkONOca%f4Dp<|d0rc@$
zXZ@!J8?;)=v242g##MndnQPP4QvQpee}xW?CV%_x?X`51h1S7=YAQfhIK+ZGQ61`V
z4jZYUAUU$_1M}v+d_UT^m2&7oOSji`CjwzMCUuBrCf6WPNmLjMrqsdF$m^0{HPHQ2
za6%QY){!FOWm$0=MmQ)gX)Y6vKt?B55sazXz5p=!Re1eyXr7mQ{Sb`USxGRx??VE(
pBwc%B9wowMvoC1A3huW60|2$mxTx-6AH4to002ovPDHLkV1g!S1knHh
index 83f98d3c0b114737f70f7f59340dff790fbbe38d..e470ebc6d671e612b89cf858a3ff2ae6b655da2b
GIT binary patch
literal 1109
zc$|GxOH30%7+xq*AVGW!Cg`wUz}VfLw!7L5D=lroCM;lSBAjHqJB1DGgWZ9a9t@%f
zF<iWmXgv8yG%-G6qDC~n5)<Q1J&}0RgC@ocdeY!*Y0;RJ$?VR|pYQwT-*5kaexR?X
zp?+^YMNtjOWAPMOf%nvIB5$`)SRqR@P7L8eD~ofAgQ%!xWe`Xj$|y=9MVpy?gE}b6
z*RQ9C@Q~aqs+Peh9)`&qHsy_u&b+Ot;|PNc8r4mS{=W2#2D&EECwQ5Y?HC%<j};s=
zSm;Zug>f~k(Va&@M_wcX2Eq!+8xy80<|VqQE0VnzW@%7_;BkpwOKM0S05Qu!fM<e!
zm4grn0s}+5AizT)z`+2^ktz6LNaO`Ea2S*ZO>T3vteA>-m+m4bi5|n)7Fjlz%Q3ki
zV>zQN42Q#>Mj+rP2){dHVkPf4-2-KXIC51-x3O-Sz&k4$YYIy=UH$~aUY9l9l1;=g
zHm}$$WH|O8pe%0;HH>w%i&H4OG1^Vf*oaLb*P3!v^5e1xJhCmu9Hd~&Nn6%L*~Nh|
z3tR4(WrNtD0NP-X;fm&QL5^n(CItDCmqC`}#Q+pJj?8ud+GIu5O^@4N6qjW&X}VZ3
zRg{cNG>I^}u8FXV3x>l&;s_s&L70G1*p-L|LxBL~gRxjtD0AbMI%Oaem$}+sZg)kl
zS2t^H<ml5#>vk*y6#WtP%DKcU=d#AtD(4cf$Yn_~?0;2J4v|vxw(B)c4(nZlOwt+-
z>4E*~z&X-Cmy_{mI=`IxwYu8*DpI{Td_Q}U`m!r>t9Nwij&Cybo*Ld#eJS$feBB%8
zB*<Rd@nClO=F796uK4lF?_|@Nd1dy+4PSQ)_jUI8<^^%&L)FzSk?GlnmTyyY59=<}
zG&ZJdJDQ$1J!*6!+qZT$Q*WdD`fCy|PJzXiyQiv}3$;JArk=N_TR&5~55ATji?3U^
gRWJBHN}Fpr>fO#g*GKOCeCjoNGSL^m-8C}z2fBb-&Hw-a
index c237c29559c2b4f0e74dd89f33bcbe17fb0f866e..51efb99a2175cd63a33338e8a58ae0a7bc983d6a
GIT binary patch
literal 1118
zc$|GxUuYCZ9NueabDWK)D4JG^!v;j5w>xv2-R72Dle@i~xsr|P<-j9I&ED=@Zb|k}
zb|<;bQ8Wf041^Y%QhXCk3++oED%gjDO6x-d?Th5a_+VRF5J4o`hdu=2>?IdPVusnB
znIGTx&2PW`{T7CYPPN6}i!lt-mLJF#=nBFo`WAir_4<Ewd6)E;$cR%RHO<3J+HlGk
z<SlI+7qDi`POab`hG{u%7E7e04$8V?aaxGs0?TE>(bF5anm&OEDC2R{PO(pZzQqF5
zNU>)`6{@a+tL8x6!z1;fqF$fSlLp)S5$FkIDqvxvfxw!yeK|<64PBY;!!XZ+282wc
z*sY{W>M&3o4+D`C;yOeKND_y-L`gy)fCNMd9?~hrQI{-Aa^gd<Jy?31XH?`uHn)8j
zJ*C(xA+F5xwOWm<37q4N^C+22h8l@PoFd}>tWC5aZu^~0g)H`U&vc3D*dRP>WoMeC
zSho2Imb)Ws``b2A!}vgRdBj2fC7`PA4YjNtv`-4SvNzf<&bpW{VBeYcbo%2eogvwk
z6%T8~@rsT!*>rKZ>JZ1TIxbK~Bya)=9Bh~e1t@YB*M-DwF9jZoastT^(s>eq6RM`0
zcE~;15LZ<>Z~H{Eb)3(pSQ_C>(~waH3dy9D?iSMuLj5R>GW}_xE0I8=peSjn$;~?Y
zw1sWb<Qgxynb&f|y4hl5&z!+V&T}l#@JBXx$wkQtLPpHxs9t9GT(-EzF1a+g5`?ei
z@-!L#m8xilXsLzUof@Zyoi4#PZ4Hn1z}I8@zNh{3Z9bbW25aSw&CT9l``$SG*_YQV
zf5ak%rHiHc!Dr_pQ|CuV*J5X5SE3#8%`g64taco2Z~6Pv9~CBgXZ^Q#j@Alabu;&V
z`iGhS+$eX9!AEnSv@XhHtC8jX9n-hJYstM2A2H?omo`sL9@?0@dt><bzT;OXTQ}|;
zZ2RWv%Ev7aE?zly`M~nR(k~ZI=cB8U_14(6|J&M+{PlSKZSo|z?MM3NBFvvvFn+T>
Q^DylAeE(4Phs@agbM)b51^@s6
--- a/mobile/android/base/sync/net/SyncResponse.java
+++ b/mobile/android/base/sync/net/SyncResponse.java
@@ -44,16 +44,30 @@ public class SyncResponse {
   public int getStatusCode() {
     return this.response.getStatusLine().getStatusCode();
   }
 
   public boolean wasSuccessful() {
     return this.getStatusCode() == 200;
   }
 
+  /**
+   * Fetch the content type of the HTTP response body.
+   *
+   * @return a <code>Header</code> instance, or <code>null</code> if there was
+   *         no body or no valid Content-Type.
+   */
+  public Header getContentType() {
+    HttpEntity entity = this.response.getEntity();
+    if (entity == null) {
+      return null;
+    }
+    return entity.getContentType();
+  }
+
   private String body = null;
   public String body() throws IllegalStateException, IOException {
     if (body != null) {
       return body;
     }
     InputStreamReader is = new InputStreamReader(this.response.getEntity().getContent());
     // Oh, Java, you are so evil.
     body = new Scanner(is).useDelimiter("\\A").next();
@@ -141,41 +155,75 @@ public class SyncResponse {
       return (int)((then - now) / 1000);     // Convert milliseconds to seconds.
     } catch (DateParseException e) {
       Logger.warn(LOG_TAG, "Retry-After header neither integer nor date: " + retryAfter);
       return -1;
     }
   }
 
   /**
+   * @return A number of seconds, or -1 if the 'X-Backoff' header was not
+   *         present.
+   */
+  public int backoffInSeconds() throws NumberFormatException {
+    return this.getIntegerHeader("x-backoff");
+  }
+
+  /**
    * @return A number of seconds, or -1 if the 'X-Weave-Backoff' header was not
    *         present.
    */
   public int weaveBackoffInSeconds() throws NumberFormatException {
     return this.getIntegerHeader("x-weave-backoff");
   }
 
   /**
-   * @return A number of milliseconds, or -1 if neither the 'Retry-After' or
-   *         'X-Weave-Backoff' header was present.
+   * Extract a number of seconds, or -1 if none of the specified headers were present.
+   *
+   * @param includeRetryAfter
+   *          if <code>true</code>, the Retry-After header is excluded. This is
+   *          useful for processing non-error responses where a Retry-After
+   *          header would be unexpected.
+   * @return the maximum of the three possible backoff headers, in seconds.
    */
-  public long totalBackoffInMilliseconds() {
+  public int totalBackoffInSeconds(boolean includeRetryAfter) {
     int retryAfterInSeconds = -1;
-    try {
-      retryAfterInSeconds = retryAfterInSeconds();
-    } catch (NumberFormatException e) {
+    if (includeRetryAfter) {
+      try {
+        retryAfterInSeconds = retryAfterInSeconds();
+      } catch (NumberFormatException e) {
+      }
     }
 
     int weaveBackoffInSeconds = -1;
     try {
       weaveBackoffInSeconds = weaveBackoffInSeconds();
     } catch (NumberFormatException e) {
     }
 
-    long totalBackoff = (long) Math.max(retryAfterInSeconds, weaveBackoffInSeconds);
+    int backoffInSeconds = -1;
+    try {
+      backoffInSeconds = backoffInSeconds();
+    } catch (NumberFormatException e) {
+    }
+
+    int totalBackoff = Math.max(retryAfterInSeconds, Math.max(backoffInSeconds, weaveBackoffInSeconds));
+    if (totalBackoff < 0) {
+      return -1;
+    } else {
+      return totalBackoff;
+    }
+  }
+
+  /**
+   * @return A number of milliseconds, or -1 if neither the 'Retry-After',
+   *         'X-Backoff', or 'X-Weave-Backoff' header were present.
+   */
+  public long totalBackoffInMilliseconds() {
+    long totalBackoff = totalBackoffInSeconds(true);
     if (totalBackoff < 0) {
       return -1;
     } else {
       return 1000 * totalBackoff;
     }
   }
 
   /**
--- a/mobile/android/base/sync/net/SyncStorageRecordRequest.java
+++ b/mobile/android/base/sync/net/SyncStorageRecordRequest.java
@@ -20,16 +20,17 @@ import org.mozilla.gecko.sync.ThreadPool
  * Includes:
  * * Basic Auth headers (via Resource)
  * * Error responses:
  *   * 401
  *   * 503
  * * Headers:
  *   * Retry-After
  *   * X-Weave-Backoff
+ *   * X-Backoff
  *   * X-Weave-Records?
  *   * ...
  * * Timeouts
  * * Network errors
  * * application/newlines
  * * JSON parsing
  * * Content-Type and Content-Length validation.
  */
--- a/mobile/android/base/tests/testUITelemetry.java
+++ b/mobile/android/base/tests/testUITelemetry.java
@@ -1,24 +1,31 @@
 package org.mozilla.gecko.tests;
 
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.Telemetry;
 
 import android.util.Log;
 
 public class testUITelemetry extends JavascriptTest {
     public testUITelemetry() {
         super("testUITelemetry.js");
     }
 
     @Override
     public void testJavascript() throws Exception {
         blockForGeckoReady();
+
+        // We can't run these tests unless telemetry is turned on --
+        // the events will be dropped on the floor.
+        Log.i("GeckoTest", "Enabling telemetry.");
+        PrefsHelper.setPref(AppConstants.TELEMETRY_PREF_NAME, true);
+
         Log.i("GeckoTest", "Adding telemetry events.");
-
         try {
             Telemetry.sendUIEvent("enone", "method0");
             Telemetry.startUISession("foo");
             Telemetry.sendUIEvent("efoo", "method1");
             Telemetry.startUISession("foo");
             Telemetry.sendUIEvent("efoo", "method2");
             Telemetry.startUISession("bar");
             Telemetry.sendUIEvent("efoobar", "method3", "foobarextras");
--- a/mobile/android/base/tests/testUITelemetry.js
+++ b/mobile/android/base/tests/testUITelemetry.js
@@ -1,31 +1,47 @@
 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
 
 function do_check_array_eq(a1, a2) {
   do_check_eq(a1.length, a2.length);
   for (let i = 0; i < a1.length; ++i) {
     do_check_eq(a1[i], a2[i]);
   }
 }
 
-add_test(function test_telemetry_events() {
-  let bridge = Components.classes["@mozilla.org/android/bridge;1"]
-                         .getService(Components.interfaces.nsIAndroidBridge);
+function getObserver() {
+  let bridge = Cc["@mozilla.org/android/bridge;1"]
+                 .getService(Ci.nsIAndroidBridge);
   let obsXPCOM = bridge.browserApp.getUITelemetryObserver();
   do_check_true(!!obsXPCOM);
+  return obsXPCOM.wrappedJSObject;
+}
 
-  let obs = obsXPCOM.wrappedJSObject;
+/**
+ * The following event test will fail if telemetry isn't enabled. The Java-side
+ * part of this test should have turned it on; fail if it didn't work.
+ */
+add_test(function test_enabled() {
+  let obs = getObserver();
   do_check_true(!!obs);
+  do_check_true(obs.enabled);
+  run_next_test();
+});
 
+add_test(function test_telemetry_events() {
+  let obs = getObserver();
   let measurements = obs.getUIMeasurements();
 
   let expected = [
     ["event", "enone", "method0", [], null],
     ["event", "efoo", "method1", ["foo"], null],
     ["event", "efoo", "method2", ["foo"], null],
     ["event", "efoobar", "method3", ["foo", "bar"], "foobarextras"],
     ["session", "foo", "reasonfoo"],
--- a/mobile/android/base/tokenserver/TokenServerClient.java
+++ b/mobile/android/base/tokenserver/TokenServerClient.java
@@ -24,16 +24,17 @@ import org.mozilla.gecko.sync.net.BaseRe
 import org.mozilla.gecko.sync.net.BrowserIDAuthHeaderProvider;
 import org.mozilla.gecko.sync.net.SyncResponse;
 import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerConditionsRequiredException;
 import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerInvalidCredentialsException;
 import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerMalformedRequestException;
 import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerMalformedResponseException;
 import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerUnknownServiceException;
 
+import ch.boye.httpclientandroidlib.Header;
 import ch.boye.httpclientandroidlib.HttpHeaders;
 import ch.boye.httpclientandroidlib.HttpResponse;
 import ch.boye.httpclientandroidlib.client.ClientProtocolException;
 import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
 import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
 import ch.boye.httpclientandroidlib.message.BasicHeader;
 
 /**
@@ -88,36 +89,62 @@ public class TokenServerClient {
     executor.execute(new Runnable() {
       @Override
       public void run() {
         delegate.handleFailure(e);
       }
     });
   }
 
+  /**
+   * Notify the delegate that some kind of backoff header (X-Backoff,
+   * X-Weave-Backoff, Retry-After) was received and should be acted upon.
+   *
+   * This method is non-terminal, and will be followed by a separate
+   * <code>invoke*</code> call.
+   *
+   * @param delegate
+   *          the delegate to inform.
+   * @param backoffSeconds
+   *          the number of seconds for which the system should wait before
+   *          making another token server request to this server.
+   */
+  protected void notifyBackoff(final TokenServerClientDelegate delegate, final int backoffSeconds) {
+    executor.execute(new Runnable() {
+      @Override
+      public void run() {
+        delegate.handleBackoff(backoffSeconds);
+      }
+    });
+  }
+
   protected void invokeHandleError(final TokenServerClientDelegate delegate, final Exception e) {
     executor.execute(new Runnable() {
       @Override
       public void run() {
         delegate.handleError(e);
       }
     });
   }
 
-  public TokenServerToken processResponse(HttpResponse response)
-      throws TokenServerException {
-    SyncResponse res = new SyncResponse(response);
+  public TokenServerToken processResponse(SyncResponse res) throws TokenServerException {
     int statusCode = res.getStatusCode();
 
     Logger.debug(LOG_TAG, "Got token response with status code " + statusCode + ".");
 
     // Responses should *always* be JSON, even in the case of 4xx and 5xx
     // errors. If we don't see JSON, the server is likely very unhappy.
-    String contentType = response.getEntity().getContentType().getValue();
-    if (!contentType.equals("application/json") && !contentType.startsWith("application/json;")) {
+    final Header contentType = res.getContentType();
+    if (contentType == null) {
+      throw new TokenServerMalformedResponseException(null, "Non-JSON response Content-Type.");
+    }
+
+    final String type = contentType.getValue();
+    if (!type.equals("application/json") &&
+        !type.startsWith("application/json;")) {
       Logger.warn(LOG_TAG, "Got non-JSON response with Content-Type " +
           contentType + ". Misconfigured server?");
       throw new TokenServerMalformedResponseException(null, "Non-JSON response Content-Type.");
     }
 
     // Responses should *always* be a valid JSON object.
     ExtendedJSONObject result;
     try {
@@ -225,20 +252,31 @@ public class TokenServerClient {
       this.assertion = assertion;
       this.clientState = clientState;
       this.resource = resource;
       this.conditionsAccepted = conditionsAccepted;
     }
 
     @Override
     public void handleHttpResponse(HttpResponse response) {
+      // Skew.
       SkewHandler skewHandler = SkewHandler.getSkewHandlerForResource(resource);
       skewHandler.updateSkew(response, System.currentTimeMillis());
+
+      // Extract backoff regardless of whether this was an error response, and
+      // Retry-After for 503 responses. The error will be handled elsewhere.)
+      SyncResponse res = new SyncResponse(response);
+      final boolean includeRetryAfter = res.getStatusCode() == 503;
+      int backoffInSeconds = res.totalBackoffInSeconds(includeRetryAfter);
+      if (backoffInSeconds > -1) {
+        client.notifyBackoff(delegate, backoffInSeconds);
+      }
+
       try {
-        TokenServerToken token = client.processResponse(response);
+        TokenServerToken token = client.processResponse(res);
         client.invokeHandleSuccess(delegate, token);
       } catch (TokenServerException e) {
         client.invokeHandleFailure(delegate, e);
       }
     }
 
     @Override
     public void handleTransportException(GeneralSecurityException e) {
--- a/mobile/android/base/tokenserver/TokenServerClientDelegate.java
+++ b/mobile/android/base/tokenserver/TokenServerClientDelegate.java
@@ -1,12 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.tokenserver;
 
-
 public interface TokenServerClientDelegate {
   void handleSuccess(TokenServerToken token);
   void handleFailure(TokenServerException e);
   void handleError(Exception e);
+
+  /**
+   * Might be called multiple times, in addition to the other terminating handler methods.
+   */
+  void handleBackoff(int backoffSeconds);
 }
\ No newline at end of file
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -4301,8 +4301,11 @@ pref("dom.inter-app-communication-api.en
 // The tables used for Safebrowsing phishing and malware checks.
 pref("urlclassifier.malware_table", "goog-malware-shavar");
 pref("urlclassifier.phish_table", "goog-phish-shavar");
 pref("urlclassifier.download_block_table", "");
 pref("urlclassifier.download_allow_table", "");
 
 // Turn off Spatial navigation by default.
 pref("snav.enabled", false);
+
+// Wakelock is disabled by default.
+pref("dom.wakelock.enabled", false);
--- a/services/common/tests/unit/head_helpers.js
+++ b/services/common/tests/unit/head_helpers.js
@@ -168,16 +168,21 @@ function installFakePAC() {
 }
 
 function uninstallFakePAC() {
   _("Uninstalling fake PAC.");
   let CID = PACSystemSettings.CID;
   Cm.nsIComponentRegistrar.unregisterFactory(CID, PACSystemSettings);
 }
 
-// We want to ensure the legacy provider is used for most of these tests; the
-// tests that know how to deal with the Firefox Accounts identity hack things
-// to ensure that still works.
+// We want to ensure the legacy provider is used for most of these tests,
+// including after a service.startOver.  The tests that know how to deal with
+// the Firefox Accounts identity hack things to ensure that still works.
 function setDefaultIdentityConfig() {
   Cu.import("resource://gre/modules/Services.jsm");
   Services.prefs.setBoolPref("services.sync.fxaccounts.enabled", false);
+  Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", true);
+  do_register_cleanup(function() {
+    Services.prefs.clearUserPref("services.sync.fxaccounts.enabled");
+    Services.prefs.clearUserPref("services.sync-testing.startOverKeepIdentity");
+  });
 }
 setDefaultIdentityConfig();
--- a/services/sync/Weave.js
+++ b/services/sync/Weave.js
@@ -101,20 +101,20 @@ WeaveService.prototype = {
     } catch (_) {
       // That pref doesn't exist - so let's assume this is a first-run.
       // If sync already appears configured, we assume it's for the legacy
       // provider.
       let prefs = Services.prefs.getBranch(SYNC_PREFS_BRANCH);
       fxAccountsEnabled = !prefs.prefHasUserValue("username");
       Services.prefs.setBoolPref("services.sync.fxaccounts.enabled", fxAccountsEnabled);
     }
-    // Currently we don't support toggling this pref after initialization, so
-    // inject the pref value as a regular boolean.
-    delete this.fxAccountsEnabled;
-    return this.fxAccountsEnabled = fxAccountsEnabled;
+    // Currently we don't support toggling this pref after initialization -
+    // except when sync is reset - but this 1 exception is enough that we can't
+    // cache the value.
+    return fxAccountsEnabled;
   },
 
   observe: function (subject, topic, data) {
     switch (topic) {
     case "app-startup":
       let os = Cc["@mozilla.org/observer-service;1"].
                getService(Ci.nsIObserverService);
       os.addObserver(this, "final-ui-startup", true);
--- a/services/sync/modules/browserid_identity.js
+++ b/services/sync/modules/browserid_identity.js
@@ -232,18 +232,17 @@ this.BrowserIDManager.prototype = {
 
   /**
    * Sets the active account name.
    *
    * This should almost always be called in favor of setting username, as
    * username is derived from account.
    *
    * Changing the account name has the side-effect of wiping out stored
-   * credentials. Keep in mind that persistCredentials() will need to be called
-   * to flush the changes to disk.
+   * credentials.
    *
    * Set this value to null to clear out identity information.
    */
   set account(value) {
     throw "account setter should be not used in BrowserIDManager";
   },
 
   /**
--- a/services/sync/modules/engines/passwords.js
+++ b/services/sync/modules/engines/passwords.js
@@ -35,28 +35,40 @@ PasswordEngine.prototype = {
   _trackerObj: PasswordTracker,
   _recordObj: LoginRec,
   applyIncomingBatchSize: PASSWORDS_STORE_BATCH_SIZE,
 
   _syncFinish: function _syncFinish() {
     SyncEngine.prototype._syncFinish.call(this);
 
     // Delete the weave credentials from the server once
-    if (!Svc.Prefs.get("deletePwd", false)) {
+    if (!Svc.Prefs.get("deletePwdFxA", false)) {
       try {
-        let ids = Services.logins.findLogins({}, PWDMGR_HOST, "", "")
-                          .map(function(info) {
-          return info.QueryInterface(Components.interfaces.nsILoginMetaInfo).guid;
-        });
-        let coll = new Collection(this.engineURL, null, this.service);
-        coll.ids = ids;
-        let ret = coll.delete();
-        this._log.debug("Delete result: " + ret);
-
-        Svc.Prefs.set("deletePwd", true);
+        let ids = [];
+        for (let host of Utils.getSyncCredentialsHosts()) {
+          for (let info of Services.logins.findLogins({}, host, "", "")) {
+            ids.push(info.QueryInterface(Components.interfaces.nsILoginMetaInfo).guid);
+          }
+        }
+        if (ids.length) {
+          let coll = new Collection(this.engineURL, null, this.service);
+          coll.ids = ids;
+          let ret = coll.delete();
+          this._log.debug("Delete result: " + ret);
+          if (!ret.success && ret.status != 400) {
+            // A non-400 failure means try again next time.
+            return;
+          }
+        } else {
+          this._log.debug("Didn't find any passwords to delete");
+        }
+        // If there were no ids to delete, or we succeeded, or got a 400,
+        // record success.
+        Svc.Prefs.set("deletePwdFxA", true);
+        Svc.Prefs.reset("deletePwd"); // The old prefname we previously used.
       }
       catch(ex) {
         this._log.debug("Password deletes failed: " + Utils.exceptionStr(ex));
       }
     }
   },
 
   _findDupe: function _findDupe(item) {
@@ -145,18 +157,19 @@ PasswordStore.prototype = {
 
   getAllIDs: function PasswordStore__getAllIDs() {
     let items = {};
     let logins = Services.logins.getAllLogins({});
 
     for (let i = 0; i < logins.length; i++) {
       // Skip over Weave password/passphrase entries
       let metaInfo = logins[i].QueryInterface(Ci.nsILoginMetaInfo);
-      if (metaInfo.hostname == PWDMGR_HOST)
+      if (Utils.getSyncCredentialsHosts().has(metaInfo.hostname)) {
         continue;
+      }
 
       items[metaInfo.guid] = metaInfo;
     }
 
     return items;
   },
 
   changeItemID: function PasswordStore__changeItemID(oldID, newID) {
@@ -283,17 +296,17 @@ PasswordTracker.prototype = {
     switch (data) {
       case "modifyLogin":
         subject = subject.QueryInterface(Ci.nsIArray).queryElementAt(1, Ci.nsILoginMetaInfo);
         // fallthrough
       case "addLogin":
       case "removeLogin":
         // Skip over Weave password/passphrase changes.
         subject.QueryInterface(Ci.nsILoginMetaInfo).QueryInterface(Ci.nsILoginInfo);
-        if (subject.hostname == PWDMGR_HOST) {
+        if (Utils.getSyncCredentialsHosts().has(subject.hostname)) {
           break;
         }
 
         this.score += SCORE_INCREMENT_XLARGE;
         this._log.trace(data + ": " + subject.guid);
         this.addChangedID(subject.guid);
         break;
       case "removeAllLogins":
--- a/services/sync/modules/identity.js
+++ b/services/sync/modules/identity.js
@@ -443,19 +443,21 @@ IdentityManager.prototype = {
                                 password, "", "");
     Services.logins.addLogin(login);
   },
 
   /**
    * Deletes Sync credentials from the password manager.
    */
   deleteSyncCredentials: function deleteSyncCredentials() {
-    let logins = Services.logins.findLogins({}, PWDMGR_HOST, "", "");
-    for each (let login in logins) {
-      Services.logins.removeLogin(login);
+    for (let host of Utils.getSyncCredentialsHosts()) {
+      let logins = Services.logins.findLogins({}, host, "", "");
+      for each (let login in logins) {
+        Services.logins.removeLogin(login);
+      }
     }
 
     // Wait until after store is updated in case it fails.
     this._basicPassword = null;
     this._basicPasswordAllowLookup = true;
     this._basicPasswordUpdated = false;
 
     this._syncKey = null;
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -884,17 +884,42 @@ Sync11Service.prototype = {
     this._ignorePrefObserver = true;
     Svc.Prefs.resetBranch("");
     this._ignorePrefObserver = false;
 
     Svc.Prefs.set("lastversion", WEAVE_VERSION);
 
     this.identity.deleteSyncCredentials();
 
-    Svc.Obs.notify("weave:service:start-over:finish");
+    // If necessary, reset the identity manager, then re-initialize it so the
+    // FxA manager is used.  This is configurable via a pref - mainly for tests.
+    let keepIdentity = false;
+    try {
+      keepIdentity = Services.prefs.getBoolPref("services.sync-testing.startOverKeepIdentity");
+    } catch (_) { /* no such pref */ }
+    if (keepIdentity) {
+      Svc.Obs.notify("weave:service:start-over:finish");
+      return;
+    }
+
+    this.identity.username = "";
+    Services.prefs.clearUserPref("services.sync.fxaccounts.enabled");
+    this.status.__authManager = null;
+    this.identity = Status._authManager;
+    this._clusterManager = this.identity.createClusterManager(this);
+
+    // Tell the new identity manager to initialize itself
+    this.identity.initialize().then(() => {
+      Svc.Obs.notify("weave:service:start-over:finish");
+    }).then(null, err => {
+      this._log.error("startOver failed to re-initialize the identity manager: " + err);
+      // Still send the observer notification so the current state is
+      // reflected in the UI.
+      Svc.Obs.notify("weave:service:start-over:finish");
+    });
   },
 
   persistLogin: function persistLogin() {
     try {
       this.identity.persistCredentials(true);
     } catch (ex) {
       this._log.info("Unable to persist credentials: " + ex);
     }
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -596,16 +596,48 @@ this.Utils = {
   calculateBackoff: function calculateBackoff(attempts, baseInterval,
                                               statusInterval) {
     let backoffInterval = attempts *
                           (Math.floor(Math.random() * baseInterval) +
                            baseInterval);
     return Math.max(Math.min(backoffInterval, MAXIMUM_BACKOFF_INTERVAL),
                     statusInterval);
   },
+
+  /**
+   * Return a set of hostnames (including the protocol) which may have
+   * credentials for sync itself stored in the login manager.
+   *
+   * In general, these hosts will not have their passwords synced, will be
+   * reset when we drop sync credentials, etc.
+   */
+  getSyncCredentialsHosts: function() {
+    // This is somewhat expensive and the result static, so we cache the result.
+    if (this._syncCredentialsHosts) {
+      return this._syncCredentialsHosts;
+    }
+    let result = new Set();
+    // the legacy sync host.
+    result.add(PWDMGR_HOST);
+    // The FxA hosts - these almost certainly all have the same hostname, but
+    // better safe than sorry...
+    for (let prefName of ["identity.fxaccounts.remote.force_auth.uri",
+                          "identity.fxaccounts.remote.uri",
+                          "identity.fxaccounts.settings.uri"]) {
+      let prefVal;
+      try {
+        prefVal = Services.prefs.getCharPref(prefName);
+      } catch (_) {
+        continue;
+      }
+      let uri = Services.io.newURI(prefVal, null, null);
+      result.add(uri.prePath);
+    }
+    return this._syncCredentialsHosts = result;
+  },
 };
 
 XPCOMUtils.defineLazyGetter(Utils, "_utf8Converter", function() {
   let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                     .createInstance(Ci.nsIScriptableUnicodeConverter);
   converter.charset = "UTF-8";
   return converter;
 });
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/unit/test_fxa_startOver.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource://testing-common/services/sync/utils.js");
+Cu.import("resource://services-sync/identity.js");
+Cu.import("resource://services-sync/browserid_identity.js");
+Cu.import("resource://services-sync/service.js");
+
+function run_test() {
+  initTestLogging("Trace");
+  run_next_test();
+}
+
+add_task(function* test_startover() {
+  let oldValue = Services.prefs.getBoolPref("services.sync-testing.startOverKeepIdentity", true);
+  Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", false);
+
+  yield configureIdentity({username: "johndoe"});
+  // The pref that forces FxA identities should not be set.
+  do_check_false(Services.prefs.getBoolPref("services.sync.fxaccounts.enabled"));
+  // And the boolean flag on the xpcom service should reflect this.
+  let xps = Cc["@mozilla.org/weave/service;1"]
+            .getService(Components.interfaces.nsISupports)
+            .wrappedJSObject;
+  do_check_false(xps.fxAccountsEnabled);
+
+  // we expect the "legacy" provider (but can't instanceof that, as BrowserIDManager
+  // extends it)
+  do_check_false(Service.identity instanceof BrowserIDManager);
+  Service.login();
+  // We should have a cluster URL
+  do_check_true(Service.clusterURL.length > 0);
+
+  // remember some stuff so we can reset it after.
+  let oldIdentidy = Service.identity;
+  let oldClusterManager = Service._clusterManager;
+  let deferred = Promise.defer();
+  Services.obs.addObserver(function observeStartOverFinished() {
+    Services.obs.removeObserver(observeStartOverFinished, "weave:service:start-over:finish");
+    deferred.resolve();
+  }, "weave:service:start-over:finish", false);
+
+  Service.startOver();
+  yield deferred; // wait for the observer to fire.
+
+  // should have reset the pref that indicates if FxA is enabled.
+  do_check_true(Services.prefs.getBoolPref("services.sync.fxaccounts.enabled"));
+  // the xpcom service should agree FxA is enabled.
+  do_check_true(xps.fxAccountsEnabled);
+  // should have swapped identities.
+  do_check_true(Service.identity instanceof BrowserIDManager);
+  // should have clobbered the cluster URL
+  do_check_eq(Service.clusterURL, "");
+
+  // reset the world.
+  Service.identity = oldIdentity = Service.identity;
+  Service._clusterManager = Service._clusterManager;
+  Services.prefs.setBoolPref("services.sync.fxaccounts.enabled", false);
+
+  Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", oldValue);
+});
--- a/services/sync/tests/unit/xpcshell.ini
+++ b/services/sync/tests/unit/xpcshell.ini
@@ -116,16 +116,19 @@ skip-if = os == "android"
 [test_notifications.js]
 [test_score_triggers.js]
 [test_sendcredentials_controller.js]
 [test_status.js]
 [test_status_checkSetup.js]
 [test_syncscheduler.js]
 [test_upgrade_old_sync_key.js]
 
+# Firefox Accounts specific tests
+[test_fxa_startOver.js]
+
 # Finally, we test each engine.
 [test_addons_engine.js]
 run-sequentially = Hardcoded port in static files.
 [test_addons_reconciler.js]
 [test_addons_store.js]
 run-sequentially = Hardcoded port in static files.
 [test_addons_tracker.js]
 [test_bookmark_batch_fail.js]
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -773,17 +773,17 @@ SpecialPowersAPI.prototype = {
       return;
     }
 
     /* Set lock and get prefs from the _pendingPrefs queue */
     this._applyingPermissions = true;
     var transaction = this._pendingPermissions.shift();
     var pendingActions = transaction[0];
     var callback = transaction[1];
-    lastPermission = pendingActions[pendingActions.length-1];
+    var lastPermission = pendingActions[pendingActions.length-1];
 
     var self = this;
     var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
     this._permissionObserver._lastPermission = lastPermission;
     this._permissionObserver._callback = callback;
     this._permissionObserver._nextCallback = function () {
         self._applyingPermissions = false;
         // Now apply any permissions that may have been queued while we were applying
--- a/toolkit/components/osfile/modules/osfile_async_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_async_front.jsm
@@ -780,16 +780,31 @@ File.copy = function copy(sourcePath, de
  * behavior may not be the same across all platforms.
  */
 File.move = function move(sourcePath, destPath, options) {
   return Scheduler.post("move", [Type.path.toMsg(sourcePath),
     Type.path.toMsg(destPath), options], [sourcePath, destPath]);
 };
 
 /**
+ * Gets the number of bytes available on disk to the current user.
+ *
+ * @param {string} Platform-specific path to a directory on the disk to 
+ * query for free available bytes.
+ *
+ * @return {number} The number of bytes available for the current user.
+ * @throws {OS.File.Error} In case of any error.
+ */
+File.getAvailableFreeSpace = function getAvailableFreeSpace(sourcePath) {
+  return Scheduler.post("getAvailableFreeSpace",
+    [Type.path.toMsg(sourcePath)], sourcePath
+  ).then(Type.uint64_t.fromMsg);
+};
+
+/**
  * Remove an empty directory.
  *
  * @param {string} path The name of the directory to remove.
  * @param {*=} options Additional options.
  *   - {bool} ignoreAbsent If |true|, do not fail if the
  *     directory does not exist yet.
  */
 File.removeEmptyDir = function removeEmptyDir(path, options) {
--- a/toolkit/components/osfile/modules/osfile_async_worker.js
+++ b/toolkit/components/osfile/modules/osfile_async_worker.js
@@ -319,16 +319,20 @@ const EXCEPTION_NAMES = {
    copy: function copy(sourcePath, destPath, options) {
      return File.copy(Type.path.fromMsg(sourcePath),
        Type.path.fromMsg(destPath), options);
    },
    move: function move(sourcePath, destPath, options) {
      return File.move(Type.path.fromMsg(sourcePath),
        Type.path.fromMsg(destPath), options);
    },
+   getAvailableFreeSpace: function getAvailableFreeSpace(sourcePath) {
+     return Type.uint64_t.toMsg(
+       File.getAvailableFreeSpace(Type.path.fromMsg(sourcePath)));
+   },
    makeDir: function makeDir(path, options) {
      return File.makeDir(Type.path.fromMsg(path), options);
    },
    removeEmptyDir: function removeEmptyDir(path, options) {
      return File.removeEmptyDir(Type.path.fromMsg(path), options);
    },
    remove: function remove(path) {
      return File.remove(Type.path.fromMsg(path));
--- a/toolkit/components/osfile/modules/osfile_unix_back.jsm
+++ b/toolkit/components/osfile/modules/osfile_unix_back.jsm
@@ -213,16 +213,34 @@
            Const.OSFILE_OFFSETOF_TIMEVAL_TV_USEC,
            "tv_usec",
            Type.long.implementation);
          Type.timeval = timeval.getType();
          Type.timevals = new SharedAll.Type("two timevals",
            ctypes.ArrayType(Type.timeval.implementation, 2));
        }
 
+       // Types fsblkcnt_t and fsfilcnt_t, used by structure |statvfs|
+       Type.fsblkcnt_t =
+         Type.uintn_t(Const.OSFILE_SIZEOF_FSBLKCNT_T).withName("fsblkcnt_t");
+
+       // Structure |statvfs|
+       // Use an hollow structure
+       {
+         let statvfs = new SharedAll.HollowStructure("statvfs",
+           Const.OSFILE_SIZEOF_STATVFS);
+
+         statvfs.add_field_at(Const.OSFILE_OFFSETOF_STATVFS_F_BSIZE,
+                        "f_bsize", Type.unsigned_long.implementation);
+         statvfs.add_field_at(Const.OSFILE_OFFSETOF_STATVFS_F_BAVAIL,
+                        "f_bavail", Type.fsblkcnt_t.implementation);
+
+         Type.statvfs = statvfs.getType();
+       }
+
        // Declare libc functions as functions of |OS.Unix.File|
 
        // Finalizer-related functions
        libc.declareLazy(SysFile, "_close",
                         "close", ctypes.default_abi,
                         /*return */ctypes.int,
                         /*fd*/     ctypes.int);
 
@@ -501,16 +519,22 @@
                     /*return*/ Type.long,
                     /*fd_in*/  Type.fd,
                     /*off_in*/ Type.off_t.in_ptr,
                     /*fd_out*/ Type.fd,
                     /*off_out*/Type.off_t.in_ptr,
                     /*len*/    Type.size_t,
                     /*flags*/  Type.unsigned_int); // Linux/Android-specific
 
+       libc.declareLazyFFI(SysFile,  "statvfs",
+                               "statvfs", ctypes.default_abi,
+                    /*return*/ Type.negativeone_or_nothing,
+                    /*path*/   Type.path,
+                    /*buf*/    Type.statvfs.out_ptr);
+
        libc.declareLazyFFI(SysFile,  "symlink",
                                "symlink", ctypes.default_abi,
                     /*return*/ Type.negativeone_or_nothing,
                     /*source*/ Type.path,
                     /*dest*/   Type.path);
 
        libc.declareLazyFFI(SysFile,  "truncate",
                               "truncate", ctypes.default_abi,
--- a/toolkit/components/osfile/modules/osfile_unix_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_unix_front.jsm
@@ -347,16 +347,37 @@
              ctypes.errno == Const.ENOENT) {
            return;
          }
          throw new File.Error("removeEmptyDir");
        }
      };
 
      /**
+      * Gets the number of bytes available on disk to the current user.
+      *
+      * @param {string} sourcePath Platform-specific path to a directory on 
+      * the disk to query for free available bytes.
+      *
+      * @return {number} The number of bytes available for the current user.
+      * @throws {OS.File.Error} In case of any error.
+      */
+     File.getAvailableFreeSpace = function Unix_getAvailableFreeSpace(sourcePath) {
+       let fileSystemInfo = new Type.statvfs.implementation();
+       let fileSystemInfoPtr = fileSystemInfo.address();
+
+       throw_on_negative("statvfs",  UnixFile.statvfs(sourcePath, fileSystemInfoPtr));
+
+       let bytes = new Type.uint64_t.implementation(
+                        fileSystemInfo.f_bsize * fileSystemInfo.f_bavail);
+
+       return bytes.value;
+     };
+
+     /**
       * Default mode for opening directories.
       */
      const DEFAULT_UNIX_MODE_DIR = Const.S_IRWXU;
 
      /**
       * Create a directory.
       *
       * @param {string} path The name of the directory.
--- a/toolkit/components/osfile/modules/osfile_win_back.jsm
+++ b/toolkit/components/osfile/modules/osfile_win_back.jsm
@@ -291,16 +291,24 @@
 
        libc.declareLazyFFI(SysFile, "GetCurrentDirectory",
          "GetCurrentDirectoryW", ctypes.winapi_abi,
                     /*return*/ Type.zero_or_DWORD,
                     /*length*/ Type.DWORD,
                     /*buf*/    Type.out_path
                    );
 
+       libc.declareLazyFFI(SysFile, "GetDiskFreeSpaceEx",
+         "GetDiskFreeSpaceExW", ctypes.winapi_abi,
+                    /*return*/ Type.zero_or_nothing,
+                    /*directoryName*/ Type.path,
+                    /*freeBytesForUser*/ Type.uint64_t.out_ptr,
+                    /*totalBytesForUser*/ Type.uint64_t.out_ptr,
+                    /*freeTotalBytesOnDrive*/ Type.uint64_t.out_ptr);
+
        libc.declareLazyFFI(SysFile, "GetFileInformationByHandle",
          "GetFileIn