Merge m-c to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 08 Oct 2015 16:11:21 +0200
changeset 266866 0e9f34142a508d2b8266c34a314ef71e21a2cded
parent 266865 cb177e743d72a2dc6cb8acf441fca24a6431d4ab (current diff)
parent 266809 1f4cf75c894862cf3634d6014d8de9c807a054a7 (diff)
child 266867 ca546bb0c669ec55f6264c80c86992d0fc18f10b
push id29499
push userkwierso@gmail.com
push dateThu, 08 Oct 2015 21:29:10 +0000
treeherdermozilla-central@46da59584acb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to mozilla-inbound
browser/themes/linux/aboutCertError.css
browser/themes/osx/aboutCertError.css
browser/themes/windows/aboutCertError.css
devtools/client/sourceeditor/css-tokenizer.js
devtools/client/styleinspector/css-parsing-utils.js
devtools/client/styleinspector/test/unit/.eslintrc
devtools/client/styleinspector/test/unit/test_escapeCSSComment.js
devtools/client/styleinspector/test/unit/test_parseDeclarations.js
devtools/client/styleinspector/test/unit/test_parsePseudoClassesAndAttributes.js
devtools/client/styleinspector/test/unit/test_parseSingleValue.js
devtools/client/styleinspector/test/unit/test_rewriteDeclarations.js
devtools/client/styleinspector/test/unit/xpcshell.ini
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b99837aa2294348317bcae68acabe71d9a83d774"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="31a7849fe9a8b743d6f5e5facc212f0ef9d57499"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
@@ -117,17 +117,17 @@
   <project name="platform/system/security" path="system/security" revision="ee8068b9e7bfb2770635062fc9c2035be2142bd8"/>
   <project name="platform/system/vold" path="system/vold" revision="42fa2a0f14f965970a4b629a176bbd2666edf017"/>
   <project name="platform/external/curl" path="external/curl" revision="e68addd988448959ea8157c5de637346b4180c33"/>
   <project name="platform/external/icu4c" path="external/icu4c" revision="d3ec7428eb276db43b7ed0544e09344a6014806c"/>
   <project name="platform/hardware/libhardware_legacy" path="hardware/libhardware_legacy" revision="76c4bf4bc430a1b8317f2f21ef735867733e50cc"/>
   <project name="platform/system/media" path="system/media" revision="c1332c21c608f4932a6d7e83450411cde53315ef"/>
   <default remote="caf" revision="LNX.LA.3.5.2.1.1" sync-j="4"/>
   <!-- Platform common things -->
-  <project name="device-shinano-common" path="device/sony/shinano-common" remote="b2g" revision="2f09386bb130b0111219cfc830316797392d48b0"/>
+  <project name="device-shinano-common" path="device/sony/shinano-common" remote="b2g" revision="4a5672c356ba539095109d827ba103cd8dfdcf1a"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="1bb28abbc215f45220620af5cd60a8ac1be93722"/>
   <project name="device/qcom/common" path="device/qcom/common" revision="2501e5940ba69ece7654ff85611c76ae5bda299c"/>
   <project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="d620691cad7aee780018e98159ff03bf99840317"/>
   <project name="init_sh" path="external/init_sh" remote="b2g" revision="feb58d2b397e45ead9b904d5c4d9255df408db56"/>
   <project name="platform_external_bluetooth_bluedroid" path="external/bluetooth/bluedroid" remote="b2g" revision="70f536bd97d901b96b94669ae1aa2fd0fb54b258"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="0a01977f34d6e86fe23d6c0ec75e96ba988bbebb"/>
   <project name="platform_external_libnfc-pn547" path="external/libnfc-pn547" remote="b2g" revision="5bb999b84b8adc14f6bea004d523ba258dea8188"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="5b71e40213f650459e95d35b6f14af7e88d8ab62"/>
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b99837aa2294348317bcae68acabe71d9a83d774"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="31a7849fe9a8b743d6f5e5facc212f0ef9d57499"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <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="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <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="b99837aa2294348317bcae68acabe71d9a83d774"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="4ace9aaee0e048dfda11bb787646c59982a3dc80"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="065f6361461030d32c6dc08d716b013bfadab1d9"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="31a7849fe9a8b743d6f5e5facc212f0ef9d57499"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- 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
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b99837aa2294348317bcae68acabe71d9a83d774"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="31a7849fe9a8b743d6f5e5facc212f0ef9d57499"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <!-- 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-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b99837aa2294348317bcae68acabe71d9a83d774"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="31a7849fe9a8b743d6f5e5facc212f0ef9d57499"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b99837aa2294348317bcae68acabe71d9a83d774"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="31a7849fe9a8b743d6f5e5facc212f0ef9d57499"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <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="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <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="b99837aa2294348317bcae68acabe71d9a83d774"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="4ace9aaee0e048dfda11bb787646c59982a3dc80"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="065f6361461030d32c6dc08d716b013bfadab1d9"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="31a7849fe9a8b743d6f5e5facc212f0ef9d57499"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/config.json
+++ b/b2g/config/flame-kk/config.json
@@ -1,13 +1,13 @@
 {
     "config_version": 2,
     "tooltool_manifest": "releng-flame-kk.tt",
     "mock_target": "mozilla-centos6-x86_64",
-    "mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "glibc-devel.i686", "libstdc++.i686", "zlib-devel.i686", "ncurses-devel.i686", "libX11-devel.i686", "mesa-libGL-devel.i686", "mesa-libGL-devel", "libX11-devel", "git", "libxml2", "dosfstools"],
+    "mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "glibc-devel.i686", "libstdc++.i686", "zlib-devel.i686", "ncurses-devel.i686", "libX11-devel.i686", "mesa-libGL-devel.i686", "mesa-libGL-devel", "libX11-devel", "git", "libxml2", "dosfstools", "java-1.6.0-openjdk"],
     "mock_files": [
         ["/home/cltbld/.ssh", "/home/mock_mozilla/.ssh"],
         ["/builds/crash-stats-api.token", "/builds/crash-stats-api.token"]
     ],
     "build_targets": ["", "blobfree"],
     "upload_files": [
         "{objdir}/dist/b2g-*.crashreporter-symbols.zip",
         "{objdir}/dist/b2g-*.tar.gz",
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b99837aa2294348317bcae68acabe71d9a83d774"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="31a7849fe9a8b743d6f5e5facc212f0ef9d57499"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "b99837aa2294348317bcae68acabe71d9a83d774", 
+        "git_revision": "4973f57cd8f9a62a95f783a24eac32da2bde99fc", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "cd2549520049a8532b966d3edda167f7bc444fba", 
+    "revision": "c728de03bc96ef160fd5a662b3efcd5cf2c2b844", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4-kk/sources.xml
+++ b/b2g/config/nexus-4-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b99837aa2294348317bcae68acabe71d9a83d774"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="31a7849fe9a8b743d6f5e5facc212f0ef9d57499"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -13,17 +13,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b99837aa2294348317bcae68acabe71d9a83d774"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="31a7849fe9a8b743d6f5e5facc212f0ef9d57499"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <!-- 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/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b99837aa2294348317bcae68acabe71d9a83d774"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4973f57cd8f9a62a95f783a24eac32da2bde99fc"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7e0fe55ac52323eace5a6119ab2b911fc4f64495"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="31a7849fe9a8b743d6f5e5facc212f0ef9d57499"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
@@ -136,17 +136,17 @@
   <project name="platform_frameworks_av" path="frameworks/av" remote="b2g" revision="e372b6a77f71c8f9fbbf6f8adbfa7bf8ef45dc60"/>
   <project name="platform_frameworks_base" path="frameworks/base" remote="b2g" revision="04e26ebdc36ca83f4ee3e9e2082b3fcf04c5b971"/>
   <project name="platform_frameworks_wilhelm" path="frameworks/wilhelm" remote="b2g" revision="0dbf5baafadf6d233c0a29e392fa3293f0121673"/>
   <project name="platform_system_core" path="system/core" remote="b2g" revision="f594bc64eacac490857748b1139ffcb34c856bbd"/>
   <project name="platform_external_sepolicy" path="external/sepolicy" remote="b2g" revision="3f6be48a46c54dd8cacaf216ab5b145de5ffefd2"/>
   <default remote="caf" revision="refs/tags/android-5.1.0_r1" sync-j="4"/>
   <!-- Nexus 5 specific things -->
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="fe7df1bc8dd0fd71571505d7be1c31a4ad1e40fb"/>
-  <project name="device-hammerhead" path="device/lge/hammerhead" remote="b2g" revision="844d9fe1c7114c6d586fbea611cbb7038413d762"/>
+  <project name="device-hammerhead" path="device/lge/hammerhead" remote="b2g" revision="2900636d764df131e7914923c1ca813fc8879a7b"/>
   <project name="device_lge_hammerhead-kernel" path="device/lge/hammerhead-kernel" remote="b2g" revision="8b3ffcfdd3d3852eca5488628f8bb2a08acbffa7"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="5d0ae53d9588c3d70c005aec9be94af9a534de16"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="c15b6e266136cd0cdd9b94d0bbed1962d9dd6672"/>
   <project name="platform/hardware/broadcom/libbt" path="hardware/broadcom/libbt" revision="399fe3d3c8f38c599a56becddc456133e62a5d70"/>
   <project name="platform/hardware/broadcom/wlan" path="hardware/broadcom/wlan" revision="3f3134d5cb19d5ace48d36d0100467a545d430eb"/>
   <project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="810c3dd29d009822a71eba9910e429a9ad114533"/>
   <project name="hardware_qcom_display" path="hardware/qcom/display" remote="b2g" revision="d8a56d7215bd26a61e43dcde20e64826a5fec265"/>
   <project name="platform/hardware/qcom/keymaster" path="hardware/qcom/keymaster" revision="eaede9f8bc206736a889bc57817047c31e205589"/>
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -224,30 +224,24 @@ var StarUI = {
         this._anchorToolbarButton = parent;
         parent.setAttribute("open", "true");
       }
     }
     this.panel.openPopup(aAnchorElement, aPosition);
 
     gEditItemOverlay.initPanel({ node: aNode
                                , hiddenRows: ["description", "location",
-                                              "loadInSidebar", "keyword"] });
+                                              "loadInSidebar", "keyword"]
+                               , focusedElement: "preferred" });
   }),
 
   panelShown:
   function SU_panelShown(aEvent) {
     if (aEvent.target == this.panel) {
-      if (!this._element("editBookmarkPanelContent").hidden) {
-        let fieldToFocus = "editBMPanel_" +
-          gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField");
-        var elt = this._element(fieldToFocus);
-        elt.focus();
-        elt.select();
-      }
-      else {
+      if (this._element("editBookmarkPanelContent").hidden) {
         // Note this isn't actually used anymore, we should remove this
         // once we decide not to bring back the page bookmarked notification
         this.panel.focus();
       }
     }
   },
 
   quitEditMode: function SU_quitEditMode() {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -250,16 +250,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/SimpleServiceDiscovery.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
   "resource://gre/modules/ReaderMode.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderParent",
   "resource:///modules/ReaderParent.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
+  "resource://gre/modules/LoginManagerParent.jsm");
+
 var gInitialPages = [
   "about:blank",
   "about:newtab",
   "about:home",
   "about:privatebrowsing",
   "about:welcomeback",
   "about:sessionrestore"
 ];
@@ -1187,16 +1190,20 @@ var gBrowserInit = {
         SessionStore.reviveCrashedTab(tab);
         break;
       case "restoreAll":
         SessionStore.reviveAllCrashedTabs();
         break;
       }
     }, false, true);
 
+    gBrowser.addEventListener("InsecureLoginFormsStateChange", function() {
+      gIdentityHandler.refreshForInsecureLoginForms();
+    });
+
     let uriToLoad = this._getUriToLoad();
     if (uriToLoad && uriToLoad != "about:blank") {
       if (uriToLoad instanceof Ci.nsISupportsArray) {
         let count = uriToLoad.Count();
         let specs = [];
         for (let i = 0; i < count; i++) {
           let urisstring = uriToLoad.GetElementAt(i).QueryInterface(Ci.nsISupportsString);
           specs.push(urisstring.data);
@@ -6985,28 +6992,39 @@ var gIdentityHandler = {
     this._sslStatus = gBrowser.securityUI
                               .QueryInterface(Ci.nsISSLStatusProvider)
                               .SSLStatus;
     if (this._sslStatus) {
       this._sslStatus.QueryInterface(Ci.nsISSLStatus);
     }
 
     // Then, update the user interface with the available data.
-
-    if (this._identityBox) {
-      this.refreshIdentityBlock();
-    }
+    this.refreshIdentityBlock();
 
     // NOTE: We do NOT update the identity popup (the control center) when
     // we receive a new security state. If the user opened the popup and looks
     // at the provided information we don't want to suddenly change the panel
     // contents.
   },
 
   /**
+   * This is called asynchronously when requested by the Logins module, after
+   * the insecure login forms state for the page has been updated.
+   */
+  refreshForInsecureLoginForms() {
+    // Check this._uri because we don't want to refresh the user interface if
+    // this is called before the first page load in the window for any reason.
+    if (!this._uri) {
+      Cu.reportError("Unexpected early call to refreshForInsecureLoginForms.");
+      return;
+    }
+    this.refreshIdentityBlock();
+  },
+
+  /**
    * Attempt to provide proper IDN treatment for host names
    */
   getEffectiveHost: function() {
     if (!this._IDNService)
       this._IDNService = Cc["@mozilla.org/network/idn-service;1"]
                          .getService(Ci.nsIIDNService);
     try {
       return this._IDNService.convertToDisplayIDN(this._uri.host, {});
@@ -7032,16 +7050,20 @@ var gIdentityHandler = {
     }
     return "unknownIdentity";
   },
 
   /**
    * Updates the identity block user interface with the data from this object.
    */
   refreshIdentityBlock() {
+    if (!this._identityBox) {
+      return;
+    }
+
     let icon_label = "";
     let tooltip = "";
     let icon_country_label = "";
     let icon_labels_dir = "ltr";
 
     if (this._isSecureInternalUI) {
       this._identityBox.className = "chromeUI";
       let brandBundle = document.getElementById("bundle_brand");
@@ -7100,16 +7122,21 @@ var gIdentityHandler = {
         } else if (this._isMixedActiveContentBlocked) {
           this._identityBox.classList.add("mixedDisplayContentLoadedActiveBlocked");
         } else if (this._isMixedPassiveContentLoaded) {
           this._identityBox.classList.add("mixedDisplayContent");
         } else {
           this._identityBox.classList.add("weakCipher");
         }
       }
+      if (LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser)) {
+        // Insecure login forms can only be present on "unknown identity"
+        // pages, either already insecure or with mixed active content loaded.
+        this._identityBox.classList.add("insecureLoginForms");
+      }
       tooltip = gNavigatorBundle.getString("identity.unknown.tooltip");
     }
 
     // Push the appropriate strings out to the UI
     this._identityBox.tooltipText = tooltip;
     this._identityIconLabel.value = icon_label;
     this._identityIconCountryLabel.value = icon_country_label;
     // Set cropping and direction
@@ -7137,16 +7164,22 @@ var gIdentityHandler = {
     } else if (this._isURILoadedFromFile) {
       connection = "file";
     } else if (this._isEV) {
       connection = "secure-ev";
     } else if (this._isSecure) {
       connection = "secure";
     }
 
+    // Determine if there are insecure login forms.
+    let loginforms = "secure";
+    if (LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser)) {
+      loginforms = "insecure";
+    }
+
     // Determine the mixed content state.
     let mixedcontent = [];
     if (this._isMixedPassiveContentLoaded) {
       mixedcontent.push("passive-loaded");
     }
     if (this._isMixedActiveContentLoaded) {
       mixedcontent.push("active-loaded");
     } else if (this._isMixedActiveContentBlocked) {
@@ -7174,16 +7207,17 @@ var gIdentityHandler = {
       } else {
         elem.removeAttribute(attr);
       }
     }
 
     for (let id of elementIDs) {
       let element = document.getElementById(id);
       updateAttribute(element, "connection", connection);
+      updateAttribute(element, "loginforms", loginforms);
       updateAttribute(element, "ciphers", ciphers);
       updateAttribute(element, "mixedcontent", mixedcontent);
       updateAttribute(element, "isbroken", this._isBroken);
     }
 
     // Initialize the optional strings to empty values
     let supplemental = "";
     let verifier = "";
--- a/browser/base/content/test/alerts/browser_notification_open_settings.js
+++ b/browser/base/content/test/alerts/browser_notification_open_settings.js
@@ -8,11 +8,11 @@ add_task(function* test_settingsOpen() {
   }, function* dummyTabTask(aBrowser) {
     let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:preferences#content");
     info("simulate a notifications-open-settings notification");
     let uri = NetUtil.newURI("https://example.com");
     let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
     Services.obs.notifyObservers(principal, "notifications-open-settings", null);
     let tab = yield tabPromise;
     ok(tab, "The notification settings tab opened");
-    BrowserTestUtils.removeTab(tab);
+    yield BrowserTestUtils.removeTab(tab);
   });
 });
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -264,17 +264,17 @@ tags = mcb
 [browser_bug832435.js]
 [browser_bug839103.js]
 [browser_bug880101.js]
 [browser_bug882977.js]
 [browser_bug902156.js]
 tags = mcb
 [browser_bug906190.js]
 tags = mcb
-skip-if = buildapp == "mulet" || e10s # Bug 1093642 - test manipulates content and relies on content focus
+skip-if = buildapp == "mulet" || e10s || os == "linux" # Bug 1093642 - test manipulates content and relies on content focus, Bug 1212520 - Re-enable on Linux
 [browser_mixedContentFromOnunload.js]
 tags = mcb
 [browser_mixedContentFramesOnHttp.js]
 tags = mcb
 [browser_bug970746.js]
 [browser_bug1015721.js]
 skip-if = os == 'win' || e10s # Bug 1159268 - Need a content-process safe version of synthesizeWheel
 [browser_bug1064280_changeUrlInPinnedTab.js]
@@ -318,16 +318,17 @@ support-files = fxa_profile_handler.sjs
 [browser_fxa_web_channel.js]
 [browser_gestureSupport.js]
 skip-if = e10s # Bug 863514 - no gesture support.
 [browser_getshortcutoruri.js]
 [browser_hide_removing.js]
 [browser_homeDrop.js]
 skip-if = buildapp == 'mulet'
 [browser_identity_UI.js]
+[browser_insecureLoginForms.js]
 [browser_keywordBookmarklets.js]
 skip-if = e10s # Bug 1102025 - different principals for the bookmarklet only in e10s mode (unclear if test or 'real' issue)
 [browser_keywordSearch.js]
 [browser_keywordSearch_postData.js]
 [browser_lastAccessedTab.js]
 skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bug 969405)
 [browser_locationBarCommand.js]
 skip-if = os == "linux" # Linux: Intermittent failures, bug 917535
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_insecureLoginForms.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Load directly from the browser-chrome support files of login tests.
+const testUrlPath =
+      "://example.com/browser/toolkit/components/passwordmgr/test/browser/";
+
+/**
+ * Waits for the given number of occurrences of InsecureLoginFormsStateChange
+ * on the given browser element.
+ */
+function waitForInsecureLoginFormsStateChange(browser, count) {
+  return BrowserTestUtils.waitForEvent(browser, "InsecureLoginFormsStateChange",
+                                       false, () => --count == 0);
+}
+
+/**
+ * Checks the insecure login forms logic for the identity block.
+ */
+add_task(function* test_simple() {
+  for (let scheme of ["http", "https"]) {
+    let tab = gBrowser.addTab(scheme + testUrlPath + "form_basic.html");
+    let browser = tab.linkedBrowser;
+    yield Promise.all([
+      BrowserTestUtils.switchTab(gBrowser, tab),
+      BrowserTestUtils.browserLoaded(browser),
+      // One event is triggered by pageshow and one by DOMFormHasPassword.
+      waitForInsecureLoginFormsStateChange(browser, 2),
+    ]);
+
+    let { gIdentityHandler } = gBrowser.ownerGlobal;
+    gIdentityHandler._identityBox.click();
+    document.getElementById("identity-popup-security-expander").click();
+
+    if (scheme == "http") {
+      let identityBoxImage = gBrowser.ownerGlobal
+            .getComputedStyle(document.getElementById("page-proxy-favicon"), "")
+            .getPropertyValue("list-style-image");
+      let securityViewBG = gBrowser.ownerGlobal
+            .getComputedStyle(document.getElementById("identity-popup-securityView"), "")
+            .getPropertyValue("background-image");
+      let securityContentBG = gBrowser.ownerGlobal
+            .getComputedStyle(document.getElementById("identity-popup-security-content"), "")
+            .getPropertyValue("background-image");
+      is(identityBoxImage,
+         "url(\"chrome://browser/skin/identity-mixed-active-loaded.svg\")",
+         "Using expected icon image in the identity block");
+      is(securityViewBG,
+         "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
+         "Using expected icon image in the Control Center main view");
+      is(securityContentBG,
+         "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
+         "Using expected icon image in the Control Center subview");
+    }
+
+    // Messages should be visible when the scheme is HTTP, and invisible when
+    // the scheme is HTTPS.
+    is(Array.every(document.querySelectorAll("[when-loginforms=insecure]"),
+                   element => !is_hidden(element)),
+       scheme == "http",
+       "The relevant messages should visible or hidden.");
+
+    gIdentityHandler._identityPopup.hidden = true;
+    gBrowser.removeTab(tab);
+  }
+});
+
+/**
+ * Checks that the insecure login forms logic does not regress mixed content
+ * blocking messages when mixed active content is loaded.
+ */
+add_task(function* test_mixedcontent() {
+  yield new Promise(resolve => SpecialPowers.pushPrefEnv({
+    "set": [["security.mixed_content.block_active_content", false]],
+  }, resolve));
+
+  // Load the page with the subframe in a new tab.
+  let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html");
+  let browser = tab.linkedBrowser;
+  yield Promise.all([
+    BrowserTestUtils.switchTab(gBrowser, tab),
+    BrowserTestUtils.browserLoaded(browser),
+    // Two events are triggered by pageshow and one by DOMFormHasPassword.
+    waitForInsecureLoginFormsStateChange(browser, 3),
+  ]);
+
+  assertMixedContentBlockingState(browser, { activeLoaded: true,
+                                             activeBlocked: false,
+                                             passiveLoaded: false });
+
+  gBrowser.removeTab(tab);
+});
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -883,16 +883,23 @@ function assertMixedContentBlockingState
       // There is a case here with weak ciphers, but no bc tests are handling this yet.
       is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/conn-degraded.svg\")",
         "CC using degraded icon");
       is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/conn-degraded.svg\")",
         "CC using degraded icon");
     }
   }
 
+  if (activeLoaded || activeBlocked || passiveLoaded) {
+    doc.getElementById("identity-popup-security-expander").click();
+    is(Array.filter(doc.querySelectorAll("[observes=identity-popup-mcb-learn-more]"),
+                    element => !is_hidden(element)).length, 1,
+       "The 'Learn more' link should be visible once.");
+  }
+
   gIdentityHandler._identityPopup.hidden = true;
 }
 
 function makeActionURI(action, params) {
   let url = "moz-action:" + action + "," + JSON.stringify(params);
   return NetUtil.newURI(url);
 }
 
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -36,16 +36,17 @@
           <vbox id="identity-popup-security-descriptions">
             <description class="identity-popup-warning-gray"
                          when-mixedcontent="active-blocked">&identity.activeBlocked;</description>
             <description class="identity-popup-warning-yellow"
                          when-mixedcontent="passive-loaded">&identity.passiveLoaded;</description>
             <description when-mixedcontent="active-loaded">&identity.activeLoaded;</description>
             <description class="identity-popup-warning-yellow"
                          when-ciphers="weak">&identity.weakEncryption;</description>
+            <description when-loginforms="insecure">&identity.insecureLoginForms;</description>
           </vbox>
         </vbox>
         <button id="identity-popup-security-expander"
                 class="identity-popup-expander"
                 when-connection="not-secure secure secure-ev"
                 oncommand="gIdentityHandler.toggleSubView('security', this)"/>
       </hbox>
 
@@ -111,17 +112,21 @@
                      when-connection="secure-ev"
                      class="header"/>
         <description id="identity-popup-content-supplemental"
                      when-connection="secure-ev"/>
         <description id="identity-popup-content-verifier"
                      when-connection="secure secure-ev"/>
 
         <!-- Connection is Not Secure -->
-        <description when-connection="not-secure">&identity.description.insecure;</description>
+        <description when-connection="not-secure"
+                     and-when-loginforms="secure">&identity.description.insecure;</description>
+
+        <!-- Insecure login forms -->
+        <description when-loginforms="insecure">&identity.description.insecureLoginForms;</description>
 
         <!-- Weak Cipher -->
         <description when-ciphers="weak">&identity.description.weakCipher;</description>
         <description class="identity-popup-warning-yellow"
                      when-ciphers="weak">&identity.description.weakCipher2;</description>
 
         <!-- Active Mixed Content Blocked -->
         <description class="identity-popup-warning-gray"
@@ -133,18 +138,24 @@
                      when-mixedcontent="passive-loaded">&identity.description.passiveLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
 
         <!-- Passive Mixed Content Loaded, Active Mixed Content Blocked -->
         <description when-mixedcontent="passive-loaded active-blocked">&identity.description.passiveLoaded;</description>
         <description when-mixedcontent="passive-loaded active-blocked"
                      class="identity-popup-warning-yellow">&identity.description.passiveLoaded3; <label observes="identity-popup-mcb-learn-more"/></description>
 
         <!-- Active Mixed Content Blocking Disabled -->
-        <description when-mixedcontent="active-loaded">&identity.description.activeLoaded;</description>
-        <description when-mixedcontent="active-loaded">&identity.description.activeLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
+        <description when-mixedcontent="active-loaded"
+                     and-when-loginforms="secure">&identity.description.activeLoaded;</description>
+        <description when-mixedcontent="active-loaded"
+                     and-when-loginforms="secure">&identity.description.activeLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
+        <!-- Show only the first message when there are insecure login forms,
+             and make sure the Learn More link is included. -->
+        <description when-mixedcontent="active-loaded"
+                     and-when-loginforms="insecure">&identity.description.activeLoaded; <label observes="identity-popup-mcb-learn-more"/></description>
 
         <!-- Buttons to enable/disable mixed content blocking. -->
         <button when-mixedcontent="active-blocked"
                 label="&identity.disableMixedContentBlocking.label;"
                 accesskey="&identity.disableMixedContentBlocking.accesskey;"
                 oncommand="gIdentityHandler.disableMixedContentProtection()"/>
         <button when-mixedcontent="active-loaded"
                 label="&identity.enableMixedContentBlocking.label;"
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -10,20 +10,20 @@ html {
 button::-moz-focus-inner {
   border: 0;
 }
 
 .conversation-toolbar {
   z-index: 1020; /* required to have it superimposed to the video element */
   border: 0;
   left: 1.2rem;
-  right: .7rem;
-  height: 2.6rem;
+  right: 1.2rem;
+  height: 2.4rem;
   position: absolute;
-  bottom: 1.5rem;
+  bottom: 1.2rem;
 }
 
 html[dir="rtl"] .conversation-toolbar {
   left: .7rem;
   right: 1.2rem;
 }
 
 .idle {
@@ -43,21 +43,21 @@ html[dir="rtl"] .conversation-toolbar {
 html[dir="rtl"] .conversation-toolbar > li {
   float: right;
   margin-left: .7rem;
   margin-right: auto;
 }
 
 .conversation-toolbar .btn {
   background-position: center;
-  background-size: 28px;
+  background-size: 24px;
   background-repeat: no-repeat;
   background-color: transparent;
-  height: 28px;
-  width: 33px;
+  height: 24px;
+  width: 24px;
 }
 
 .conversation-toolbar-media-btn-group-box {
   background-position: center;
   background-repeat: no-repeat;
   background-color: transparent;
   background-image: url("../img/svg/media-group.svg");
   background-size: cover;
@@ -850,18 +850,18 @@ body[platform="win"] .share-service-drop
   border: 0.1rem solid #5cccee;
 }
 
 .media-layout {
   height: 100%;
 }
 
 .standalone-room-wrapper > .media-layout {
-  /* 50px is the header, 3em is the footer. */
-  height: calc(100% - 50px - 3em);
+  /* 50px is the header, 10px is the bottom margin. */
+  height: calc(100% - 50px - 10px);
   margin: 0 10px;
 }
 
 .media-layout > .media-wrapper {
   display: flex;
   flex-flow: column wrap;
   height: 100%;
 }
@@ -972,18 +972,18 @@ body[platform="win"] .share-service-drop
 }
 
 .media-wrapper.receiving-screen-share > .local {
   order: 4;
 }
 
 @media screen and (max-width:640px) {
   .standalone-room-wrapper > .media-layout {
-    /* 50px is height of header, 25px is height of footer. */
-    height: calc(100% - 50px - 25px);
+    /* 50px is height of header, 10px is bottom margin. */
+    height: calc(100% - 50px - 10px);
   }
 
   .media-layout > .media-wrapper {
     flex-direction: row;
     margin: 0;
     width: 100%;
   }
 
@@ -1422,18 +1422,18 @@ html[dir="rtl"] .text-chat-entry.receive
   /* Rooms specific responsive styling */
   .standalone .room-conversation-wrapper .room-inner-info-area {
     right: 0;
     margin: auto;
     width: 100%;
     left: 0;
   }
   .standalone .room-conversation-wrapper .video-layout-wrapper {
-    /* 50px: header's height; 25px: footer's height */
-    height: calc(100% - 50px - 25px);
+    /* 50px: header's height; 10px: bottom margin */
+    height: calc(100% - 50px - 10px);
   }
   .standalone .room-conversation .video_wrapper.remote_wrapper {
     width: 100%;
   }
   .standalone .room-conversation .video_wrapper.remote_wrapper.not-joined {
     width: 100%;
   }
 }
--- a/browser/components/loop/standalone/Makefile
+++ b/browser/components/loop/standalone/Makefile
@@ -20,17 +20,18 @@ install: npm_install
 
 npm_install:
 	@npm install
 
 # build the dist dir, which contains a production version of the code and
 # assets
 .PHONY: dist
 dist:
-	cp -pr content dist
+	mkdir -p dist
+	cp -pR content dist
 	NODE_ENV="production" $(NODE_LOCAL_BIN)/webpack \
 		-p -v --display-errors
 	sed 's#webappEntryPoint.js#js/standalone.js#' \
 		< content/index.html > dist/index.html
 
 .PHONY: distclean
 distclean:
 	rm -fr dist
--- a/browser/components/loop/standalone/content/css/webapp.css
+++ b/browser/components/loop/standalone/content/css/webapp.css
@@ -52,84 +52,37 @@ body,
   left: 0;
   width: 50px;
   height: 50px;
   background: transparent url(../shared/img/beta-ribbon.svg) no-repeat;
   background-size: 50px;
   z-index: 1000;
 }
 
-/* Footer */
+/* Mozilla Logo */
 
-.footer-logo {
-  width: 100px;
-  margin: 0 auto;
-  height: 30px;
+.focus-stream > .standalone-moz-logo {
+  width: 50px;
+  height: 13px;
   background-size: contain;
   background-image: url("../img/mozilla-logo.svg#logo-white");
   background-repeat: no-repeat;
-}
-
-.rooms-footer {
-  background: #000;
-  margin: 0 10px;
-  text-align: left;
-  height: 3em;
-  position: relative;
-}
-
-html[dir="rtl"] .rooms-footer {
-  text-align: right;
+  position: absolute;
+  bottom: 1.2rem;
+  right: 1.2rem;
+  left: auto;
 }
 
-.rooms-footer p {
-  /* Right-margin offset to account for .footer-logo plus 20px. */
-  /* Zero other margins due to 1em margin from reset.css. */
-  margin: 0 120px 0 0;
-  /* Vertically align in the middle. */
-  position: absolute;
-  top: 50%;
-  transform: translate(0, -50%);
-}
-
-html[dir="rtl"] .rooms-footer p {
-  margin: 0 0 0 120px;
-}
-
-.rooms-footer a {
-  color: #555;
-}
-
-.rooms-footer .footer-logo {
-  /* Vertically-align in the middle. */
-  position: absolute;
-  top: 50%;
-  transform: translate(0, -50%);
-  /* Align to the right. */
-  right: 0;
-}
-
-html[dir="rtl"] .rooms-footer .footer-logo {
-  left: 0;
+html[dir="rtl"] .focus-stream > .standalone-moz-logo {
+  left: 1.2rem;
   right: auto;
 }
 
 @media screen and (max-width:640px) {
-  .rooms-footer {
-    font-size: 80%;
-    height: 25px;
-    text-align: center;
-  }
-
-  .rooms-footer p {
-    margin: 0;
-    width: 100%;
-  }
-
-  .rooms-footer .footer-logo {
+  .focus-stream > .standalone-moz-logo {
     display: none;
   }
 }
 
 .btn-large {
   font-size: 1rem;
   padding: .3em .5rem;
 }
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -411,30 +411,16 @@ loop.standaloneRoomViews = (function(moz
              target: "_blank"}, 
             React.createElement("i", {className: "icon icon-help"})
           )
         )
       );
     }
   });
 
-  var StandaloneRoomFooter = React.createClass({displayName: "StandaloneRoomFooter",
-    propTypes: {
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
-    },
-
-    render: function() {
-      return (
-        React.createElement("footer", {className: "rooms-footer"}, 
-          React.createElement("div", {className: "footer-logo"})
-        )
-      );
-    }
-  });
-
   var StandaloneRoomView = React.createClass({displayName: "StandaloneRoomView",
     mixins: [
       Backbone.Events,
       sharedMixins.MediaSetupMixin,
       sharedMixins.RoomsAudioMixin,
       sharedMixins.DocumentTitleMixin
     ],
 
@@ -673,24 +659,36 @@ loop.standaloneRoomViews = (function(moz
                       visible: this._roomIsActive()}, 
               dispatcher: this.props.dispatcher, 
               enableHangup: this._roomIsActive(), 
               hangup: this.leaveRoom, 
               hangupButtonLabel: mozL10n.get("rooms_leave_button_label"), 
               publishStream: this.publishStream, 
               show: true, 
               video: {enabled: !this.state.videoMuted,
-                      visible: this._roomIsActive()}})
-          ), 
-          React.createElement(StandaloneRoomFooter, {dispatcher: this.props.dispatcher})
+                      visible: this._roomIsActive()}}), 
+            React.createElement(StandaloneMozLogo, {dispatcher: this.props.dispatcher})
+          )
         )
       );
     }
   });
 
+  var StandaloneMozLogo = React.createClass({displayName: "StandaloneMozLogo",
+    propTypes: {
+      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
+    },
+
+    render: function() {
+      return (
+        React.createElement("div", {className: "standalone-moz-logo"})
+      );
+    }
+  });
+
   var StandaloneRoomControllerView = React.createClass({displayName: "StandaloneRoomControllerView",
     mixins: [
       loop.store.StoreMixin("activeRoomStore")
     ],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       isFirefox: React.PropTypes.bool.isRequired
@@ -721,15 +719,14 @@ loop.standaloneRoomViews = (function(moz
       );
     }
   });
 
   return {
     StandaloneHandleUserAgentView: StandaloneHandleUserAgentView,
     StandaloneRoomControllerView: StandaloneRoomControllerView,
     StandaloneRoomFailureView: StandaloneRoomFailureView,
-    StandaloneRoomFooter: StandaloneRoomFooter,
     StandaloneRoomHeader: StandaloneRoomHeader,
     StandaloneRoomInfoArea: StandaloneRoomInfoArea,
     StandaloneRoomView: StandaloneRoomView,
     ToSView: ToSView
   };
 })(navigator.mozL10n);
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -411,30 +411,16 @@ loop.standaloneRoomViews = (function(moz
              target="_blank">
             <i className="icon icon-help"></i>
           </a>
         </header>
       );
     }
   });
 
-  var StandaloneRoomFooter = React.createClass({
-    propTypes: {
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
-    },
-
-    render: function() {
-      return (
-        <footer className="rooms-footer">
-          <div className="footer-logo" />
-        </footer>
-      );
-    }
-  });
-
   var StandaloneRoomView = React.createClass({
     mixins: [
       Backbone.Events,
       sharedMixins.MediaSetupMixin,
       sharedMixins.RoomsAudioMixin,
       sharedMixins.DocumentTitleMixin
     ],
 
@@ -674,23 +660,35 @@ loop.standaloneRoomViews = (function(moz
               dispatcher={this.props.dispatcher}
               enableHangup={this._roomIsActive()}
               hangup={this.leaveRoom}
               hangupButtonLabel={mozL10n.get("rooms_leave_button_label")}
               publishStream={this.publishStream}
               show={true}
               video={{enabled: !this.state.videoMuted,
                       visible: this._roomIsActive()}} />
+            <StandaloneMozLogo dispatcher={this.props.dispatcher}/>
           </sharedViews.MediaLayoutView>
-          <StandaloneRoomFooter dispatcher={this.props.dispatcher} />
         </div>
       );
     }
   });
 
+  var StandaloneMozLogo = React.createClass({
+    propTypes: {
+      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
+    },
+
+    render: function() {
+      return (
+        <div className="standalone-moz-logo" />
+      );
+    }
+  });
+
   var StandaloneRoomControllerView = React.createClass({
     mixins: [
       loop.store.StoreMixin("activeRoomStore")
     ],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       isFirefox: React.PropTypes.bool.isRequired
@@ -721,15 +719,14 @@ loop.standaloneRoomViews = (function(moz
       );
     }
   });
 
   return {
     StandaloneHandleUserAgentView: StandaloneHandleUserAgentView,
     StandaloneRoomControllerView: StandaloneRoomControllerView,
     StandaloneRoomFailureView: StandaloneRoomFailureView,
-    StandaloneRoomFooter: StandaloneRoomFooter,
     StandaloneRoomHeader: StandaloneRoomHeader,
     StandaloneRoomInfoArea: StandaloneRoomInfoArea,
     StandaloneRoomView: StandaloneRoomView,
     ToSView: ToSView
   };
 })(navigator.mozL10n);
--- a/browser/components/places/content/bookmarkProperties.js
+++ b/browser/components/places/content/bookmarkProperties.js
@@ -310,25 +310,27 @@ var BookmarkPropertiesPanel = {
     // the dialog is resized.
     window.addEventListener("resize", this);
 
     this._beginBatch();
 
     switch (this._action) {
       case ACTION_EDIT:
         gEditItemOverlay.initPanel({ node: this._node
-                                   , hiddenRows: this._hiddenRows });
+                                   , hiddenRows: this._hiddenRows
+                                   , focusedElement: "first" });
         acceptButton.disabled = gEditItemOverlay.readOnly;
         break;
       case ACTION_ADD:
         this._node = yield this._promiseNewItem();
         // Edit the new item
         gEditItemOverlay.initPanel({ node: this._node
                                    , hiddenRows: this._hiddenRows
-                                   , postData: this._postData });
+                                   , postData: this._postData
+                                   , focusedElement: "first" });
 
         // Empty location field if the uri is about:blank, this way inserting a new
         // url will be easier for the user, Accept button will be automatically
         // disabled by the input listener until the user fills the field.
         let locationField = this._element("locationField");
         if (locationField.value == "about:blank")
           locationField.value = "";
 
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -51,22 +51,23 @@ var gEditItemOverlay = {
     let postData = aInitInfo.postData;
     if (node && "parent" in node) {
       let parent = node.parent;
       if (parent) {
         isParentReadOnly = !PlacesUtils.nodeIsFolder(parent) ||
                             PlacesUIUtils.isContentsReadOnly(parent);
       }
     }
+    let focusedElement = aInitInfo.focusedElement;
 
     return this._paneInfo = { itemId, itemGuid, isItem,
                               isURI, uri, title,
                               isBookmark, isFolderShortcut, isParentReadOnly,
                               bulkTagging, uris,
-                              visibleRows, postData, isTag };
+                              visibleRows, postData, isTag, focusedElement };
   },
 
   get initialized() {
     return this._paneInfo != null;
   },
 
   // Backwards-compatibility getters
   get itemId() {
@@ -176,17 +177,17 @@ var gEditItemOverlay = {
     // For sanity ensure that the implementer has uninited the panel before
     // trying to init it again, or we could end up leaking due to observers.
     if (this.initialized)
       this.uninitPanel(false);
 
     let { itemId, itemGuid, isItem,
           isURI, uri, title,
           isBookmark, bulkTagging, uris,
-          visibleRows } = this._setPaneInfo(aInfo);
+          visibleRows, focusedElement } = this._setPaneInfo(aInfo);
 
     let showOrCollapse =
       (rowId, isAppropriateForInput, nameInHiddenRows = null) => {
         let visible = isAppropriateForInput;
         if (visible && "hiddenRows" in aInfo && nameInHiddenRows)
           visible &= aInfo.hiddenRows.indexOf(nameInHiddenRows) == -1;
         if (visible)
           visibleRows.add(rowId);
@@ -247,16 +248,34 @@ var gEditItemOverlay = {
     }
 
     // Observe changes.
     if (!this._observersAdded) {
       PlacesUtils.bookmarks.addObserver(this, false);
       window.addEventListener("unload", this, false);
       this._observersAdded = true;
     }
+
+    // The focusedElement possible values are:
+    //  * preferred: focus the field that the user touched first the last
+    //    time the pane was shown (either namePicker or tagsField)
+    //  * first: focus the first non collapsed textbox
+    // Note: since all controls are collapsed by default, we don't get the
+    // default XUL dialog behavior, that selects the first control, so we set
+    // the focus explicitly.
+    let elt;
+    if (focusedElement === "preferred") {
+      elt = this._element(gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField"));
+    } else if (focusedElement === "first") {
+      elt = document.querySelector("textbox:not([collapsed=true])");
+    }
+    if (elt) {
+      elt.focus();
+      elt.select();
+    }
   },
 
   /**
    * Finds tags that are in common among this._currentInfo.uris;
    */
   _getCommonTags() {
     if ("_cachedCommonTags" in this._paneInfo)
       return this._paneInfo._cachedCommonTags;
--- a/browser/components/places/tests/browser/head.js
+++ b/browser/components/places/tests/browser/head.js
@@ -317,26 +317,35 @@ var withBookmarksDialog = Task.async(fun
   info("withBookmarksDialog: waiting for the dialog");
   let dialogWin = yield dialogPromise;
 
   // Ensure overlay is loaded
   info("waiting for the overlay to be loaded");
   yield waitForCondition(() => dialogWin.gEditItemOverlay.initialized,
                          "EditItemOverlay should be initialized");
 
+  // Check the first textbox is focused.
+  let doc = dialogWin.document;
+  let elt = doc.querySelector("textbox:not([collapsed=true])");
+  if (elt) {
+    info("waiting for focus on the first textfield");
+    yield waitForCondition(() => doc.activeElement == elt.inputField,
+                           "The first non collapsed textbox should have been focused");
+  }
+
   info("withBookmarksDialog: executing the task");
   try {
     yield taskFn(dialogWin);
   } finally {
     if (!closed) {
       if (!autoCancel) {
         ok(false, "The test should have closed the dialog!");
       }
       info("withBookmarksDialog: canceling the dialog");
-      dialogWin.document.documentElement.cancelDialog();
+      doc.documentElement.cancelDialog();
     }
   }
 });
 
 /**
  * Opens the contextual menu on the element pointed by the given selector.
  *
  * @param selector
deleted file mode 100644
--- a/browser/themes/linux/aboutCertError.css
+++ /dev/null
@@ -1,72 +0,0 @@
-/* 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/. */
-
-
-html {
-  background: -moz-Dialog;
-}
-
-body {
-  margin: 0;
-  padding: 0 1em;
-  color: -moz-FieldText;
-  font: message-box;
-}
-
-h1 {
-  margin: 0 0 .6em 0;
-  border-bottom: 1px solid ThreeDLightShadow;
-  font-size: 160%;
-}
-
-h2 {
-  font-size: 130%;
-}
-
-#errorPageContainer {
-  position: relative;
-  min-width: 13em;
-  max-width: 52em;
-  margin: 4em auto;
-  border: 1px solid #FFBD09; /* pale yellow extracted from yellow passport icon */
-  border-radius: 10px;
-  padding: 3em;
-  -moz-padding-start: 30px;
-  background: url("chrome://global/skin/icons/sslWarning.png") left 0 no-repeat -moz-Field;
-  background-origin: content-box;
-}
-
-#errorPageContainer:-moz-dir(rtl) {
-  background-position: right 0;
-}
-
-#errorTitle {
-  -moz-margin-start: 80px;
-}
-
-#errorLongContent {
-  -moz-margin-start: 80px;
-}
-
-.expander > button {
-  -moz-padding-start: 20px;
-  -moz-margin-start: -20px;
-  background: url("chrome://browser/skin/aboutCertError_sectionExpanded.png") left center no-repeat;
-  border: none;
-  font: inherit;
-  color: inherit;
-  cursor: pointer;
-}
-
-.expander > button:-moz-dir(rtl) {
-  background-position: right center;
-}
-
-.expander[collapsed] > button {
-  background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed.png");
-}
-
-.expander[collapsed] > button:-moz-dir(rtl) {
-  background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed-rtl.png");
-}
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -760,17 +760,17 @@ toolbarbutton[constrain-size="true"][cui
 
 #forward-button {
   -moz-box-align: stretch; /* let the button shape grow vertically with the location bar */
   padding: 0;
 }
 
 #forward-button > .toolbarbutton-icon {
   background-clip: padding-box;
-  padding-left: 9px;
+  padding-left: calc(var(--backbutton-urlbar-overlap) + 4px);
   padding-right: 3px;
   border: 1px solid #9a9a9a;
   border-left-style: none;
   border-radius: 0;
 }
 
 @conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
   transition: margin-left @forwardTransitionLength@ ease-out;
@@ -1201,17 +1201,17 @@ richlistitem[type~="action"][actiontype=
 /* Combined go/reload/stop button in location bar */
 
 #urlbar-go-button,
 #urlbar-reload-button,
 #urlbar-stop-button {
   -moz-appearance: none;
   list-style-image: url("chrome://browser/skin/reload-stop-go.png");
   padding: 0 9px;
-  margin-inline-start: 2px;
+  margin-inline-start: 5px;
   border-inline-start: 1px solid var(--urlbar-separator-color);
   border-image: linear-gradient(transparent 15%,
                                 var(--urlbar-separator-color) 15%,
                                 var(--urlbar-separator-color) 85%,
                                 transparent 85%);
   border-image-slice: 1;
 }
 
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -3,17 +3,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 browser.jar:
 % skin browser classic/1.0 %skin/classic/browser/
 % override chrome://global/skin/icons/warning-16.png moz-icon://stock/gtk-dialog-warning?size=menu
 #include ../shared/jar.inc.mn
   skin/classic/browser/sanitizeDialog.css
   skin/classic/browser/aboutSessionRestore-window-icon.png
-  skin/classic/browser/aboutCertError.css
   skin/classic/browser/aboutCertError_sectionCollapsed.png
   skin/classic/browser/aboutCertError_sectionCollapsed-rtl.png
   skin/classic/browser/aboutCertError_sectionExpanded.png
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/aboutSyncTabs.css
 #endif
   skin/classic/browser/actionicon-tab.png
 * skin/classic/browser/browser.css
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1893,18 +1893,17 @@ richlistitem[type~="action"][actiontype=
 /* ----- COMBINED GO/RELOAD/STOP BUTTON IN LOCATION BAR ----- */
 
 #urlbar-go-button,
 #urlbar-reload-button,
 #urlbar-stop-button {
   margin: 0;
   list-style-image: url("chrome://browser/skin/reload-stop-go.png");
   padding: 0 9px;
-  margin-inline-start: 2px;
-  border-inline-end-style: none;
+  margin-inline-start: 5px;
   border-inline-start: 1px solid var(--urlbar-separator-color);
   border-image: linear-gradient(transparent 15%,
                                 var(--urlbar-separator-color) 15%,
                                 var(--urlbar-separator-color) 85%,
                                 transparent 85%);
   border-image-slice: 1;
 }
 
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -2,17 +2,16 @@
 # 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/.
 
 browser.jar:
 % skin browser classic/1.0 %skin/classic/browser/
 #include ../shared/jar.inc.mn
   skin/classic/browser/sanitizeDialog.css
   skin/classic/browser/aboutSessionRestore-window-icon.png
-  skin/classic/browser/aboutCertError.css
   skin/classic/browser/aboutCertError_sectionCollapsed.png
   skin/classic/browser/aboutCertError_sectionCollapsed-rtl.png
   skin/classic/browser/aboutCertError_sectionExpanded.png
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/aboutSyncTabs.css
 #endif
   skin/classic/browser/actionicon-tab.png
   skin/classic/browser/actionicon-tab@2x.png
rename from browser/themes/osx/aboutCertError.css
rename to browser/themes/shared/aboutCertError.css
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -1,38 +1,48 @@
 %if 0
 /* 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/. */
 %endif
 
 /* Hide all conditional elements by default. */
-:-moz-any([when-connection],[when-mixedcontent],[when-ciphers]) {
+:-moz-any([when-connection],[when-mixedcontent],[when-ciphers],[when-loginforms]) {
   display: none;
 }
 
 /* Show the right elements for the right connection states. */
 #identity-popup[connection=not-secure] [when-connection~=not-secure],
 #identity-popup[connection=secure-ev] [when-connection~=secure-ev],
 #identity-popup[connection=secure] [when-connection~=secure],
 #identity-popup[connection=chrome] [when-connection~=chrome],
 #identity-popup[connection=file] [when-connection~=file],
+/* Show insecure login forms messages when needed. */
+#identity-popup[loginforms=insecure] [when-loginforms=insecure],
 /* Show weak cipher messages when needed. */
 #identity-popup[ciphers=weak] [when-ciphers~=weak],
 /* Show mixed content warnings when needed */
 #identity-popup[mixedcontent~=active-loaded] [when-mixedcontent=active-loaded],
 #identity-popup[mixedcontent~=passive-loaded]:not([mixedcontent~=active-loaded]) [when-mixedcontent=passive-loaded],
 #identity-popup[mixedcontent~=active-blocked]:not([mixedcontent~=passive-loaded]) [when-mixedcontent=active-blocked],
 /* Show the right elements when there is mixed passive content loaded and active blocked. */
 #identity-popup[mixedcontent~=active-blocked][mixedcontent~=passive-loaded] [when-mixedcontent~=active-blocked][when-mixedcontent~=passive-loaded],
 /* Show 'disable MCB' button always when there is mixed active content blocked. */
 #identity-popup-securityView-body[mixedcontent~=active-blocked] > button[when-mixedcontent=active-blocked] {
   display: inherit;
 }
 
+/* Hide redundant messages based on insecure login forms presence. */
+#identity-popup[loginforms=secure] [and-when-loginforms=insecure] {
+  display: none;
+}
+#identity-popup[loginforms=insecure] [and-when-loginforms=secure] {
+  display: none;
+}
+
 /* Hide 'not secure' message in subview when weak cipher or mixed content messages are shown. */
 #identity-popup-securityView-body:-moz-any([mixedcontent],[ciphers]) > description[when-connection=not-secure],
 /* Hide 'passive-loaded (only)' message when there is mixed passive content loaded and active blocked. */
 #identity-popup-securityView-body[mixedcontent~=passive-loaded][mixedcontent~=active-blocked] > description[when-mixedcontent=passive-loaded] {
   display: none;
 }
 
 /* Make sure hidden elements don't accidentally become visible from one of the
@@ -214,16 +224,18 @@
 /* Use [isbroken] to make sure we don't show a lock on an http page. See Bug 1192162. */
 #identity-popup[ciphers=weak] #identity-popup-securityView,
 #identity-popup[ciphers=weak] #identity-popup-security-content,
 #identity-popup[mixedcontent~=passive-loaded][isbroken] #identity-popup-securityView,
 #identity-popup[mixedcontent~=passive-loaded][isbroken] #identity-popup-security-content {
   background-image: url(chrome://browser/skin/controlcenter/conn-degraded.svg);
 }
 
+#identity-popup[loginforms=insecure] #identity-popup-securityView,
+#identity-popup[loginforms=insecure] #identity-popup-security-content,
 #identity-popup[mixedcontent~=active-loaded][isbroken] #identity-popup-securityView,
 #identity-popup[mixedcontent~=active-loaded][isbroken] #identity-popup-security-content {
   background-image: url(chrome://browser/skin/controlcenter/mcb-disabled.svg);
 }
 
 #identity-popup-security-descriptions > description {
   margin-top: 6px;
   color: Graytext;
--- a/browser/themes/shared/identity-block/identity-block.inc.css
+++ b/browser/themes/shared/identity-block/identity-block.inc.css
@@ -118,16 +118,17 @@
   list-style-image: url(chrome://branding/content/identity-icons-brand.svg);
 }
 
 .verifiedDomain > #identity-icons > #page-proxy-favicon[pageproxystate="valid"],
 .verifiedIdentity > #identity-icons > #page-proxy-favicon[pageproxystate="valid"] {
   list-style-image: url(chrome://browser/skin/identity-secure.svg);
 }
 
+.insecureLoginForms > #identity-icons > #page-proxy-favicon[pageproxystate="valid"],
 .mixedActiveContent > #identity-icons > #page-proxy-favicon[pageproxystate="valid"] {
   list-style-image: url(chrome://browser/skin/identity-mixed-active-loaded.svg);
 }
 
 .weakCipher > #identity-icons > #page-proxy-favicon[pageproxystate="valid"],
 .mixedDisplayContent > #identity-icons > #page-proxy-favicon[pageproxystate="valid"],
 .mixedDisplayContentLoadedActiveBlocked > #identity-icons > #page-proxy-favicon[pageproxystate="valid"] {
   list-style-image: url(chrome://browser/skin/identity-mixed-passive-loaded.svg);
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -2,16 +2,17 @@
 # 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/.
 
 # This is not a complete / proper jar manifest. It is included by the
 # actual theme-specific manifests, so that shared resources need only
 # be specified once. As a result, the source file paths are relative
 # to the location of the actual manifest.
 
+  skin/classic/browser/aboutCertError.css                      (../shared/aboutCertError.css)
   skin/classic/browser/aboutNetError.css                       (../shared/aboutNetError.css)
   skin/classic/browser/aboutNetError_info.svg                  (../shared/aboutNetError_info.svg)
   skin/classic/browser/aboutNetError_alert.svg                 (../shared/aboutNetError_alert.svg)
 * skin/classic/browser/aboutProviderDirectory.css              (../shared/aboutProviderDirectory.css)
 * skin/classic/browser/aboutSessionRestore.css                 (../shared/aboutSessionRestore.css)
   skin/classic/browser/aboutSocialError.css                    (../shared/aboutSocialError.css)
   skin/classic/browser/aboutTabCrashed.css                     (../shared/aboutTabCrashed.css)
   skin/classic/browser/aboutWelcomeBack.css                    (../shared/aboutWelcomeBack.css)
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -94,17 +94,17 @@
   padding-left: 3px;
   border-width: 0 8px 0 0;
   border-style: solid;
   border-image: url("chrome://browser/skin/urlbar-arrow.png") 0 8 0 0 fill;
   -moz-margin-end: -8px;
 }
 
 @conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box {
-  padding-left: 7px;
+  padding-left: calc(var(--backbutton-urlbar-overlap) + 3px);
 }
 
 /* This changes the direction of the main notification box on the url bar. */
 #notification-popup-box:-moz-locale-dir(rtl),
 /* This adds a second flip for the notification anchors, as they don't switch direction
    for RTL mode. */
 .notification-anchor-icon:-moz-locale-dir(rtl) {
   transform: scaleX(-1);
deleted file mode 100644
--- a/browser/themes/windows/aboutCertError.css
+++ /dev/null
@@ -1,72 +0,0 @@
-/* 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/. */
-
-
-html {
-  background: -moz-Dialog;
-}
-
-body {
-  margin: 0;
-  padding: 0 1em;
-  color: -moz-FieldText;
-  font: message-box;
-}
-
-h1 {
-  margin: 0 0 .6em 0;
-  border-bottom: 1px solid ThreeDLightShadow;
-  font-size: 160%;
-}
-
-h2 {
-  font-size: 130%;
-}
-
-#errorPageContainer {
-  position: relative;
-  min-width: 13em;
-  max-width: 52em;
-  margin: 4em auto;
-  border: 1px solid #FFBD09; /* pale yellow extracted from yellow passport icon */
-  border-radius: 10px;
-  padding: 3em;
-  -moz-padding-start: 30px;
-  background: url("chrome://global/skin/icons/sslWarning.png") left 0 no-repeat -moz-Field;
-  background-origin: content-box;
-}
-
-#errorPageContainer:-moz-dir(rtl) {
-  background-position: right 0;
-}
-
-#errorTitle {
-  -moz-margin-start: 80px;
-}
-
-#errorLongContent {
-  -moz-margin-start: 80px;
-}
-
-.expander > button {
-  -moz-padding-start: 20px;
-  -moz-margin-start: -20px;
-  background: url("chrome://browser/skin/aboutCertError_sectionExpanded.png") left center no-repeat;
-  border: none;
-  font: inherit;
-  color: inherit;
-  cursor: pointer;
-}
-
-.expander > button:-moz-dir(rtl) {
-  background-position: right center;
-}
-
-.expander[collapsed] > button {
-  background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed.png");
-}
-
-.expander[collapsed] > button:-moz-dir(rtl) {
-  background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed-rtl.png");
-}
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -7,35 +7,33 @@
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace html url("http://www.w3.org/1999/xhtml");
 @namespace svg url("http://www.w3.org/2000/svg");
 
 %include ../shared/browser.inc
 %include windowsShared.inc
 %filter substitution
 %define toolbarShadowColor hsla(209,67%,12%,0.35)
-%define navbarTextboxCustomBorder border-color: rgba(0,0,0,.32);
 %define forwardTransitionLength 150ms
 %define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-wrapper
-%define conditionalForwardWithUrlbarWidth 30
 
 :root {
   --space-above-tabbar: 15px;
 
-  --backbutton-urlbar-overlap: 5px;
+  --backbutton-urlbar-overlap: 6px;
 
   --toolbarbutton-vertical-inner-padding: 2px;
   --toolbarbutton-vertical-outer-padding: 8px;
 
   --toolbarbutton-hover-background: rgba(0,0,0,.1);
-  --toolbarbutton-hover-bordercolor: rgba(0,0,0,.1);
+  --toolbarbutton-hover-bordercolor: rgba(0,0,0,.2);
   --toolbarbutton-hover-boxshadow: none;
 
   --toolbarbutton-active-background: rgba(0,0,0,.15);
-  --toolbarbutton-active-bordercolor: rgba(0,0,0,.15);
+  --toolbarbutton-active-bordercolor: rgba(0,0,0,.3);
   --toolbarbutton-active-boxshadow: 0 0 0 1px rgba(0,0,0,.15) inset;
 
   --toolbarbutton-checkedhover-backgroundcolor: rgba(0,0,0,.1);
 
   --identity-box-verified-background-color: #fff;
 
   --urlbar-dropmarker-url: url("chrome://browser/skin/urlbar-history-dropmarker.png");
   --urlbar-dropmarker-region: rect(0px, 11px, 14px, 0px);
@@ -46,28 +44,16 @@
   --urlbar-dropmarker-hover-2x-region: rect(0, 44px, 28px, 22px);
   --urlbar-dropmarker-active-2x-region: rect(0, 66px, 28px, 44px);
 
   --panel-separator-color: ThreeDLightShadow;
 
   --urlbar-separator-color: hsla(0,0%,16%,.2);
 }
 
-#nav-bar[brighttext] {
-  --toolbarbutton-hover-background: rgba(255,255,255,.15);
-  --toolbarbutton-hover-bordercolor: rgba(255,255,255,.15);
-  --toolbarbutton-hover-boxshadow: none;
-
-  --toolbarbutton-active-background: rgba(255,255,255,.3);
-  --toolbarbutton-active-bordercolor: rgba(255,255,255,.3);
-  --toolbarbutton-active-boxshadow: 0 0 0 1px rgba(255,255,255,.3) inset;
-
-  --toolbarbutton-checkedhover-backgroundcolor: rgba(255,255,255,.2);
-}
-
 #menubar-items {
   -moz-box-orient: vertical; /* for flex hack */
 }
 
 #main-menubar {
   -moz-box-flex: 1; /* make menu items expand to fill toolbar height */
 }
 
@@ -722,16 +708,18 @@ toolbar[brighttext] .toolbarbutton-1 > .
 .findbar-button > .toolbarbutton-text,
 #nav-bar .toolbarbutton-1 > .toolbarbutton-icon,
 #nav-bar .toolbarbutton-1 > .toolbarbutton-text,
 #nav-bar .toolbarbutton-1 > .toolbarbutton-badge-stack,
 #nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
 #nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
 @conditionalForwardWithUrlbar@ > .toolbarbutton-1:-moz-any([disabled],:not([open]):not([disabled]):not(:active)) > .toolbarbutton-icon {
   padding: var(--toolbarbutton-vertical-inner-padding) 6px;
+  background-origin: padding-box !important;
+  background-clip: padding-box !important;
   border: 1px solid transparent;
   border-radius: 1px;
   transition-property: background-color, border-color, box-shadow;
   transition-duration: 150ms;
 }
 
 toolbaritem[cui-areatype="toolbar"] > :-moz-any(@nestedButtons@) > .toolbarbutton-icon,
 :-moz-any(@primaryToolbarButtons@):-moz-any([cui-areatype="toolbar"],:not([cui-areatype])):not(:-moz-any(@nestedButtons@)) > .toolbarbutton-icon,
@@ -851,20 +839,16 @@ toolbarbutton[constrain-size="true"][cui
   opacity: .2;
 }
 
 #nav-bar[brighttext] .toolbaritem-combined-buttons > separator,
 #nav-bar[brighttext] .toolbarbutton-1:not(:hover):not(:active):not([open]) > .toolbarbutton-menubutton-dropmarker::before {
   opacity: .3;
 }
 
-@conditionalForwardWithUrlbar@ > .toolbarbutton-1:-moz-any([disabled],:not([open]):not([disabled]):not(:active)) > .toolbarbutton-icon {
-  border-color: hsla(210,4%,10%,.1);
-}
-
 .findbar-button:not(:-moz-any([checked="true"],[disabled="true"])):hover > .toolbarbutton-text,
 #nav-bar .toolbarbutton-1:not([disabled=true]) > .toolbarbutton-menubutton-button[open] + .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
 #nav-bar .toolbarbutton-1:not([disabled=true]):-moz-any(:hover,[open]) > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
 #nav-bar .toolbarbutton-1:not([disabled=true]):-moz-any(:hover,[open]) > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
 #nav-bar .toolbarbutton-1:not([disabled=true]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-icon,
 #nav-bar .toolbarbutton-1:not([disabled=true]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-text,
 #nav-bar .toolbarbutton-1:not([disabled=true]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-badge-stack,
 @conditionalForwardWithUrlbar@ > #forward-button:not([open]):not(:active):not([disabled]):hover > .toolbarbutton-icon,
@@ -914,132 +898,84 @@ toolbarbutton[constrain-size="true"][cui
                     linear-gradient(transparent, rgba(0,0,0,.25) 30%);
   background-position: 1px -1px, 0 -1px, 100% -1px;
   background-size: calc(100% - 2px) 100%, 1px 100%, 1px 100%;
   background-repeat: no-repeat;
 }
 
 /* unified back/forward button */
 
+:-moz-any(#back-button, #forward-button) > .toolbarbutton-icon {
+  border-color: var(--urlbar-border-color-hover) !important;
+}
+
+:-moz-any(#back-button, #forward-button):not(:hover):not(:active):not([open=true]) > .toolbarbutton-icon,
+:-moz-any(#back-button, #forward-button)[disabled=true] > .toolbarbutton-icon {
+  background-color: rgba(255,255,255,.15) !important;
+}
+
 #forward-button {
   -moz-box-align: stretch; /* let the button shape grow vertically with the location bar */
   padding: 0 !important;
 }
 
 #forward-button > menupopup {
   margin-top: 1px !important;
 }
 
 #forward-button > .toolbarbutton-icon {
-  background-clip: padding-box !important;
   border-left-style: none !important;
   border-radius: 0 !important;
-  padding-left: 9px !important;
+  padding-left: calc(var(--backbutton-urlbar-overlap) + 3px) !important;
   padding-right: 3px !important;
-  width: 31px !important; /* horizontal padding + border + actual icon width */
+% icon width + border + horizontal padding without --backbutton-urlbar-overlap
+%define forwardButtonWidth 25
+  width: calc(@forwardButtonWidth@px + var(--backbutton-urlbar-overlap)) !important;
 }
 
 @conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
   transition: margin-left @forwardTransitionLength@ ease-out;
 }
 
 @conditionalForwardWithUrlbar@ > #forward-button[disabled] {
-  margin-left: -@conditionalForwardWithUrlbarWidth@px;
+  margin-left: calc(-@forwardButtonWidth@px - var(--backbutton-urlbar-overlap));
 }
 
 @conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] {
   /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
   transition-delay: 100s;
 }
 
 @conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
   /* when not hovered anymore, trigger a new transition to hide the forward button immediately */
-  margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
+  margin-left: calc(-@forwardButtonWidth@.01px - var(--backbutton-urlbar-overlap));
 }
 
 #back-button {
   padding-top: 3px !important;
   padding-bottom: 3px !important;
   -moz-padding-start: 5px !important;
   -moz-padding-end: 0 !important;
   position: relative !important;
   z-index: 1 !important;
   border-radius: 0 10000px 10000px 0 !important;
-  --toolbarbutton-hover-boxshadow: 0 1px 0 0 hsla(210,4%,10%,.25),
-                                   0 0 0 1px hsla(210,4%,10%,.25);
 }
 
 #back-button:-moz-locale-dir(rtl) {
   border-radius: 10000px 0 0 10000px !important;
 }
 
 #back-button > menupopup {
   margin-top: -1px !important;
 }
 
 #back-button > .toolbarbutton-icon {
   border-radius: 10000px !important;
-  background-clip: padding-box !important;
-  background-color: hsla(210,25%,98%,.08) !important;
   padding: 6px !important;
-  border-style: none !important;
-  width: 30px !important; /* horizontal padding + border + actual icon width */
-  box-shadow: 0 1px 0 0 hsla(210,4%,10%,.25),
-              0 0 0 1px hsla(210,4%,10%,.25);
-  transition-property: background-color, box-shadow !important;
-  transition-duration: 250ms !important;
-}
-
-#back-button:not([disabled="true"]):not([open="true"]):not(:active):hover > .toolbarbutton-icon {
-  background-color: hsla(210,4%,10%,.08) !important;
-}
-
-#back-button:not([disabled="true"]):hover:active > .toolbarbutton-icon,
-#back-button[open="true"] > .toolbarbutton-icon {
-  background-color: hsla(210,4%,10%,.12) !important;
-  box-shadow: 0 1px 0 0 hsla(210,4%,10%,.25),
-              0 0 0 1px hsla(210,4%,10%,.25),
-              0 1px 0 0 hsla(210,80%,20%,.1) inset !important;
-}
-
-@media (-moz-os-version: windows-xp),
-       (-moz-os-version: windows-vista),
-       (-moz-os-version: windows-win7) {
-  #back-button > .toolbarbutton-icon {
-    background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1)) !important;
-    box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset,
-                0 0 0 1px hsla(0,0%,100%,.3) inset,
-                0 0 0 1px hsla(210,54%,20%,.25),
-                0 1px 0 hsla(210,54%,20%,.35) !important;
-  }
-
-  #back-button:not([disabled="true"]):not([open="true"]):not(:active):hover > .toolbarbutton-icon {
-    background-color: hsla(210,48%,96%,.75) !important;
-    box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset,
-                0 0 0 1px hsla(0,0%,100%,.3) inset,
-                0 0 0 1px hsla(210,54%,20%,.3),
-                0 1px 0 hsla(210,54%,20%,.4),
-                0 0 4px hsla(210,54%,20%,.2) !important;
-  }
-
-  #back-button:not([disabled="true"]):hover:active > .toolbarbutton-icon,
-  #back-button[open="true"] > .toolbarbutton-icon {
-    background-color: hsla(210,54%,20%,.15) !important;
-    box-shadow: 0 1px 1px hsla(210,54%,20%,.1) inset,
-                0 0 1px hsla(210,54%,20%,.2) inset,
-                0 0 0 1px hsla(210,54%,20%,.4),
-                0 1px 0 hsla(210,54%,20%,.2) !important;
-    transition: none;
-  }
-
-  #main-window:not([customizing]) #back-button[disabled] > .toolbarbutton-icon {
-    box-shadow: 0 0 0 1px hsla(210,54%,20%,.55),
-                0 1px 0 hsla(210,54%,20%,.65) !important;
-    transition: none;
-  }
+  width: 32px !important; /* icon width + horizontal padding + border */
 }
 
 #back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
   transform: scaleX(-1);
 }
 
 .unified-nav-back[_moz-menuactive]:-moz-locale-dir(ltr),
 .unified-nav-forward[_moz-menuactive]:-moz-locale-dir(rtl) {
@@ -1168,67 +1104,87 @@ toolbarbutton[constrain-size="true"][cui
   #restore-button:-moz-locale-dir(rtl),
   #close-button:-moz-locale-dir(rtl) {
     transform: scaleX(-1);
   }
 }
 
 /* ::::: Location Bar ::::: */
 
+#nav-bar {
+  --urlbar-border-color: ThreeDShadow;
+  --urlbar-border-color-hover: var(--urlbar-border-color);
+}
+
+#nav-bar:-moz-lwtheme {
+  --urlbar-border-color: rgba(0,0,0,.32);
+}
+
+@media (-moz-windows-default-theme) {
+  @media (-moz-os-version: windows-vista),
+         (-moz-os-version: windows-win7),
+         (-moz-os-version: windows-win8) {
+    #nav-bar:not(:-moz-lwtheme) {
+      --urlbar-border-color: hsla(210,54%,20%,.25) hsla(210,54%,20%,.27) hsla(210,54%,20%,.3);
+      --urlbar-border-color-hover: hsla(210,54%,20%,.35) hsla(210,54%,20%,.37) hsla(210,54%,20%,.4);
+    }
+  }
+
+  @media (-moz-os-version: windows-win10) {
+    #nav-bar:not(:-moz-lwtheme) {
+      --urlbar-border-color: hsl(0,0%,90%);
+      --urlbar-border-color-hover: hsl(0,0%,80%);
+    }
+  }
+}
+
 #urlbar,
 .searchbar-textbox {
   -moz-appearance: none;
   margin: 0 3px;
   padding: 0;
   background-clip: padding-box;
-  border: 1px solid ThreeDShadow;
+  border: 1px solid;
+  border-color: var(--urlbar-border-color);
+}
+
+#urlbar:hover,
+.searchbar-textbox:hover {
+  border-color: var(--urlbar-border-color-hover);
 }
 
 /* overlap the urlbar's border */
 #PopupAutoCompleteRichResult {
   margin-top: -1px;
 }
 
 @media (-moz-windows-default-theme) {
   #urlbar,
   .searchbar-textbox {
-    @navbarTextboxCustomBorder@
     border-radius: 1px;
   }
 
   @media (-moz-os-version: windows-vista),
          (-moz-os-version: windows-win7),
          (-moz-os-version: windows-win8) {
     #urlbar:not(:-moz-lwtheme),
     .searchbar-textbox:not(:-moz-lwtheme) {
-      border-color: hsla(210,54%,20%,.25) hsla(210,54%,20%,.27) hsla(210,54%,20%,.3);
       box-shadow: 0 1px 0 hsla(0,0%,0%,.01) inset,
                   0 1px 0 hsla(0,0%,100%,.1);
     }
-
-    #urlbar:not(:-moz-lwtheme):hover,
-    .searchbar-textbox:not(:-moz-lwtheme):hover {
-      border-color: hsla(210,54%,20%,.35) hsla(210,54%,20%,.37) hsla(210,54%,20%,.4);
-    }
   }
 
   @media (-moz-os-version: windows-win10) {
     #urlbar:not(:-moz-lwtheme),
     .searchbar-textbox:not(:-moz-lwtheme) {
-      border-color: hsl(0,0%,90%);
       padding: 1px;
       transition-property: border-color, box-shadow;
       transition-duration: .1s;
     }
 
-    #urlbar:not(:-moz-lwtheme):hover,
-    .searchbar-textbox:not(:-moz-lwtheme):hover {
-      border-color: hsl(0,0%,80%);
-    }
-
     #urlbar:not(:-moz-lwtheme)[focused],
     .searchbar-textbox:not(:-moz-lwtheme)[focused] {
       box-shadow: 0 0 0 1px Highlight inset;
     }
 
     /* overlap the urlbar's border and inset box-shadow */
     #PopupAutoCompleteRichResult:not(:-moz-lwtheme) {
       margin-top: -2px;
@@ -1255,17 +1211,16 @@ toolbarbutton[constrain-size="true"][cui
     --toolbarbutton-vertical-inner-padding: 4px;
     --toolbarbutton-vertical-outer-padding: 5px;
   }
 }
 
 #urlbar:-moz-lwtheme,
 .searchbar-textbox:-moz-lwtheme {
   background-color: rgba(255,255,255,.8);
-  @navbarTextboxCustomBorder@
   color: black;
 }
 
 #urlbar:-moz-lwtheme:hover:not([readonly]),
 .searchbar-textbox:-moz-lwtheme:hover {
   background-color: rgba(255,255,255,.9);
 }
 
@@ -1296,17 +1251,17 @@ toolbarbutton[constrain-size="true"][cui
 }
 
 @media (-moz-os-version: windows-win10) {
   @conditionalForwardWithUrlbar@ {
     clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path-win10");
   }
 
   :root {
-    --backbutton-urlbar-overlap: 8px;
+    --backbutton-urlbar-overlap: 9px;
   }
 }
 
 @conditionalForwardWithUrlbar@:-moz-locale-dir(rtl),
 @conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
   /* let urlbar-back-button-clip-path clip the urlbar's right side for RTL */
   transform: scaleX(-1);
 }
@@ -1604,17 +1559,17 @@ richlistitem[type~="action"][actiontype=
 
 #urlbar-go-button,
 #urlbar-reload-button,
 #urlbar-stop-button {
   -moz-appearance: none;
   border-style: none;
   list-style-image: url("chrome://browser/skin/reload-stop-go.png");
   padding: 0 9px;
-  margin-inline-start: 2px;
+  margin-inline-start: 5px;
   border-inline-start: 1px solid var(--urlbar-separator-color);
   border-image: linear-gradient(transparent 15%,
                                 var(--urlbar-separator-color) 15%,
                                 var(--urlbar-separator-color) 85%,
                                 transparent 85%);
   border-image-slice: 1;
 }
 
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -2,17 +2,16 @@
 # 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/.
 
 browser.jar:
 % skin browser classic/1.0 %skin/classic/browser/
 #include ../shared/jar.inc.mn
   skin/classic/browser/sanitizeDialog.css
   skin/classic/browser/aboutSessionRestore-window-icon.png
-  skin/classic/browser/aboutCertError.css
   skin/classic/browser/aboutCertError_sectionCollapsed.png
   skin/classic/browser/aboutCertError_sectionCollapsed-rtl.png
   skin/classic/browser/aboutCertError_sectionExpanded.png
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/aboutSyncTabs.css
 #endif
   skin/classic/browser/actionicon-tab.png
   skin/classic/browser/actionicon-tab@2x.png
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -159,16 +159,17 @@ devtools.jar:
     content/eyedropper/nocursor.css (eyedropper/nocursor.css)
     content/aboutdebugging/aboutdebugging.xhtml (aboutdebugging/aboutdebugging.xhtml)
     content/aboutdebugging/aboutdebugging.css (aboutdebugging/aboutdebugging.css)
     content/aboutdebugging/aboutdebugging.js (aboutdebugging/aboutdebugging.js)
 %   skin devtools classic/1.0 %skin/
 *   skin/themes/common.css (themes/common.css)
 *   skin/themes/dark-theme.css (themes/dark-theme.css)
 *   skin/themes/light-theme.css (themes/light-theme.css)
+    skin/themes/variables.css (themes/variables.css)
     skin/themes/images/add.svg (themes/images/add.svg)
     skin/themes/images/filters.svg (themes/images/filters.svg)
     skin/themes/images/filter-swatch.svg (themes/images/filter-swatch.svg)
     skin/themes/images/pseudo-class.svg (themes/images/pseudo-class.svg)
     skin/themes/images/controls.png (themes/images/controls.png)
     skin/themes/images/controls@2x.png (themes/images/controls@2x.png)
     skin/themes/images/animation-fast-track.svg (themes/images/animation-fast-track.svg)
     skin/themes/images/performance-icons.svg (themes/images/performance-icons.svg)
--- a/devtools/client/markupview/test/browser_markupview_dragdrop_reorder.js
+++ b/devtools/client/markupview/test/browser_markupview_dragdrop_reorder.js
@@ -18,16 +18,21 @@ add_task(function*() {
   let parent = yield getNode("#test");
   let parentContainer = yield getContainerForNodeFront(parentFront, inspector);
 
   yield inspector.markup.expandNode(parentFront);
   yield waitForMultipleChildrenUpdates(inspector);
 
   parentContainer.elt.scrollIntoView(true);
 
+  info("Testing putting an element back in it's original place");
+  yield dragElementToOriginalLocation("#firstChild", inspector);
+  is(parent.children[0].id, "firstChild", "#firstChild is still the first child of #test");
+  is(parent.children[1].id, "middleChild", "#middleChild is still the second child of #test");
+
   info("Testing switching elements inside their parent");
   yield moveElementDown("#firstChild", "#middleChild", inspector);
 
   is(parent.children[0].id, "middleChild", "#firstChild is now the second child of #test");
   is(parent.children[1].id, "firstChild", "#middleChild is now the first child of #test");
 
   info("Testing switching elements with a last child");
   yield moveElementDown("#firstChild", "#lastChild", inspector);
@@ -90,22 +95,44 @@ function* dragContainer(selector, target
     });
 
     container.markup._onMouseUp();
   }, GRAB_DELAY+1);
 
   return updated;
 };
 
+function* dragElementToOriginalLocation(selector, inspector) {
+  let el = yield getContainerForSelector(selector, inspector);
+  let height = el.tagLine.getBoundingClientRect().height;
+
+  info("Picking up and putting back down " + selector);
+
+  function onMutation() {
+    ok(false, "Mutation received from dragging a node back to its location");
+  }
+  inspector.on("markupmutation", onMutation);
+  yield dragContainer(selector, {x: 0, y: 0}, inspector);
+
+  // Wait a bit to make sure the event never fires.
+  // This doesn't need to catch *all* cases, since the mutation
+  // will cause failure later in the test when it checks element ordering.
+  yield new Promise(resolve => {
+    setTimeout(resolve, 500);
+  });
+  inspector.off("markupmutation", onMutation);
+}
+
 function* moveElementDown(selector, next, inspector) {
   let onMutated = inspector.once("markupmutation");
   let uiUpdate = inspector.once("inspector-updated");
 
   let el = yield getContainerForSelector(next, inspector);
   let height = el.tagLine.getBoundingClientRect().height;
 
   info("Switching " + selector + ' with ' + next);
 
   yield dragContainer(selector, {x: 0, y: Math.round(height) + 2}, inspector);
 
-  yield onMutated;
+  let mutations = yield onMutated;
+  is(mutations.length, 2, "2 mutations");
   yield uiUpdate;
 };
\ No newline at end of file
rename from devtools/client/styleinspector/css-parsing-utils.js
rename to devtools/client/shared/css-parsing-utils.js
--- a/devtools/client/styleinspector/css-parsing-utils.js
+++ b/devtools/client/shared/css-parsing-utils.js
@@ -9,17 +9,16 @@
 // parseDeclarations - parse a CSS rule into declarations
 // RuleRewriter - rewrite CSS rule text
 // parsePseudoClassesAndAttributes - parse selector and extract
 //     pseudo-classes
 // parseSingleValue - parse a single CSS property value
 
 "use strict";
 
-const {cssTokenizer} = require("devtools/client/sourceeditor/css-tokenizer");
 const {Cc, Ci, Cu} = require("chrome");
 Cu.importGlobalProperties(["CSS"]);
 const promise = require("promise");
 Cu.import("resource://gre/modules/Task.jsm", this);
 loader.lazyGetter(this, "DOMUtils", () => {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
 
@@ -39,16 +38,95 @@ const EMPTY_COMMENT_END_RX = /^[ \r\n\t\
 const BLANK_LINE_RX = /^[ \t]*(?:\r\n|\n|\r|\f|$)/;
 
 // When commenting out a declaration, we put this character into the
 // comment opener so that future parses of the commented text know to
 // bypass the property name validity heuristic.
 const COMMENT_PARSING_HEURISTIC_BYPASS_CHAR = "!";
 
 /**
+ * A generator function that lexes a CSS source string, yielding the
+ * CSS tokens.  Comment tokens are dropped.
+ *
+ * @param {String} CSS source string
+ * @yield {CSSToken} The next CSSToken that is lexed
+ * @see CSSToken for details about the returned tokens
+ */
+function* cssTokenizer(string) {
+  let lexer = DOMUtils.getCSSLexer(string);
+  while (true) {
+    let token = lexer.nextToken();
+    if (!token) {
+      break;
+    }
+    // None of the existing consumers want comments.
+    if (token.tokenType !== "comment") {
+      yield token;
+    }
+  }
+}
+
+/**
+ * Pass |string| to the CSS lexer and return an array of all the
+ * returned tokens.  Comment tokens are not included.  In addition to
+ * the usual information, each token will have starting and ending
+ * line and column information attached.  Specifically, each token
+ * has an additional "loc" attribute.  This attribute is an object
+ * of the form {line: L, column: C}.  Lines and columns are both zero
+ * based.
+ *
+ * It's best not to add new uses of this function.  In general it is
+ * simpler and better to use the CSSToken offsets, rather than line
+ * and column.  Also, this function lexes the entire input string at
+ * once, rather than lazily yielding a token stream.  Use
+ * |cssTokenizer| or |DOMUtils.getCSSLexer| instead.
+ *
+ * @param{String} string The input string.
+ * @return {Array} An array of tokens (@see CSSToken) that have
+ *        line and column information.
+ */
+function cssTokenizerWithLineColumn(string) {
+  let lexer = DOMUtils.getCSSLexer(string);
+  let result = [];
+  let prevToken = undefined;
+  while (true) {
+    let token = lexer.nextToken();
+    let lineNumber = lexer.lineNumber;
+    let columnNumber = lexer.columnNumber;
+
+    if (prevToken) {
+      prevToken.loc.end = {
+        line: lineNumber,
+        column: columnNumber
+      };
+    }
+
+    if (!token) {
+      break;
+    }
+
+    if (token.tokenType === "comment") {
+      // We've already dealt with the previous token's location.
+      prevToken = undefined;
+    } else {
+      let startLoc = {
+        line: lineNumber,
+        column: columnNumber
+      };
+      token.loc = {start: startLoc};
+
+      result.push(token);
+      prevToken = token;
+    }
+  }
+
+  return result;
+}
+
+/**
  * Escape a comment body.  Find the comment start and end strings in a
  * string and inserts backslashes so that the resulting text can
  * itself be put inside a comment.
  *
  * @param {String} inputString
  *                 input string
  * @return {String} the escaped result
  */
@@ -987,16 +1065,18 @@ function parsePseudoClassesAndAttributes
 function parseSingleValue(value) {
   let declaration = parseDeclarations("a: " + value + ";")[0];
   return {
     value: declaration ? declaration.value : "",
     priority: declaration ? declaration.priority : ""
   };
 }
 
+exports.cssTokenizer = cssTokenizer;
+exports.cssTokenizerWithLineColumn = cssTokenizerWithLineColumn;
 exports.escapeCSSComment = escapeCSSComment;
 // unescapeCSSComment is exported for testing.
 exports._unescapeCSSComment = unescapeCSSComment;
 exports.parseDeclarations = parseDeclarations;
 // parseCommentDeclarations is exported for testing.
 exports._parseCommentDeclarations = parseCommentDeclarations;
 exports.RuleRewriter = RuleRewriter;
 exports.parsePseudoClassesAndAttributes = parsePseudoClassesAndAttributes;
--- a/devtools/client/shared/moz.build
+++ b/devtools/client/shared/moz.build
@@ -12,16 +12,17 @@ DIRS += [
     'vendor',
     'widgets',
 ]
 
 DevToolsModules(
     'AppCacheUtils.jsm',
     'autocomplete-popup.js',
     'browser-loader.js',
+    'css-parsing-utils.js',
     'Curl.jsm',
     'DeveloperToolbar.jsm',
     'devices.js',
     'DOMHelpers.jsm',
     'doorhanger.js',
     'frame-script-utils.js',
     'getjson.js',
     'inplace-editor.js',
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/unit/test_escapeCSSComment.js
@@ -0,0 +1,43 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var Cu = Components.utils;
+Cu.import("resource://gre/modules/devtools/Loader.jsm");
+const {escapeCSSComment, _unescapeCSSComment} =
+      devtools.require("devtools/client/shared/css-parsing-utils");
+
+const TEST_DATA = [
+  {
+    input: "simple",
+    expected: "simple"
+  },
+  {
+    input: "/* comment */",
+    expected: "/\\* comment *\\/"
+  },
+  {
+    input: "/* two *//* comments */",
+    expected: "/\\* two *\\//\\* comments *\\/"
+  },
+  {
+    input: "/* nested /\\* comment *\\/ */",
+    expected: "/\\* nested /\\\\* comment *\\\\/ *\\/",
+  }
+];
+
+function run_test() {
+  let i = 0;
+  for (let test of TEST_DATA) {
+    ++i;
+    do_print("Test #" + i);
+
+    let escaped = escapeCSSComment(test.input);
+    equal(escaped, test.expected);
+    let unescaped = _unescapeCSSComment(escaped);
+    equal(unescaped, test.input);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/unit/test_parseDeclarations.js
@@ -0,0 +1,425 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var Cu = Components.utils;
+const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+const {parseDeclarations, _parseCommentDeclarations} =
+  require("devtools/client/shared/css-parsing-utils");
+
+const TEST_DATA = [
+  // Simple test
+  {
+    input: "p:v;",
+    expected: [{name: "p", value: "v", priority: "", offsets: [0, 4]}]
+  },
+  // Simple test
+  {
+    input: "this:is;a:test;",
+    expected: [
+      {name: "this", value: "is", priority: "", offsets: [0, 8]},
+      {name: "a", value: "test", priority: "", offsets: [8, 15]}
+    ]
+  },
+  // Test a single declaration with semi-colon
+  {
+    input: "name:value;",
+    expected: [{name: "name", value: "value", priority: "", offsets: [0, 11]}]
+  },
+  // Test a single declaration without semi-colon
+  {
+    input: "name:value",
+    expected: [{name: "name", value: "value", priority: "", offsets: [0, 10]}]
+  },
+  // Test multiple declarations separated by whitespaces and carriage
+  // returns and tabs
+  {
+    input: "p1 : v1 ; \t\t  \n p2:v2;   \n\n\n\n\t  p3    :   v3;",
+    expected: [
+      {name: "p1", value: "v1", priority: "", offsets: [0, 9]},
+      {name: "p2", value: "v2", priority: "", offsets: [16, 22]},
+      {name: "p3", value: "v3", priority: "", offsets: [32, 45]},
+    ]
+  },
+  // Test simple priority
+  {
+    input: "p1: v1; p2: v2 !important;",
+    expected: [
+      {name: "p1", value: "v1", priority: "", offsets: [0, 7]},
+      {name: "p2", value: "v2", priority: "important", offsets: [8, 26]}
+    ]
+  },
+  // Test simple priority
+  {
+    input: "p1: v1 !important; p2: v2",
+    expected: [
+      {name: "p1", value: "v1", priority: "important", offsets: [0, 18]},
+      {name: "p2", value: "v2", priority: "", offsets: [19, 25]}
+    ]
+  },
+  // Test simple priority
+  {
+    input: "p1: v1 !  important; p2: v2 ! important;",
+    expected: [
+      {name: "p1", value: "v1", priority: "important", offsets: [0, 20]},
+      {name: "p2", value: "v2", priority: "important", offsets: [21, 40]}
+    ]
+  },
+  // Test invalid priority
+  {
+    input: "p1: v1 important;",
+    expected: [
+      {name: "p1", value: "v1 important", priority: "", offsets: [0, 17]}
+    ]
+  },
+  // Test various types of background-image urls
+  {
+    input: "background-image: url(../../relative/image.png)",
+    expected: [{
+      name: "background-image",
+      value: "url(../../relative/image.png)",
+      priority: "",
+      offsets: [0, 47]
+    }]
+  },
+  {
+    input: "background-image: url(http://site.com/test.png)",
+    expected: [{
+      name: "background-image",
+      value: "url(http://site.com/test.png)",
+      priority: "",
+      offsets: [0, 47]
+    }]
+  },
+  {
+    input: "background-image: url(wow.gif)",
+    expected: [{
+      name: "background-image",
+      value: "url(wow.gif)",
+      priority: "",
+      offsets: [0, 30]
+    }]
+  },
+  // Test that urls with :;{} characters in them are parsed correctly
+  {
+    input: "background: red url(\"http://site.com/image{}:;.png?id=4#wat\") "
+      + "repeat top right",
+    expected: [{
+      name: "background",
+      value: "red url(\"http://site.com/image{}:;.png?id=4#wat\") " +
+        "repeat top right",
+      priority: "",
+      offsets: [0, 78]
+    }]
+  },
+  // Test that an empty string results in an empty array
+  {input: "", expected: []},
+  // Test that a string comprised only of whitespaces results in an empty array
+  {input: "       \n \n   \n   \n \t  \t\t\t  ", expected: []},
+  // Test that a null input throws an exception
+  {input: null, throws: true},
+  // Test that a undefined input throws an exception
+  {input: undefined, throws: true},
+  // Test that :;{} characters in quoted content are not parsed as multiple
+  // declarations
+  {
+    input: "content: \";color:red;}selector{color:yellow;\"",
+    expected: [{
+      name: "content",
+      value: "\";color:red;}selector{color:yellow;\"",
+      priority: "",
+      offsets: [0, 45]
+    }]
+  },
+  // Test that rules aren't parsed, just declarations. So { and } found after a
+  // property name should be part of the property name, same for values.
+  {
+    input: "body {color:red;} p {color: blue;}",
+    expected: [
+      {name: "body {color", value: "red", priority: "", offsets: [0, 16]},
+      {name: "} p {color", value: "blue", priority: "", offsets: [16, 33]},
+      {name: "}", value: "", priority: "", offsets: [33, 34]}
+    ]
+  },
+  // Test unbalanced : and ;
+  {
+    input: "color :red : font : arial;",
+    expected: [
+      {name: "color", value: "red : font : arial", priority: "",
+       offsets: [0, 26]}
+    ]
+  },
+  {
+    input: "background: red;;;;;",
+    expected: [{name: "background", value: "red", priority: "",
+                offsets: [0, 16]}]
+  },
+  {
+    input: "background:;",
+    expected: [{name: "background", value: "", priority: "",
+                offsets: [0, 12]}]
+  },
+  {input: ";;;;;", expected: []},
+  {input: ":;:;", expected: []},
+  // Test name only
+  {input: "color", expected: [
+    {name: "color", value: "", priority: "", offsets: [0, 5]}
+  ]},
+  // Test trailing name without :
+  {input: "color:blue;font", expected: [
+    {name: "color", value: "blue", priority: "", offsets: [0, 11]},
+    {name: "font", value: "", priority: "", offsets: [11, 15]}
+  ]},
+  // Test trailing name with :
+  {input: "color:blue;font:", expected: [
+    {name: "color", value: "blue", priority: "", offsets: [0, 11]},
+    {name: "font", value: "", priority: "", offsets: [11, 16]}
+  ]},
+  // Test leading value
+  {input: "Arial;color:blue;", expected: [
+    {name: "", value: "Arial", priority: "", offsets: [0, 6]},
+    {name: "color", value: "blue", priority: "", offsets: [6, 17]}
+  ]},
+  // Test hex colors
+  {
+    input: "color: #333",
+    expected: [{name: "color", value: "#333", priority: "", offsets: [0, 11]}]
+  },
+  {
+    input: "color: #456789",
+    expected: [{name: "color", value: "#456789", priority: "",
+                offsets: [0, 14]}]
+  },
+  {
+    input: "wat: #XYZ",
+    expected: [{name: "wat", value: "#XYZ", priority: "", offsets: [0, 9]}]
+  },
+  // Test string/url quotes escaping
+  {
+    input: "content: \"this is a 'string'\"",
+    expected: [{name: "content", value: "\"this is a 'string'\"", priority: "",
+                offsets: [0, 29]}]
+  },
+  {
+    input: 'content: "this is a \\"string\\""',
+    expected: [{
+      name: "content",
+      value: '"this is a \\"string\\""',
+      priority: "",
+      offsets: [0, 31]}]
+  },
+  {
+    input: "content: 'this is a \"string\"'",
+    expected: [{
+      name: "content",
+      value: '\'this is a "string"\'',
+      priority: "",
+      offsets: [0, 29]
+    }]
+  },
+  {
+    input: "content: 'this is a \\'string\\''",
+    expected: [{
+      name: "content",
+      value: "'this is a \\'string\\''",
+      priority: "",
+      offsets: [0, 31],
+    }]
+  },
+  {
+    input: "content: 'this \\' is a \" really strange string'",
+    expected: [{
+      name: "content",
+      value: "'this \\' is a \" really strange string'",
+      priority: "",
+      offsets: [0, 47]
+    }]
+  },
+  {
+    input: "content: \"a not s\\\
+          o very long title\"",
+    expected: [
+      {name: "content", value: '"a not s\\\
+          o very long title"', priority: "", offsets: [0, 46]}
+    ]
+  },
+  // Test calc with nested parentheses
+  {
+    input: "width: calc((100% - 3em) / 2)",
+    expected: [{name: "width", value: "calc((100% - 3em) / 2)", priority: "",
+                offsets: [0, 29]}]
+  },
+
+  // Simple embedded comment test.
+  {
+    parseComments: true,
+    input: "width: 5; /* background: green; */ background: red;",
+    expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
+               {name: "background", value: "green", priority: "",
+                offsets: [13, 31], commentOffsets: [10, 34]},
+               {name: "background", value: "red", priority: "",
+                offsets: [35, 51]}]
+  },
+
+  // Embedded comment where the parsing heuristic fails.
+  {
+    parseComments: true,
+    input: "width: 5; /* background something: green; */ background: red;",
+    expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
+               {name: "background", value: "red", priority: "",
+                offsets: [45, 61]}]
+  },
+
+  // Embedded comment where the parsing heuristic is a bit funny.
+  {
+    parseComments: true,
+    input: "width: 5; /* background: */ background: red;",
+    expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
+               {name: "background", value: "", priority: "",
+                offsets: [13, 24], commentOffsets: [10, 27]},
+               {name: "background", value: "red", priority: "",
+                offsets: [28, 44]}]
+  },
+
+  // Another case where the parsing heuristic says not to bother; note
+  // that there is no ";" in the comment.
+  {
+    parseComments: true,
+    input: "width: 5; /* background: yellow */ background: red;",
+    expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
+               {name: "background", value: "yellow", priority: "",
+                offsets: [13, 31], commentOffsets: [10, 34]},
+               {name: "background", value: "red", priority: "",
+                offsets: [35, 51]}]
+  },
+
+  // Parsing a comment should yield text that has been unescaped, and
+  // the offsets should refer to the original text.
+  {
+    parseComments: true,
+    input: "/* content: '*\\/'; */",
+    expected: [{name: "content", value: "'*/'", priority: "",
+                offsets: [3, 18], commentOffsets: [0, 21]}]
+  },
+
+  // Parsing a comment should yield text that has been unescaped, and
+  // the offsets should refer to the original text.  This variant
+  // tests the no-semicolon path.
+  {
+    parseComments: true,
+    input: "/* content: '*\\/' */",
+    expected: [{name: "content", value: "'*/'", priority: "",
+                offsets: [3, 17], commentOffsets: [0, 20]}]
+  },
+
+  // A comment-in-a-comment should yield the correct offsets.
+  {
+    parseComments: true,
+    input: "/* color: /\\* comment *\\/ red; */",
+    expected: [{name: "color", value: "red", priority: "",
+                offsets: [3, 30], commentOffsets: [0, 33]}]
+  },
+
+  // HTML comments are ignored.
+  {
+    parseComments: true,
+    input: "<!-- color: red; --> color: blue;",
+    expected: [{name: "color", value: "red", priority: "",
+                offsets: [5, 16]},
+               {name: "color", value: "blue", priority: "",
+                offsets: [21, 33]}]
+  },
+
+  // Don't error on an empty comment.
+  {
+    parseComments: true,
+    input: "/**/",
+    expected: []
+  },
+
+  // Parsing our special comments skips the name-check heuristic.
+  {
+    parseComments: true,
+    input: "/*! walrus: zebra; */",
+    expected: [{name: "walrus", value: "zebra", priority: "",
+                offsets: [4, 18], commentOffsets: [0, 21]}]
+  }
+];
+
+function run_test() {
+  run_basic_tests();
+  run_comment_tests();
+}
+
+// Test parseDeclarations.
+function run_basic_tests() {
+  for (let test of TEST_DATA) {
+    do_print("Test input string " + test.input);
+    let output;
+    try {
+      output = parseDeclarations(test.input, test.parseComments);
+    } catch (e) {
+      do_print("parseDeclarations threw an exception with the given input " +
+        "string");
+      if (test.throws) {
+        do_print("Exception expected");
+        do_check_true(true);
+      } else {
+        do_print("Exception unexpected\n" + e);
+        do_check_true(false);
+      }
+    }
+    if (output) {
+      assertOutput(output, test.expected);
+    }
+  }
+}
+
+const COMMENT_DATA = [
+  {
+    input: "content: 'hi",
+    expected: [{name: "content", value: "'hi", priority: "", terminator: "';",
+                offsets: [2, 14], colonOffsets: [9, 11],
+                commentOffsets: [0, 16]}],
+  },
+  {
+    input: "text that once confounded the parser;",
+    expected: []
+  },
+];
+
+// Test parseCommentDeclarations.
+function run_comment_tests() {
+  for (let test of COMMENT_DATA) {
+    do_print("Test input string " + test.input);
+    let output = _parseCommentDeclarations(test.input, 0,
+                                           test.input.length + 4);
+    deepEqual(output, test.expected);
+  }
+}
+
+function assertOutput(actual, expected) {
+  if (actual.length === expected.length) {
+    for (let i = 0; i < expected.length; i++) {
+      do_check_true(!!actual[i]);
+      do_print("Check that the output item has the expected name, " +
+        "value and priority");
+      do_check_eq(expected[i].name, actual[i].name);
+      do_check_eq(expected[i].value, actual[i].value);
+      do_check_eq(expected[i].priority, actual[i].priority);
+      deepEqual(expected[i].offsets, actual[i].offsets);
+      if ("commentOffsets" in expected[i]) {
+        deepEqual(expected[i].commentOffsets, actual[i].commentOffsets);
+      }
+    }
+  } else {
+    for (let prop of actual) {
+      do_print("Actual output contained: {name: " + prop.name + ", value: " +
+        prop.value + ", priority: " + prop.priority + "}");
+    }
+    do_check_eq(actual.length, expected.length);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/unit/test_parsePseudoClassesAndAttributes.js
@@ -0,0 +1,213 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var Cu = Components.utils;
+const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+const {
+  parsePseudoClassesAndAttributes,
+  SELECTOR_ATTRIBUTE,
+  SELECTOR_ELEMENT,
+  SELECTOR_PSEUDO_CLASS
+} = require("devtools/client/shared/css-parsing-utils");
+
+const TEST_DATA = [
+  // Test that a null input throws an exception
+  {
+    input: null,
+    throws: true
+  },
+  // Test that a undefined input throws an exception
+  {
+    input: undefined,
+    throws: true
+  },
+  {
+    input: ":root",
+    expected: [
+      { value: ":root", type: SELECTOR_PSEUDO_CLASS }
+    ]
+  },
+  {
+    input: ".testclass",
+    expected: [
+      { value: ".testclass", type: SELECTOR_ELEMENT }
+    ]
+  },
+  {
+    input: "div p",
+    expected: [
+      { value: "div p", type: SELECTOR_ELEMENT }
+    ]
+  },
+  {
+    input: "div > p",
+    expected: [
+      { value: "div > p", type: SELECTOR_ELEMENT }
+    ]
+  },
+  {
+    input: "a[hidden]",
+    expected: [
+      { value: "a", type: SELECTOR_ELEMENT },
+      { value: "[hidden]", type: SELECTOR_ATTRIBUTE }
+    ]
+  },
+  {
+    input: "a[hidden=true]",
+    expected: [
+      { value: "a", type: SELECTOR_ELEMENT },
+      { value: "[hidden=true]", type: SELECTOR_ATTRIBUTE }
+    ]
+  },
+  {
+    input: "a[hidden=true] p:hover",
+    expected: [
+      { value: "a", type: SELECTOR_ELEMENT },
+      { value: "[hidden=true]", type: SELECTOR_ATTRIBUTE },
+      { value: " p", type: SELECTOR_ELEMENT },
+      { value: ":hover", type: SELECTOR_PSEUDO_CLASS }
+    ]
+  },
+  {
+    input: "a[checked=\"true\"]",
+    expected: [
+      { value: "a", type: SELECTOR_ELEMENT },
+      { value: "[checked=\"true\"]", type: SELECTOR_ATTRIBUTE }
+    ]
+  },
+  {
+    input: "a[title~=test]",
+    expected: [
+      { value: "a", type: SELECTOR_ELEMENT },
+      { value: "[title~=test]", type: SELECTOR_ATTRIBUTE }
+    ]
+  },
+  {
+    input: "h1[hidden=\"true\"][title^=\"Important\"]",
+    expected: [
+      { value: "h1", type: SELECTOR_ELEMENT },
+      { value: "[hidden=\"true\"]", type: SELECTOR_ATTRIBUTE },
+      { value: "[title^=\"Important\"]", type: SELECTOR_ATTRIBUTE}
+    ]
+  },
+  {
+    input: "p:hover",
+    expected: [
+      { value: "p", type: SELECTOR_ELEMENT },
+      { value: ":hover", type: SELECTOR_PSEUDO_CLASS }
+    ]
+  },
+  {
+    input: "p + .testclass:hover",
+    expected: [
+      { value: "p + .testclass", type: SELECTOR_ELEMENT },
+      { value: ":hover", type: SELECTOR_PSEUDO_CLASS }
+    ]
+  },
+  {
+    input: "p::before",
+    expected: [
+      { value: "p", type: SELECTOR_ELEMENT },
+      { value: "::before", type: SELECTOR_PSEUDO_CLASS }
+    ]
+  },
+  {
+    input: "p:nth-child(2)",
+    expected: [
+      { value: "p", type: SELECTOR_ELEMENT },
+      { value: ":nth-child(2)", type: SELECTOR_PSEUDO_CLASS }
+    ]
+  },
+  {
+    input: "p:not([title=\"test\"]) .testclass",
+    expected: [
+      { value: "p", type: SELECTOR_ELEMENT },
+      { value: ":not([title=\"test\"])", type: SELECTOR_PSEUDO_CLASS },
+      { value: " .testclass", type: SELECTOR_ELEMENT }
+    ]
+  },
+  {
+    input: "a\\:hover",
+    expected: [
+      { value: "a\\:hover", type: SELECTOR_ELEMENT }
+    ]
+  },
+  {
+    input: ":not(:lang(it))",
+    expected: [
+      { value: ":not(:lang(it))", type: SELECTOR_PSEUDO_CLASS }
+    ]
+  },
+  {
+    input: "p:not(:lang(it))",
+    expected: [
+      { value: "p", type: SELECTOR_ELEMENT },
+      { value: ":not(:lang(it))", type: SELECTOR_PSEUDO_CLASS }
+    ]
+  },
+  {
+    input: "p:not(p:lang(it))",
+    expected: [
+      { value: "p", type: SELECTOR_ELEMENT },
+      { value: ":not(p:lang(it))", type: SELECTOR_PSEUDO_CLASS }
+    ]
+  },
+  {
+    input: ":not(:lang(it)",
+    expected: [
+      { value: ":not(:lang(it)", type: SELECTOR_ELEMENT }
+    ]
+  },
+  {
+    input: ":not(:lang(it)))",
+    expected: [
+      { value: ":not(:lang(it))", type: SELECTOR_PSEUDO_CLASS },
+      { value: ")", type: SELECTOR_ELEMENT }
+    ]
+  }
+];
+
+function run_test() {
+  for (let test of TEST_DATA) {
+    dump("Test input string " + test.input + "\n");
+    let output;
+
+    try {
+      output = parsePseudoClassesAndAttributes(test.input);
+    } catch (e) {
+      dump("parsePseudoClassesAndAttributes threw an exception with the " +
+        "given input string\n");
+      if (test.throws) {
+        ok(true, "Exception expected");
+      } else {
+        dump();
+        ok(false, "Exception unexpected\n" + e);
+      }
+    }
+
+    if (output) {
+      assertOutput(output, test.expected);
+    }
+  }
+}
+
+function assertOutput(actual, expected) {
+  if (actual.length === expected.length) {
+    for (let i = 0; i < expected.length; i++) {
+      dump("Check that the output item has the expected value and type\n");
+      ok(!!actual[i]);
+      equal(expected[i].value, actual[i].value);
+      equal(expected[i].type, actual[i].type);
+    }
+  } else {
+    for (let prop of actual) {
+      dump("Actual output contained: {value: " + prop.value + ", type: " +
+        prop.type + "}\n");
+    }
+    equal(actual.length, expected.length);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/unit/test_parseSingleValue.js
@@ -0,0 +1,92 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var Cu = Components.utils;
+const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+const {parseSingleValue} = require("devtools/client/shared/css-parsing-utils");
+
+const TEST_DATA = [
+  {input: null, throws: true},
+  {input: undefined, throws: true},
+  {input: "", expected: {value: "", priority: ""}},
+  {input: "  \t \t \n\n  ", expected: {value: "", priority: ""}},
+  {input: "blue", expected: {value: "blue", priority: ""}},
+  {input: "blue !important", expected: {value: "blue", priority: "important"}},
+  {input: "blue!important", expected: {value: "blue", priority: "important"}},
+  {input: "blue ! important", expected: {value: "blue", priority: "important"}},
+  {
+    input: "blue !  important",
+    expected: {value: "blue", priority: "important"}
+  },
+  {input: "blue !", expected: {value: "blue", priority: ""}},
+  {input: "blue !mportant", expected: {value: "blue !mportant", priority: ""}},
+  {
+    input: "  blue   !important ",
+    expected: {value: "blue", priority: "important"}
+  },
+  {
+    input: "url(\"http://url.com/whyWouldYouDoThat!important.png\") !important",
+    expected: {
+      value: "url(\"http://url.com/whyWouldYouDoThat!important.png\")",
+      priority: "important"
+    }
+  },
+  {
+    input: "url(\"http://url.com/whyWouldYouDoThat!important.png\")",
+    expected: {
+      value: "url(\"http://url.com/whyWouldYouDoThat!important.png\")",
+      priority: ""
+    }
+  },
+  {
+    input: "\"content!important\" !important",
+    expected: {
+      value: "\"content!important\"",
+      priority: "important"
+    }
+  },
+  {
+    input: "\"content!important\"",
+    expected: {
+      value: "\"content!important\"",
+      priority: ""
+    }
+  },
+  {
+    input: "\"all the \\\"'\\\\ special characters\"",
+    expected: {
+      value: "\"all the \\\"'\\\\ special characters\"",
+      priority: ""
+    }
+  }
+];
+
+function run_test() {
+  for (let test of TEST_DATA) {
+    do_print("Test input value " + test.input);
+    try {
+      let output = parseSingleValue(test.input);
+      assertOutput(output, test.expected);
+    } catch (e) {
+      do_print("parseSingleValue threw an exception with the given input " +
+        "value");
+      if (test.throws) {
+        do_print("Exception expected");
+        do_check_true(true);
+      } else {
+        do_print("Exception unexpected\n" + e);
+        do_check_true(false);
+      }
+    }
+  }
+}
+
+function assertOutput(actual, expected) {
+  do_print("Check that the output has the expected value and priority");
+  do_check_eq(expected.value, actual.value);
+  do_check_eq(expected.priority, actual.priority);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/unit/test_rewriteDeclarations.js
@@ -0,0 +1,478 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var Cu = Components.utils;
+Cu.import("resource://gre/modules/devtools/Loader.jsm");
+const {parseDeclarations, RuleRewriter} =
+      devtools.require("devtools/client/shared/css-parsing-utils");
+
+const TEST_DATA = [
+  {
+    desc: "simple set",
+    input: "p:v;",
+    instruction: {type: "set", name: "p", value: "N", priority: "",
+                  index: 0},
+    expected: "p:N;"
+  },
+  {
+    desc: "simple set clearing !important",
+    input: "p:v !important;",
+    instruction: {type: "set", name: "p", value: "N", priority: "",
+                  index: 0},
+    expected: "p:N;"
+  },
+  {
+    desc: "simple set adding !important",
+    input: "p:v;",
+    instruction: {type: "set", name: "p", value: "N", priority: "important",
+                  index: 0},
+    expected: "p:N !important;"
+  },
+  {
+    desc: "simple set between comments",
+    input: "/*color:red;*/ p:v; /*color:green;*/",
+    instruction: {type: "set", name: "p", value: "N", priority: "",
+                  index: 1},
+    expected: "/*color:red;*/ p:N; /*color:green;*/"
+  },
+  // The rule view can generate a "set" with a previously unknown
+  // property index; which should work like "create".
+  {
+    desc: "set at unknown index",
+    input: "a:b; e: f;",
+    instruction: {type: "set", name: "c", value: "d", priority: "",
+                  index: 2},
+    expected: "a:b; e: f;c: d;"
+  },
+  {
+    desc: "simple rename",
+    input: "p:v;",
+    instruction: {type: "rename", name: "p", newName: "q", index: 0},
+    expected: "q:v;"
+  },
+  // "rename" is passed the name that the user entered, and must do
+  // any escaping necessary to ensure that this is an identifier.
+  {
+    desc: "rename requiring escape",
+    input: "p:v;",
+    instruction: {type: "rename", name: "p", newName: "a b", index: 0},
+    expected: "a\\ b:v;"
+  },
+  {
+    desc: "simple create",
+    input: "",
+    instruction: {type: "create", name: "p", value: "v", priority: "important",
+                  index: 0},
+    expected: "p: v !important;"
+  },
+  {
+    desc: "create between two properties",
+    input: "a:b; e: f;",
+    instruction: {type: "create", name: "c", value: "d", priority: "",
+                  index: 1},
+    expected: "a:b; c: d;e: f;"
+  },
+  // "create" is passed the name that the user entered, and must do
+  // any escaping necessary to ensure that this is an identifier.
+  {
+    desc: "create requiring escape",
+    input: "",
+    instruction: {type: "create", name: "a b", value: "d", priority: "",
+                  index: 1},
+    expected: "a\\ b: d;"
+  },
+  {
+    desc: "simple disable",
+    input: "p:v;",
+    instruction: {type: "enable", name: "p", value: false, index: 0},
+    expected: "/*! p:v; */"
+  },
+  {
+    desc: "simple enable",
+    input: "/* color:v; */",
+    instruction: {type: "enable", name: "color", value: true, index: 0},
+    expected: "color:v;"
+  },
+  {
+    desc: "enable with following property in comment",
+    input: "/* color:red; color: blue; */",
+    instruction: {type: "enable", name: "color", value: true, index: 0},
+    expected: "color:red; /* color: blue; */"
+  },
+  {
+    desc: "enable with preceding property in comment",
+    input: "/* color:red; color: blue; */",
+    instruction: {type: "enable", name: "color", value: true, index: 1},
+    expected: "/* color:red; */ color: blue;"
+  },
+  {
+    desc: "simple remove",
+    input: "a:b;c:d;e:f;",
+    instruction: {type: "remove", name: "c", index: 1},
+    expected: "a:b;e:f;"
+  },
+  {
+    desc: "disable with comment ender in string",
+    input: "content: '*/';",
+    instruction: {type: "enable", name: "content", value: false, index: 0},
+    expected: "/*! content: '*\\/'; */"
+  },
+  {
+    desc: "enable with comment ender in string",
+    input: "/* content: '*\\/'; */",
+    instruction: {type: "enable", name: "content", value: true, index: 0},
+    expected: "content: '*/';"
+  },
+  {
+    desc: "enable requiring semicolon insertion",
+    // Note the lack of a trailing semicolon in the comment.
+    input: "/* color:red */ color: blue;",
+    instruction: {type: "enable", name: "color", value: true, index: 0},
+    expected: "color:red; color: blue;"
+  },
+  {
+    desc: "create requiring semicolon insertion",
+    // Note the lack of a trailing semicolon.
+    input: "color: red",
+    instruction: {type: "create", name: "a", value: "b", priority: "",
+                  index: 1},
+    expected: "color: red;a: b;"
+  },
+
+  // Newline insertion.
+  {
+    desc: "simple newline insertion",
+    input: "\ncolor: red;\n",
+    instruction: {type: "create", name: "a", value: "b", priority: "",
+                  index: 1},
+    expected: "\ncolor: red;\na: b;\n"
+  },
+  // Newline insertion.
+  {
+    desc: "semicolon insertion before newline",
+    // Note the lack of a trailing semicolon.
+    input: "\ncolor: red\n",
+    instruction: {type: "create", name: "a", value: "b", priority: "",
+                  index: 1},
+    expected: "\ncolor: red;\na: b;\n"
+  },
+  // Newline insertion.
+  {
+    desc: "newline and semicolon insertion",
+    // Note the lack of a trailing semicolon and newline.
+    input: "\ncolor: red",
+    instruction: {type: "create", name: "a", value: "b", priority: "",
+                  index: 1},
+    expected: "\ncolor: red;\na: b;\n"
+  },
+
+  // Newline insertion and indentation.
+  {
+    desc: "indentation with create",
+    input: "\n  color: red;\n",
+    instruction: {type: "create", name: "a", value: "b", priority: "",
+                  index: 1},
+    expected: "\n  color: red;\n  a: b;\n"
+  },
+  // Newline insertion and indentation.
+  {
+    desc: "indentation plus semicolon insertion before newline",
+    // Note the lack of a trailing semicolon.
+    input: "\n  color: red\n",
+    instruction: {type: "create", name: "a", value: "b", priority: "",
+                  index: 1},
+    expected: "\n  color: red;\n  a: b;\n"
+  },
+  {
+    desc: "indentation inserted before trailing whitespace",
+    // Note the trailing whitespace.  This could come from a rule
+    // like:
+    // @supports (mumble) {
+    //   body {
+    //     color: red;
+    //   }
+    // }
+    // Here if we create a rule we don't want it to follow
+    // the indentation of the "}".
+    input: "\n    color: red;\n  ",
+    instruction: {type: "create", name: "a", value: "b", priority: "",
+                  index: 1},
+    expected: "\n    color: red;\n    a: b;\n  "
+  },
+  // Newline insertion and indentation.
+  {
+    desc: "indentation comes from preceding comment",
+    // Note how the comment comes before the declaration.
+    input: "\n  /* comment */ color: red\n",
+    instruction: {type: "create", name: "a", value: "b", priority: "",
+                  index: 1},
+    expected: "\n  /* comment */ color: red;\n  a: b;\n"
+  },
+  // Default indentation.
+  {
+    desc: "use of default indentation",
+    input: "\n",
+    instruction: {type: "create", name: "a", value: "b", priority: "",
+                  index: 0},
+    expected: "\n\ta: b;\n"
+  },
+
+  // Deletion handles newlines properly.
+  {
+    desc: "deletion removes newline",
+    input: "a:b;\nc:d;\ne:f;",
+    instruction: {type: "remove", name: "c", index: 1},
+    expected: "a:b;\ne:f;"
+  },
+  // Deletion handles newlines properly.
+  {
+    desc: "deletion remove blank line",
+    input: "\n  a:b;\n  c:d;  \ne:f;",
+    instruction: {type: "remove", name: "c", index: 1},
+    expected: "\n  a:b;\ne:f;"
+  },
+  // Deletion handles newlines properly.
+  {
+    desc: "deletion leaves comment",
+    input: "\n  a:b;\n  /* something */ c:d;  \ne:f;",
+    instruction: {type: "remove", name: "c", index: 1},
+    expected: "\n  a:b;\n  /* something */   \ne:f;"
+  },
+  // Deletion handles newlines properly.
+  {
+    desc: "deletion leaves previous newline",
+    input: "\n  a:b;\n  c:d;  \ne:f;",
+    instruction: {type: "remove", name: "e", index: 2},
+    expected: "\n  a:b;\n  c:d;  \n"
+  },
+  // Deletion handles newlines properly.
+  {
+    desc: "deletion removes trailing whitespace",
+    input: "\n  a:b;\n  c:d;  \n    e:f;",
+    instruction: {type: "remove", name: "e", index: 2},
+    expected: "\n  a:b;\n  c:d;  \n"
+  },
+  // Deletion handles newlines properly.
+  {
+    desc: "deletion preserves indentation",
+    input: "  a:b;\n  c:d;  \n    e:f;",
+    instruction: {type: "remove", name: "a", index: 0},
+    expected: "  c:d;  \n    e:f;"
+  },
+
+  // Termination insertion corner case.
+  {
+    desc: "enable single quote termination",
+    input: "/* content: 'hi */ color: red;",
+    instruction: {type: "enable", name: "content", value: true, index: 0},
+    expected: "content: 'hi'; color: red;"
+  },
+  // Termination insertion corner case.
+  {
+    desc: "create single quote termination",
+    input: "content: 'hi",
+    instruction: {type: "create", name: "color", value: "red", priority: "",
+                  index: 1},
+    expected: "content: 'hi';color: red;"
+  },
+
+  // Termination insertion corner case.
+  {
+    desc: "enable double quote termination",
+    input: "/* content: \"hi */ color: red;",
+    instruction: {type: "enable", name: "content", value: true, index: 0},
+    expected: "content: \"hi\"; color: red;"
+  },
+  // Termination insertion corner case.
+  {
+    desc: "create double quote termination",
+    input: "content: \"hi",
+    instruction: {type: "create", name: "color", value: "red", priority: "",
+                  index: 1},
+    expected: "content: \"hi\";color: red;"
+  },
+
+  // Termination insertion corner case.
+  {
+    desc: "enable url termination",
+    input: "/* background-image: url(something.jpg */ color: red;",
+    instruction: {type: "enable", name: "background-image", value: true,
+                  index: 0},
+    expected: "background-image: url(something.jpg); color: red;"
+  },
+  // Termination insertion corner case.
+  {
+    desc: "create url termination",
+    input: "background-image: url(something.jpg",
+    instruction: {type: "create", name: "color", value: "red", priority: "",
+                  index: 1},
+    expected: "background-image: url(something.jpg);color: red;"
+  },
+
+  // Termination insertion corner case.
+  {
+    desc: "enable url single quote termination",
+    input: "/* background-image: url('something.jpg */ color: red;",
+    instruction: {type: "enable", name: "background-image", value: true,
+                  index: 0},
+    expected: "background-image: url('something.jpg'); color: red;"
+  },
+  // Termination insertion corner case.
+  {
+    desc: "create url single quote termination",
+    input: "background-image: url('something.jpg",
+    instruction: {type: "create", name: "color", value: "red", priority: "",
+                  index: 1},
+    expected: "background-image: url('something.jpg');color: red;"
+  },
+
+  // Termination insertion corner case.
+  {
+    desc: "create url double quote termination",
+    input: "/* background-image: url(\"something.jpg */ color: red;",
+    instruction: {type: "enable", name: "background-image", value: true,
+                  index: 0},
+    expected: "background-image: url(\"something.jpg\"); color: red;"
+  },
+  // Termination insertion corner case.
+  {
+    desc: "enable url double quote termination",
+    input: "background-image: url(\"something.jpg",
+    instruction: {type: "create", name: "color", value: "red", priority: "",
+                  index: 1},
+    expected: "background-image: url(\"something.jpg\");color: red;"
+  },
+
+  // Termination insertion corner case.
+  {
+    desc: "create backslash termination",
+    input: "something: \\",
+    instruction: {type: "create", name: "color", value: "red", priority: "",
+                  index: 1},
+    expected: "something: \\\\;color: red;"
+  },
+
+  // Termination insertion corner case.
+  {
+    desc: "enable backslash single quote termination",
+    input: "something: '\\",
+    instruction: {type: "create", name: "color", value: "red", priority: "",
+                  index: 1},
+    expected: "something: '\\\\';color: red;"
+  },
+  {
+    desc: "enable backslash double quote termination",
+    input: "something: \"\\",
+    instruction: {type: "create", name: "color", value: "red", priority: "",
+                  index: 1},
+    expected: "something: \"\\\\\";color: red;"
+  },
+
+  // Termination insertion corner case.
+  {
+    desc: "enable comment termination",
+    input: "something: blah /* comment ",
+    instruction: {type: "create", name: "color", value: "red", priority: "",
+                  index: 1},
+    expected: "something: blah /* comment*/; color: red;"
+  },
+
+  // Rewrite a "heuristic override" comment.
+  {
+    desc: "enable with heuristic override comment",
+    input: "/*! walrus: zebra; */",
+    instruction: {type: "enable", name: "walrus", value: true, index: 0},
+    expected: "walrus: zebra;"
+  },
+
+  // Sanitize a bad value.
+  {
+    desc: "create sanitize unpaired brace",
+    input: "",
+    instruction: {type: "create", name: "p", value: "}", priority: "",
+                  index: 0},
+    expected: "p: \\};",
+    changed: {0: "\\}"}
+  },
+  // Sanitize a bad value.
+  {
+    desc: "set sanitize unpaired brace",
+    input: "walrus: zebra;",
+    instruction: {type: "set", name: "walrus", value: "{{}}}", priority: "",
+                  index: 0},
+    expected: "walrus: {{}}\\};",
+    changed: {0: "{{}}\\}"}
+  },
+  // Sanitize a bad value.
+  {
+    desc: "enable sanitize unpaired brace",
+    input: "/*! walrus: }*/",
+    instruction: {type: "enable", name: "walrus", value: true, index: 0},
+    expected: "walrus: \\};",
+    changed: {0: "\\}"}
+  },
+
+  // Creating a new declaration does not require an attempt to
+  // terminate a previous commented declaration.
+  {
+    desc: "disabled declaration does not need semicolon insertion",
+    input: "/*! no: semicolon */\n",
+    instruction: {type: "create", name: "walrus", value: "zebra", priority: "",
+                  index: 1},
+    expected: "/*! no: semicolon */\nwalrus: zebra;\n",
+    changed: {}
+  },
+];
+
+function rewriteDeclarations(inputString, instruction, defaultIndentation) {
+  let rewriter = new RuleRewriter(null, inputString);
+  rewriter.defaultIndentation = defaultIndentation;
+
+  switch (instruction.type) {
+    case "rename":
+      rewriter.renameProperty(instruction.index, instruction.name,
+                              instruction.newName);
+      break;
+
+    case "enable":
+      rewriter.setPropertyEnabled(instruction.index, instruction.name,
+                                  instruction.value);
+      break;
+
+    case "create":
+      rewriter.createProperty(instruction.index, instruction.name,
+                              instruction.value, instruction.priority);
+      break;
+
+    case "set":
+      rewriter.setProperty(instruction.index, instruction.name,
+                           instruction.value, instruction.priority);
+      break;
+
+    case "remove":
+      rewriter.removeProperty(instruction.index, instruction.name);
+      break;
+
+    default:
+      throw new Error("unrecognized instruction");
+  }
+
+  return rewriter.getResult();
+}
+
+function run_test() {
+  let i = 0;
+  for (let test of TEST_DATA) {
+    ++i;
+    let {changed, text} = rewriteDeclarations(test.input, test.instruction,
+                                              "\t");
+    equal(text, test.expected, "output for " + test.desc);
+    if ("changed" in test) {
+      deepEqual(changed, test.changed, "changed result for " + test.desc);
+    }
+  }
+}
--- a/devtools/client/shared/test/unit/xpcshell.ini
+++ b/devtools/client/shared/test/unit/xpcshell.ini
@@ -5,11 +5,16 @@ tail =
 firefox-appdir = browser
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 
 [test_advanceValidate.js]
 [test_attribute-parsing-01.js]
 [test_attribute-parsing-02.js]
 [test_bezierCanvas.js]
 [test_cubicBezier.js]
+[test_escapeCSSComment.js]
+[test_parseDeclarations.js]
+[test_parsePseudoClassesAndAttributes.js]
+[test_parseSingleValue.js]
+[test_rewriteDeclarations.js]
 [test_undoStack.js]
 [test_VariablesView_filtering-without-controller.js]
 [test_VariablesView_getString_promise.js]
--- a/devtools/client/shared/theme.js
+++ b/devtools/client/shared/theme.js
@@ -9,22 +9,23 @@
  * https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors
  */
 
 const { Ci, Cu } = require("chrome");
 const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 loader.lazyRequireGetter(this, "Services");
 loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/client/framework/gDevTools.jsm");
 
-const themeURIs = {
-  light: "chrome://devtools/skin/themes/light-theme.css",
-  dark: "chrome://devtools/skin/themes/dark-theme.css"
+const VARIABLES_URI = "chrome://devtools/skin/themes/variables.css";
+const THEME_SELECTOR_STRINGS = {
+  light: ":root.theme-light {",
+  dark: ":root.theme-dark {"
 }
 
-const cachedThemes = {};
+let variableFileContents;
 
 /**
  * Returns a string of the file found at URI
  */
 function readURI (uri) {
   let stream = NetUtil.newChannel({
     uri: NetUtil.newURI(uri, "UTF-8"),
     loadUsingSystemPrincipal: true}
@@ -32,56 +33,60 @@ function readURI (uri) {
 
   let count = stream.available();
   let data = NetUtil.readInputStreamToString(stream, count, { charset: "UTF-8" });
   stream.close();
   return data;
 }
 
 /**
- * Takes a theme name and either returns it from the cache,
- * or fetches the theme CSS file and caches it.
+ * Takes a theme name and returns the contents of its variable rule block.
+ * The first time this runs fetches the variables CSS file and caches it.
  */
 function getThemeFile (name) {
-  // Use the cached theme, or generate it
-  let themeFile = cachedThemes[name] || readURI(themeURIs[name]).match(/--theme-.*: .*;/g).join("\n");
-
-  // Cache if not already cached
-  if (!cachedThemes[name]) {
-    cachedThemes[name] = themeFile;
+  if (!variableFileContents) {
+    variableFileContents = readURI(VARIABLES_URI);
   }
 
-  return themeFile;
+  // If there's no theme expected for this name, use `light` as default.
+  let selector = THEME_SELECTOR_STRINGS[name] ||
+                 THEME_SELECTOR_STRINGS["light"];
+
+  // This is a pretty naive way to find the contents between:
+  // selector {
+  //   name: val;
+  // }
+  // There is test coverage for this feature (browser_theme.js)
+  // so if an } is introduced in the variables file it will catch that.
+  let theme = variableFileContents;
+  theme = theme.substring(theme.indexOf(selector));
+  theme = theme.substring(0, theme.indexOf("}"));
+
+  return theme;
 }
 
 /**
  * Returns the string value of the current theme,
  * like "dark" or "light".
  */
 const getTheme = exports.getTheme = () => Services.prefs.getCharPref("devtools.theme");
 
 /**
  * Returns a color indicated by `type` (like "toolbar-background", or "highlight-red"),
  * with the ability to specify a theme, or use whatever the current theme is
  * if left unset. If theme not found, falls back to "light" theme. Returns null
  * if the type cannot be found for the theme given.
  */
 const getColor = exports.getColor = (type, theme) => {
   let themeName = theme || getTheme();
-
-  // If there's no theme URIs for this theme, use `light` as default.
-  if (!themeURIs[themeName]) {
-    themeName = "light";
-  }
-
   let themeFile = getThemeFile(themeName);
-  let match;
+  let match = themeFile.match(new RegExp("--theme-" + type + ": (.*);"));
 
   // Return the appropriate variable in the theme, or otherwise, null.
-  return (match = themeFile.match(new RegExp("--theme-" + type + ": (.*);"))) ? match[1] : null;
+  return match ? match[1] : null;
 };
 
 /**
  * Mimics selecting the theme selector in the toolbox;
  * sets the preference and emits an event on gDevTools to trigger
  * the themeing.
  */
 const setTheme = exports.setTheme = (newTheme) => {
--- a/devtools/client/shared/widgets/FilterWidget.js
+++ b/devtools/client/shared/widgets/FilterWidget.js
@@ -9,17 +9,17 @@
   * for Rule View's filter swatches
   */
 
 const EventEmitter = require("devtools/shared/event-emitter");
 const { Cu } = require("chrome");
 const { ViewHelpers } = Cu.import("resource:///modules/devtools/client/shared/widgets/ViewHelpers.jsm", {});
 const STRINGS_URI = "chrome://browser/locale/devtools/filterwidget.properties";
 const L10N = new ViewHelpers.L10N(STRINGS_URI);
-const {cssTokenizer} = require("devtools/client/sourceeditor/css-tokenizer");
+const {cssTokenizer} = require("devtools/client/shared/css-parsing-utils");
 
 loader.lazyGetter(this, "asyncStorage",
                   () => require("devtools/shared/shared/async-storage"));
 
 const DEFAULT_FILTER_TYPE = "length";
 const UNIT_MAPPING = {
   percentage: "%",
   length: "px",
--- a/devtools/client/sourceeditor/css-autocompleter.js
+++ b/devtools/client/sourceeditor/css-autocompleter.js
@@ -1,33 +1,33 @@
 /* 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/. */
 
 const { Cc, Ci, Cu } = require('chrome');
 const {cssTokenizer, cssTokenizerWithLineColumn}  =
-      require("devtools/client/sourceeditor/css-tokenizer");
+      require("devtools/client/shared/css-parsing-utils");
 
 /**
- * Here is what this file (+ ./css-tokenizer.js) do.
+ * Here is what this file (+ css-parsing-utils.js) do.
  *
  * The main objective here is to provide as much suggestions to the user editing
  * a stylesheet in Style Editor. The possible things that can be suggested are:
  *  - CSS property names
  *  - CSS property values
  *  - CSS Selectors
  *  - Some other known CSS keywords
  *
  * Gecko provides a list of both property names and their corresponding values.
  * We take out a list of matching selectors using the Inspector actor's
  * `getSuggestionsForQuery` method. Now the only thing is to parse the CSS being
  * edited by the user, figure out what token or word is being written and last
  * but the most difficult, what is being edited.
  *
- * The file 'css-tokenizer' helps in converting the CSS into meaningful tokens,
+ * The file 'css-parsing-utils' helps to convert the CSS into meaningful tokens,
  * each having a certain type associated with it. These tokens help us to figure
  * out the currently edited word and to write a CSS state machine to figure out
  * what the user is currently editing. By that, I mean, whether he is editing a
  * selector or a property or a value, or even fine grained information like an
  * id in the selector.
  *
  * The `resolveState` method iterated over the tokens spitted out by the
  * tokenizer, using switch cases, follows a state machine logic and finally
deleted file mode 100644
--- a/devtools/client/sourceeditor/css-tokenizer.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
-
-"use strict";
-
-const {Cc, Ci} = require("chrome");
-loader.lazyGetter(this, "DOMUtils", () => {
-  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
-});
-
-/**
- * A generator function that lexes a CSS source string, yielding the
- * CSS tokens.  Comment tokens are dropped.
- *
- * @param {String} CSS source string
- * @yield {CSSToken} The next CSSToken that is lexed
- * @see CSSToken for details about the returned tokens
- */
-function* cssTokenizer(string) {
-  let lexer = DOMUtils.getCSSLexer(string);
-  while (true) {
-    let token = lexer.nextToken();
-    if (!token) {
-      break;
-    }
-    // None of the existing consumers want comments.
-    if (token.tokenType !== "comment") {
-      yield token;
-    }
-  }
-}
-
-exports.cssTokenizer = cssTokenizer;
-
-/**
- * Pass |string| to the CSS lexer and return an array of all the
- * returned tokens.  Comment tokens are not included.  In addition to
- * the usual information, each token will have starting and ending
- * line and column information attached.  Specifically, each token
- * has an additional "loc" attribute.  This attribute is an object
- * of the form {line: L, column: C}.  Lines and columns are both zero
- * based.
- *
- * It's best not to add new uses of this function.  In general it is
- * simpler and better to use the CSSToken offsets, rather than line
- * and column.  Also, this function lexes the entire input string at
- * once, rather than lazily yielding a token stream.  Use
- * |cssTokenizer| or |DOMUtils.getCSSLexer| instead.
- *
- * @param{String} string The input string.
- * @return {Array} An array of tokens (@see CSSToken) that have
- *        line and column information.
- */
-function cssTokenizerWithLineColumn(string) {
-  let lexer = DOMUtils.getCSSLexer(string);
-  let result = [];
-  let prevToken = undefined;
-  while (true) {
-    let token = lexer.nextToken();
-    let lineNumber = lexer.lineNumber;
-    let columnNumber = lexer.columnNumber;
-
-    if (prevToken) {
-      prevToken.loc.end = {
-        line: lineNumber,
-        column: columnNumber
-      };
-    }
-
-    if (!token) {
-      break;
-    }
-
-    if (token.tokenType === "comment") {
-      // We've already dealt with the previous token's location.
-      prevToken = undefined;
-    } else {
-      let startLoc = {
-        line: lineNumber,
-        column: columnNumber
-      };
-      token.loc = {start: startLoc};
-
-      result.push(token);
-      prevToken = token;
-    }
-  }
-
-  return result;
-}
-
-exports.cssTokenizerWithLineColumn = cssTokenizerWithLineColumn;
--- a/devtools/client/sourceeditor/moz.build
+++ b/devtools/client/sourceeditor/moz.build
@@ -2,14 +2,13 @@
 # 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/.
 
 DevToolsModules(
     'autocomplete.js',
     'css-autocompleter.js',
-    'css-tokenizer.js',
     'debugger.js',
     'editor.js'
 )
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/devtools/client/styleinspector/moz.build
+++ b/devtools/client/styleinspector/moz.build
@@ -1,18 +1,16 @@
 # -*- 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/.
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
-XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 
 DevToolsModules(
     'computed-view.js',
-    'css-parsing-utils.js',
     'rule-view.js',
     'style-inspector-menu.js',
     'style-inspector-overlays.js',
     'style-inspector.js',
     'utils.js',
 )
--- a/devtools/client/styleinspector/rule-view.js
+++ b/devtools/client/styleinspector/rule-view.js
@@ -28,17 +28,17 @@ const {
 const {
   escapeCSSComment,
   parseDeclarations,
   parseSingleValue,
   parsePseudoClassesAndAttributes,
   SELECTOR_ATTRIBUTE,
   SELECTOR_ELEMENT,
   SELECTOR_PSEUDO_CLASS
-} = require("devtools/client/styleinspector/css-parsing-utils");
+} = require("devtools/client/shared/css-parsing-utils");
 loader.lazyRequireGetter(this, "overlays",
   "devtools/client/styleinspector/style-inspector-overlays");
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/shared/event-emitter");
 loader.lazyRequireGetter(this, "StyleInspectorMenu",
   "devtools/client/styleinspector/style-inspector-menu");
 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
 
deleted file mode 100644
--- a/devtools/client/styleinspector/test/unit/.eslintrc
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  // Extend from the common devtools xpcshell eslintrc config.
-  "extends": "../../../../.eslintrc.xpcshell"
-}
\ No newline at end of file
deleted file mode 100644
--- a/devtools/client/styleinspector/test/unit/test_escapeCSSComment.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var Cu = Components.utils;
-Cu.import("resource://gre/modules/devtools/Loader.jsm");
-const {escapeCSSComment, _unescapeCSSComment} =
-      devtools.require("devtools/client/styleinspector/css-parsing-utils");
-
-const TEST_DATA = [
-  {
-    input: "simple",
-    expected: "simple"
-  },
-  {
-    input: "/* comment */",
-    expected: "/\\* comment *\\/"
-  },
-  {
-    input: "/* two *//* comments */",
-    expected: "/\\* two *\\//\\* comments *\\/"
-  },
-  {
-    input: "/* nested /\\* comment *\\/ */",
-    expected: "/\\* nested /\\\\* comment *\\\\/ *\\/",
-  }
-];
-
-function run_test() {
-  let i = 0;
-  for (let test of TEST_DATA) {
-    ++i;
-    do_print("Test #" + i);
-
-    let escaped = escapeCSSComment(test.input);
-    equal(escaped, test.expected);
-    let unescaped = _unescapeCSSComment(escaped);
-    equal(unescaped, test.input);
-  }
-}
deleted file mode 100644
--- a/devtools/client/styleinspector/test/unit/test_parseDeclarations.js
+++ /dev/null
@@ -1,425 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var Cu = Components.utils;
-const {require} = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
-const {parseDeclarations, _parseCommentDeclarations} =
-  require("devtools/client/styleinspector/css-parsing-utils");
-
-const TEST_DATA = [
-  // Simple test
-  {
-    input: "p:v;",
-    expected: [{name: "p", value: "v", priority: "", offsets: [0, 4]}]
-  },
-  // Simple test
-  {
-    input: "this:is;a:test;",
-    expected: [
-      {name: "this", value: "is", priority: "", offsets: [0, 8]},
-      {name: "a", value: "test", priority: "", offsets: [8, 15]}
-    ]
-  },
-  // Test a single declaration with semi-colon
-  {
-    input: "name:value;",
-    expected: [{name: "name", value: "value", priority: "", offsets: [0, 11]}]
-  },
-  // Test a single declaration without semi-colon
-  {
-    input: "name:value",
-    expected: [{name: "name", value: "value", priority: "", offsets: [0, 10]}]
-  },
-  // Test multiple declarations separated by whitespaces and carriage
-  // returns and tabs
-  {
-    input: "p1 : v1 ; \t\t  \n p2:v2;   \n\n\n\n\t  p3    :   v3;",
-    expected: [
-      {name: "p1", value: "v1", priority: "", offsets: [0, 9]},
-      {name: "p2", value: "v2", priority: "", offsets: [16, 22]},
-      {name: "p3", value: "v3", priority: "", offsets: [32, 45]},
-    ]
-  },
-  // Test simple priority
-  {
-    input: "p1: v1; p2: v2 !important;",
-    expected: [
-      {name: "p1", value: "v1", priority: "", offsets: [0, 7]},
-      {name: "p2", value: "v2", priority: "important", offsets: [8, 26]}
-    ]
-  },
-  // Test simple priority
-  {
-    input: "p1: v1 !important; p2: v2",
-    expected: [
-      {name: "p1", value: "v1", priority: "important", offsets: [0, 18]},
-      {name: "p2", value: "v2", priority: "", offsets: [19, 25]}
-    ]
-  },
-  // Test simple priority
-  {
-    input: "p1: v1 !  important; p2: v2 ! important;",
-    expected: [
-      {name: "p1", value: "v1", priority: "important", offsets: [0, 20]},
-      {name: "p2", value: "v2", priority: "important", offsets: [21, 40]}
-    ]
-  },
-  // Test invalid priority
-  {
-    input: "p1: v1 important;",
-    expected: [
-      {name: "p1", value: "v1 important", priority: "", offsets: [0, 17]}
-    ]
-  },
-  // Test various types of background-image urls
-  {
-    input: "background-image: url(../../relative/image.png)",
-    expected: [{
-      name: "background-image",
-      value: "url(../../relative/image.png)",
-      priority: "",
-      offsets: [0, 47]
-    }]
-  },
-  {
-    input: "background-image: url(http://site.com/test.png)",
-    expected: [{
-      name: "background-image",
-      value: "url(http://site.com/test.png)",
-      priority: "",
-      offsets: [0, 47]
-    }]
-  },
-  {
-    input: "background-image: url(wow.gif)",
-    expected: [{
-      name: "background-image",
-      value: "url(wow.gif)",
-      priority: "",
-      offsets: [0, 30]
-    }]
-  },
-  // Test that urls with :;{} characters in them are parsed correctly
-  {
-    input: "background: red url(\"http://site.com/image{}:;.png?id=4#wat\") "
-      + "repeat top right",
-    expected: [{
-      name: "background",
-      value: "red url(\"http://site.com/image{}:;.png?id=4#wat\") " +
-        "repeat top right",
-      priority: "",
-      offsets: [0, 78]
-    }]
-  },
-  // Test that an empty string results in an empty array
-  {input: "", expected: []},
-  // Test that a string comprised only of whitespaces results in an empty array
-  {input: "       \n \n   \n   \n \t  \t\t\t  ", expected: []},
-  // Test that a null input throws an exception
-  {input: null, throws: true},
-  // Test that a undefined input throws an exception
-  {input: undefined, throws: true},
-  // Test that :;{} characters in quoted content are not parsed as multiple
-  // declarations
-  {
-    input: "content: \";color:red;}selector{color:yellow;\"",
-    expected: [{
-      name: "content",
-      value: "\";color:red;}selector{color:yellow;\"",
-      priority: "",
-      offsets: [0, 45]
-    }]
-  },
-  // Test that rules aren't parsed, just declarations. So { and } found after a
-  // property name should be part of the property name, same for values.
-  {
-    input: "body {color:red;} p {color: blue;}",
-    expected: [
-      {name: "body {color", value: "red", priority: "", offsets: [0, 16]},
-      {name: "} p {color", value: "blue", priority: "", offsets: [16, 33]},
-      {name: "}", value: "", priority: "", offsets: [33, 34]}
-    ]
-  },
-  // Test unbalanced : and ;
-  {
-    input: "color :red : font : arial;",
-    expected: [
-      {name: "color", value: "red : font : arial", priority: "",
-       offsets: [0, 26]}
-    ]
-  },
-  {
-    input: "background: red;;;;;",
-    expected: [{name: "background", value: "red", priority: "",
-                offsets: [0, 16]}]
-  },
-  {
-    input: "background:;",
-    expected: [{name: "background", value: "", priority: "",
-                offsets: [0, 12]}]
-  },
-  {input: ";;;;;", expected: []},
-  {input: ":;:;", expected: []},
-  // Test name only
-  {input: "color", expected: [
-    {name: "color", value: "", priority: "", offsets: [0, 5]}
-  ]},
-  // Test trailing name without :
-  {input: "color:blue;font", expected: [
-    {name: "color", value: "blue", priority: "", offsets: [0, 11]},
-    {name: "font", value: "", priority: "", offsets: [11, 15]}
-  ]},
-  // Test trailing name with :
-  {input: "color:blue;font:", expected: [
-    {name: "color", value: "blue", priority: "", offsets: [0, 11]},
-    {name: "font", value: "", priority: "", offsets: [11, 16]}
-  ]},
-  // Test leading value
-  {input: "Arial;color:blue;", expected: [
-    {name: "", value: "Arial", priority: "", offsets: [0, 6]},
-    {name: "color", value: "blue", priority: "", offsets: [6, 17]}
-  ]},
-  // Test hex colors
-  {
-    input: "color: #333",
-    expected: [{name: "color", value: "#333", priority: "", offsets: [0, 11]}]
-  },
-  {
-    input: "color: #456789",
-    expected: [{name: "color", value: "#456789", priority: "",
-                offsets: [0, 14]}]
-  },
-  {
-    input: "wat: #XYZ",
-    expected: [{name: "wat", value: "#XYZ", priority: "", offsets: [0, 9]}]
-  },
-  // Test string/url quotes escaping
-  {
-    input: "content: \"this is a 'string'\"",
-    expected: [{name: "content", value: "\"this is a 'string'\"", priority: "",
-                offsets: [0, 29]}]
-  },
-  {
-    input: 'content: "this is a \\"string\\""',
-    expected: [{
-      name: "content",
-      value: '"this is a \\"string\\""',
-      priority: "",
-      offsets: [0, 31]}]
-  },
-  {
-    input: "content: 'this is a \"string\"'",
-    expected: [{
-      name: "content",
-      value: '\'this is a "string"\'',
-      priority: "",
-      offsets: [0, 29]
-    }]
-  },
-  {
-    input: "content: 'this is a \\'string\\''",
-    expected: [{
-      name: "content",
-      value: "'this is a \\'string\\''",
-      priority: "",
-      offsets: [0, 31],
-    }]
-  },
-  {
-    input: "content: 'this \\' is a \" really strange string'",
-    expected: [{
-      name: "content",
-      value: "'this \\' is a \" really strange string'",
-      priority: "",
-      offsets: [0, 47]
-    }]
-  },
-  {
-    input: "content: \"a not s\\\
-          o very long title\"",
-    expected: [
-      {name: "content", value: '"a not s\\\
-          o very long title"', priority: "", offsets: [0, 46]}
-    ]
-  },
-  // Test calc with nested parentheses
-  {
-    input: "width: calc((100% - 3em) / 2)",
-    expected: [{name: "width", value: "calc((100% - 3em) / 2)", priority: "",
-                offsets: [0, 29]}]
-  },
-
-  // Simple embedded comment test.
-  {
-    parseComments: true,
-    input: "width: 5; /* background: green; */ background: red;",
-    expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
-               {name: "background", value: "green", priority: "",
-                offsets: [13, 31], commentOffsets: [10, 34]},
-               {name: "background", value: "red", priority: "",
-                offsets: [35, 51]}]
-  },
-
-  // Embedded comment where the parsing heuristic fails.
-  {
-    parseComments: true,
-    input: "width: 5; /* background something: green; */ background: red;",
-    expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
-               {name: "background", value: "red", priority: "",
-                offsets: [45, 61]}]
-  },
-
-  // Embedded comment where the parsing heuristic is a bit funny.
-  {
-    parseComments: true,
-    input: "width: 5; /* background: */ background: red;",
-    expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
-               {name: "background", value: "", priority: "",
-                offsets: [13, 24], commentOffsets: [10, 27]},
-               {name: "background", value: "red", priority: "",
-                offsets: [28, 44]}]
-  },
-
-  // Another case where the parsing heuristic says not to bother; note
-  // that there is no ";" in the comment.
-  {
-    parseComments: true,
-    input: "width: 5; /* background: yellow */ background: red;",
-    expected: [{name: "width", value: "5", priority: "", offsets: [0, 9]},
-               {name: "background", value: "yellow", priority: "",
-                offsets: [13, 31], commentOffsets: [10, 34]},
-               {name: "background", value: "red", priority: "",
-                offsets: [35, 51]}]
-  },
-
-  // Parsing a comment should yield text that has been unescaped, and
-  // the offsets should refer to the original text.
-  {
-    parseComments: true,
-    input: "/* content: '*\\/'; */",
-    expected: [{name: "content", value: "'*/'", priority: "",
-                offsets: [3, 18], commentOffsets: [0, 21]}]
-  },
-
-  // Parsing a comment should yield text that has been unescaped, and
-  // the offsets should refer to the original text.  This variant
-  // tests the no-semicolon path.
-  {
-    parseComments: true,
-    input: "/* content: '*\\/' */",
-    expected: [{name: "content", value: "'*/'", priority: "",
-                offsets: [3, 17], commentOffsets: [0, 20]}]
-  },
-
-  // A comment-in-a-comment should yield the correct offsets.
-  {
-    parseComments: true,
-    input: "/* color: /\\* comment *\\/ red; */",
-    expected: [{name: "color", value: "red", priority: "",
-                offsets: [3, 30], commentOffsets: [0, 33]}]
-  },
-
-  // HTML comments are ignored.
-  {
-    parseComments: true,
-    input: "<!-- color: red; --> color: blue;",
-    expected: [{name: "color", value: "red", priority: "",
-                offsets: [5, 16]},
-               {name: "color", value: "blue", priority: "",
-                offsets: [21, 33]}]
-  },
-
-  // Don't error on an empty comment.
-  {
-    parseComments: true,
-    input: "/**/",
-    expected: []
-  },
-
-  // Parsing our special comments skips the name-check heuristic.
-  {
-    parseComments: true,
-    input: "/*! walrus: zebra; */",
-    expected: [{name: "walrus", value: "zebra", priority: "",
-                offsets: [4, 18], commentOffsets: [0, 21]}]
-  }
-];
-
-function run_test() {
-  run_basic_tests();
-  run_comment_tests();
-}
-
-// Test parseDeclarations.
-function run_basic_tests() {
-  for (let test of TEST_DATA) {
-    do_print("Test input string " + test.input);
-    let output;
-    try {
-      output = parseDeclarations(test.input, test.parseComments);
-    } catch (e) {
-      do_print("parseDeclarations threw an exception with the given input " +
-        "string");
-      if (test.throws) {
-        do_print("Exception expected");
-        do_check_true(true);
-      } else {
-        do_print("Exception unexpected\n" + e);
-        do_check_true(false);
-      }
-    }
-    if (output) {
-      assertOutput(output, test.expected);
-    }
-  }
-}
-
-const COMMENT_DATA = [
-  {
-    input: "content: 'hi",
-    expected: [{name: "content", value: "'hi", priority: "", terminator: "';",
-                offsets: [2, 14], colonOffsets: [9, 11],
-                commentOffsets: [0, 16]}],
-  },
-  {
-    input: "text that once confounded the parser;",
-    expected: []
-  },
-];
-
-// Test parseCommentDeclarations.
-function run_comment_tests() {
-  for (let test of COMMENT_DATA) {
-    do_print("Test input string " + test.input);
-    let output = _parseCommentDeclarations(test.input, 0,
-                                           test.input.length + 4);
-    deepEqual(output, test.expected);
-  }
-}
-
-function assertOutput(actual, expected) {
-  if (actual.length === expected.length) {
-    for (let i = 0; i < expected.length; i++) {
-      do_check_true(!!actual[i]);
-      do_print("Check that the output item has the expected name, " +
-        "value and priority");
-      do_check_eq(expected[i].name, actual[i].name);
-      do_check_eq(expected[i].value, actual[i].value);
-      do_check_eq(expected[i].priority, actual[i].priority);
-      deepEqual(expected[i].offsets, actual[i].offsets);
-      if ("commentOffsets" in expected[i]) {
-        deepEqual(expected[i].commentOffsets, actual[i].commentOffsets);
-      }
-    }
-  } else {
-    for (let prop of actual) {
-      do_print("Actual output contained: {name: " + prop.name + ", value: " +
-        prop.value + ", priority: " + prop.priority + "}");
-    }
-    do_check_eq(actual.length, expected.length);
-  }
-}
deleted file mode 100644
--- a/devtools/client/styleinspector/test/unit/test_parsePseudoClassesAndAttributes.js
+++ /dev/null
@@ -1,213 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var Cu = Components.utils;
-const {require} = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
-const {
-  parsePseudoClassesAndAttributes,
-  SELECTOR_ATTRIBUTE,
-  SELECTOR_ELEMENT,
-  SELECTOR_PSEUDO_CLASS
-} = require("devtools/client/styleinspector/css-parsing-utils");
-
-const TEST_DATA = [
-  // Test that a null input throws an exception
-  {
-    input: null,
-    throws: true
-  },
-  // Test that a undefined input throws an exception
-  {
-    input: undefined,
-    throws: true
-  },
-  {
-    input: ":root",
-    expected: [
-      { value: ":root", type: SELECTOR_PSEUDO_CLASS }
-    ]
-  },
-  {
-    input: ".testclass",
-    expected: [
-      { value: ".testclass", type: SELECTOR_ELEMENT }
-    ]
-  },
-  {
-    input: "div p",
-    expected: [
-      { value: "div p", type: SELECTOR_ELEMENT }
-    ]
-  },
-  {
-    input: "div > p",
-    expected: [
-      { value: "div > p", type: SELECTOR_ELEMENT }
-    ]
-  },
-  {
-    input: "a[hidden]",
-    expected: [
-      { value: "a", type: SELECTOR_ELEMENT },
-      { value: "[hidden]", type: SELECTOR_ATTRIBUTE }
-    ]
-  },
-  {
-    input: "a[hidden=true]",
-    expected: [
-      { value: "a", type: SELECTOR_ELEMENT },
-      { value: "[hidden=true]", type: SELECTOR_ATTRIBUTE }
-    ]
-  },
-  {
-    input: "a[hidden=true] p:hover",
-    expected: [
-      { value: "a", type: SELECTOR_ELEMENT },
-      { value: "[hidden=true]", type: SELECTOR_ATTRIBUTE },
-      { value: " p", type: SELECTOR_ELEMENT },
-      { value: ":hover", type: SELECTOR_PSEUDO_CLASS }
-    ]
-  },
-  {
-    input: "a[checked=\"true\"]",
-    expected: [
-      { value: "a", type: SELECTOR_ELEMENT },
-      { value: "[checked=\"true\"]", type: SELECTOR_ATTRIBUTE }
-    ]
-  },
-  {
-    input: "a[title~=test]",
-    expected: [
-      { value: "a", type: SELECTOR_ELEMENT },
-      { value: "[title~=test]", type: SELECTOR_ATTRIBUTE }
-    ]
-  },
-  {
-    input: "h1[hidden=\"true\"][title^=\"Important\"]",
-    expected: [
-      { value: "h1", type: SELECTOR_ELEMENT },
-      { value: "[hidden=\"true\"]", type: SELECTOR_ATTRIBUTE },
-      { value: "[title^=\"Important\"]", type: SELECTOR_ATTRIBUTE}
-    ]
-  },
-  {
-    input: "p:hover",
-    expected: [
-      { value: "p", type: SELECTOR_ELEMENT },
-      { value: ":hover", type: SELECTOR_PSEUDO_CLASS }
-    ]
-  },
-  {
-    input: "p + .testclass:hover",
-    expected: [
-      { value: "p + .testclass", type: SELECTOR_ELEMENT },
-      { value: ":hover", type: SELECTOR_PSEUDO_CLASS }
-    ]
-  },
-  {
-    input: "p::before",
-    expected: [
-      { value: "p", type: SELECTOR_ELEMENT },
-      { value: "::before", type: SELECTOR_PSEUDO_CLASS }
-    ]
-  },
-  {
-    input: "p:nth-child(2)",
-    expected: [
-      { value: "p", type: SELECTOR_ELEMENT },
-      { value: ":nth-child(2)", type: SELECTOR_PSEUDO_CLASS }
-    ]
-  },
-  {
-    input: "p:not([title=\"test\"]) .testclass",
-    expected: [
-      { value: "p", type: SELECTOR_ELEMENT },
-      { value: ":not([title=\"test\"])", type: SELECTOR_PSEUDO_CLASS },
-      { value: " .testclass", type: SELECTOR_ELEMENT }
-    ]
-  },
-  {
-    input: "a\\:hover",
-    expected: [
-      { value: "a\\:hover", type: SELECTOR_ELEMENT }
-    ]
-  },
-  {
-    input: ":not(:lang(it))",
-    expected: [
-      { value: ":not(:lang(it))", type: SELECTOR_PSEUDO_CLASS }
-    ]
-  },
-  {
-    input: "p:not(:lang(it))",
-    expected: [
-      { value: "p", type: SELECTOR_ELEMENT },
-      { value: ":not(:lang(it))", type: SELECTOR_PSEUDO_CLASS }
-    ]
-  },
-  {
-    input: "p:not(p:lang(it))",
-    expected: [
-      { value: "p", type: SELECTOR_ELEMENT },
-      { value: ":not(p:lang(it))", type: SELECTOR_PSEUDO_CLASS }
-    ]
-  },
-  {
-    input: ":not(:lang(it)",
-    expected: [
-      { value: ":not(:lang(it)", type: SELECTOR_ELEMENT }
-    ]
-  },
-  {
-    input: ":not(:lang(it)))",
-    expected: [
-      { value: ":not(:lang(it))", type: SELECTOR_PSEUDO_CLASS },
-      { value: ")", type: SELECTOR_ELEMENT }
-    ]
-  }
-];
-
-function run_test() {
-  for (let test of TEST_DATA) {
-    dump("Test input string " + test.input + "\n");
-    let output;
-
-    try {
-      output = parsePseudoClassesAndAttributes(test.input);
-    } catch (e) {
-      dump("parsePseudoClassesAndAttributes threw an exception with the " +
-        "given input string\n");
-      if (test.throws) {
-        ok(true, "Exception expected");
-      } else {
-        dump();
-        ok(false, "Exception unexpected\n" + e);
-      }
-    }
-
-    if (output) {
-      assertOutput(output, test.expected);
-    }
-  }
-}
-
-function assertOutput(actual, expected) {
-  if (actual.length === expected.length) {
-    for (let i = 0; i < expected.length; i++) {
-      dump("Check that the output item has the expected value and type\n");
-      ok(!!actual[i]);
-      equal(expected[i].value, actual[i].value);
-      equal(expected[i].type, actual[i].type);
-    }
-  } else {
-    for (let prop of actual) {
-      dump("Actual output contained: {value: " + prop.value + ", type: " +
-        prop.type + "}\n");
-    }
-    equal(actual.length, expected.length);
-  }
-}
deleted file mode 100644
--- a/devtools/client/styleinspector/test/unit/test_parseSingleValue.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var Cu = Components.utils;
-const {require} = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
-const {parseSingleValue} = require("devtools/client/styleinspector/css-parsing-utils");
-
-const TEST_DATA = [
-  {input: null, throws: true},
-  {input: undefined, throws: true},
-  {input: "", expected: {value: "", priority: ""}},
-  {input: "  \t \t \n\n  ", expected: {value: "", priority: ""}},
-  {input: "blue", expected: {value: "blue", priority: ""}},
-  {input: "blue !important", expected: {value: "blue", priority: "important"}},
-  {input: "blue!important", expected: {value: "blue", priority: "important"}},
-  {input: "blue ! important", expected: {value: "blue", priority: "important"}},
-  {
-    input: "blue !  important",
-    expected: {value: "blue", priority: "important"}
-  },
-  {input: "blue !", expected: {value: "blue", priority: ""}},
-  {input: "blue !mportant", expected: {value: "blue !mportant", priority: ""}},
-  {
-    input: "  blue   !important ",
-    expected: {value: "blue", priority: "important"}
-  },
-  {
-    input: "url(\"http://url.com/whyWouldYouDoThat!important.png\") !important",
-    expected: {
-      value: "url(\"http://url.com/whyWouldYouDoThat!important.png\")",
-      priority: "important"
-    }
-  },
-  {
-    input: "url(\"http://url.com/whyWouldYouDoThat!important.png\")",
-    expected: {
-      value: "url(\"http://url.com/whyWouldYouDoThat!important.png\")",
-      priority: ""
-    }
-  },
-  {
-    input: "\"content!important\" !important",
-    expected: {
-      value: "\"content!important\"",
-      priority: "important"
-    }
-  },
-  {
-    input: "\"content!important\"",
-    expected: {
-      value: "\"content!important\"",
-      priority: ""
-    }
-  },
-  {
-    input: "\"all the \\\"'\\\\ special characters\"",
-    expected: {
-      value: "\"all the \\\"'\\\\ special characters\"",
-      priority: ""
-    }
-  }
-];
-
-function run_test() {
-  for (let test of TEST_DATA) {
-    do_print("Test input value " + test.input);
-    try {
-      let output = parseSingleValue(test.input);
-      assertOutput(output, test.expected);
-    } catch (e) {
-      do_print("parseSingleValue threw an exception with the given input " +
-        "value");
-      if (test.throws) {
-        do_print("Exception expected");
-        do_check_true(true);
-      } else {
-        do_print("Exception unexpected\n" + e);
-        do_check_true(false);
-      }
-    }
-  }
-}
-
-function assertOutput(actual, expected) {
-  do_print("Check that the output has the expected value and priority");
-  do_check_eq(expected.value, actual.value);
-  do_check_eq(expected.priority, actual.priority);
-}
deleted file mode 100644
--- a/devtools/client/styleinspector/test/unit/test_rewriteDeclarations.js
+++ /dev/null
@@ -1,478 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var Cu = Components.utils;
-Cu.import("resource://gre/modules/devtools/Loader.jsm");
-const {parseDeclarations, RuleRewriter} =
-      devtools.require("devtools/client/styleinspector/css-parsing-utils");
-
-const TEST_DATA = [
-  {
-    desc: "simple set",
-    input: "p:v;",
-    instruction: {type: "set", name: "p", value: "N", priority: "",
-                  index: 0},
-    expected: "p:N;"
-  },
-  {
-    desc: "simple set clearing !important",
-    input: "p:v !important;",
-    instruction: {type: "set", name: "p", value: "N", priority: "",
-                  index: 0},
-    expected: "p:N;"
-  },
-  {
-    desc: "simple set adding !important",
-    input: "p:v;",
-    instruction: {type: "set", name: "p", value: "N", priority: "important",
-                  index: 0},
-    expected: "p:N !important;"
-  },
-  {
-    desc: "simple set between comments",
-    input: "/*color:red;*/ p:v; /*color:green;*/",
-    instruction: {type: "set", name: "p", value: "N", priority: "",
-                  index: 1},
-    expected: "/*color:red;*/ p:N; /*color:green;*/"
-  },
-  // The rule view can generate a "set" with a previously unknown
-  // property index; which should work like "create".
-  {
-    desc: "set at unknown index",
-    input: "a:b; e: f;",
-    instruction: {type: "set", name: "c", value: "d", priority: "",
-                  index: 2},
-    expected: "a:b; e: f;c: d;"
-  },
-  {
-    desc: "simple rename",
-    input: "p:v;",
-    instruction: {type: "rename", name: "p", newName: "q", index: 0},
-    expected: "q:v;"
-  },
-  // "rename" is passed the name that the user entered, and must do
-  // any escaping necessary to ensure that this is an identifier.
-  {
-    desc: "rename requiring escape",
-    input: "p:v;",
-    instruction: {type: "rename", name: "p", newName: "a b", index: 0},
-    expected: "a\\ b:v;"
-  },
-  {
-    desc: "simple create",
-    input: "",
-    instruction: {type: "create", name: "p", value: "v", priority: "important",
-                  index: 0},
-    expected: "p: v !important;"
-  },
-  {
-    desc: "create between two properties",
-    input: "a:b; e: f;",
-    instruction: {type: "create", name: "c", value: "d", priority: "",
-                  index: 1},
-    expected: "a:b; c: d;e: f;"
-  },
-  // "create" is passed the name that the user entered, and must do
-  // any escaping necessary to ensure that this is an identifier.
-  {
-    desc: "create requiring escape",
-    input: "",
-    instruction: {type: "create", name: "a b", value: "d", priority: "",
-                  index: 1},
-    expected: "a\\ b: d;"
-  },
-  {
-    desc: "simple disable",
-    input: "p:v;",
-    instruction: {type: "enable", name: "p", value: false, index: 0},
-    expected: "/*! p:v; */"
-  },
-  {
-    desc: "simple enable",
-    input: "/* color:v; */",
-    instruction: {type: "enable", name: "color", value: true, index: 0},
-    expected: "color:v;"
-  },
-  {
-    desc: "enable with following property in comment",
-    input: "/* color:red; color: blue; */",
-    instruction: {type: "enable", name: "color", value: true, index: 0},
-    expected: "color:red; /* color: blue; */"
-  },
-  {
-    desc: "enable with preceding property in comment",
-    input: "/* color:red; color: blue; */",
-    instruction: {type: "enable", name: "color", value: true, index: 1},
-    expected: "/* color:red; */ color: blue;"
-  },
-  {
-    desc: "simple remove",
-    input: "a:b;c:d;e:f;",
-    instruction: {type: "remove", name: "c", index: 1},
-    expected: "a:b;e:f;"
-  },
-  {
-    desc: "disable with comment ender in string",
-    input: "content: '*/';",
-    instruction: {type: "enable", name: "content", value: false, index: 0},
-    expected: "/*! content: '*\\/'; */"
-  },
-  {
-    desc: "enable with comment ender in string",
-    input: "/* content: '*\\/'; */",
-    instruction: {type: "enable", name: "content", value: true, index: 0},
-    expected: "content: '*/';"
-  },
-  {
-    desc: "enable requiring semicolon insertion",
-    // Note the lack of a trailing semicolon in the comment.
-    input: "/* color:red */ color: blue;",
-    instruction: {type: "enable", name: "color", value: true, index: 0},
-    expected: "color:red; color: blue;"
-  },
-  {
-    desc: "create requiring semicolon insertion",
-    // Note the lack of a trailing semicolon.
-    input: "color: red",
-    instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 1},
-    expected: "color: red;a: b;"
-  },
-
-  // Newline insertion.
-  {
-    desc: "simple newline insertion",
-    input: "\ncolor: red;\n",
-    instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 1},
-    expected: "\ncolor: red;\na: b;\n"
-  },
-  // Newline insertion.
-  {
-    desc: "semicolon insertion before newline",
-    // Note the lack of a trailing semicolon.
-    input: "\ncolor: red\n",
-    instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 1},
-    expected: "\ncolor: red;\na: b;\n"
-  },
-  // Newline insertion.
-  {
-    desc: "newline and semicolon insertion",
-    // Note the lack of a trailing semicolon and newline.
-    input: "\ncolor: red",
-    instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 1},
-    expected: "\ncolor: red;\na: b;\n"
-  },
-
-  // Newline insertion and indentation.
-  {
-    desc: "indentation with create",
-    input: "\n  color: red;\n",
-    instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 1},
-    expected: "\n  color: red;\n  a: b;\n"
-  },
-  // Newline insertion and indentation.
-  {
-    desc: "indentation plus semicolon insertion before newline",
-    // Note the lack of a trailing semicolon.
-    input: "\n  color: red\n",
-    instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 1},
-    expected: "\n  color: red;\n  a: b;\n"
-  },
-  {
-    desc: "indentation inserted before trailing whitespace",
-    // Note the trailing whitespace.  This could come from a rule
-    // like:
-    // @supports (mumble) {
-    //   body {
-    //     color: red;
-    //   }
-    // }
-    // Here if we create a rule we don't want it to follow
-    // the indentation of the "}".
-    input: "\n    color: red;\n  ",
-    instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 1},
-    expected: "\n    color: red;\n    a: b;\n  "
-  },
-  // Newline insertion and indentation.
-  {
-    desc: "indentation comes from preceding comment",
-    // Note how the comment comes before the declaration.
-    input: "\n  /* comment */ color: red\n",
-    instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 1},
-    expected: "\n  /* comment */ color: red;\n  a: b;\n"
-  },
-  // Default indentation.
-  {
-    desc: "use of default indentation",
-    input: "\n",
-    instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 0},
-    expected: "\n\ta: b;\n"
-  },
-
-  // Deletion handles newlines properly.
-  {
-    desc: "deletion removes newline",
-    input: "a:b;\nc:d;\ne:f;",
-    instruction: {type: "remove", name: "c", index: 1},
-    expected: "a:b;\ne:f;"
-  },
-  // Deletion handles newlines properly.
-  {
-    desc: "deletion remove blank line",
-    input: "\n  a:b;\n  c:d;  \ne:f;",
-    instruction: {type: "remove", name: "c", index: 1},
-    expected: "\n  a:b;\ne:f;"
-  },
-  // Deletion handles newlines properly.
-  {
-    desc: "deletion leaves comment",
-    input: "\n  a:b;\n  /* something */ c:d;  \ne:f;",
-    instruction: {type: "remove", name: "c", index: 1},
-    expected: "\n  a:b;\n  /* something */   \ne:f;"
-  },
-  // Deletion handles newlines properly.
-  {
-    desc: "deletion leaves previous newline",
-    input: "\n  a:b;\n  c:d;  \ne:f;",
-    instruction: {type: "remove", name: "e", index: 2},
-    expected: "\n  a:b;\n  c:d;  \n"
-  },
-  // Deletion handles newlines properly.
-  {
-    desc: "deletion removes trailing whitespace",
-    input: "\n  a:b;\n  c:d;  \n    e:f;",
-    instruction: {type: "remove", name: "e", index: 2},
-    expected: "\n  a:b;\n  c:d;  \n"
-  },
-  // Deletion handles newlines properly.
-  {
-    desc: "deletion preserves indentation",
-    input: "  a:b;\n  c:d;  \n    e:f;",
-    instruction: {type: "remove", name: "a", index: 0},
-    expected: "  c:d;  \n    e:f;"
-  },
-
-  // Termination insertion corner case.
-  {
-    desc: "enable single quote termination",
-    input: "/* content: 'hi */ color: red;",
-    instruction: {type: "enable", name: "content", value: true, index: 0},
-    expected: "content: 'hi'; color: red;"
-  },
-  // Termination insertion corner case.
-  {
-    desc: "create single quote termination",
-    input: "content: 'hi",
-    instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
-    expected: "content: 'hi';color: red;"
-  },
-
-  // Termination insertion corner case.
-  {
-    desc: "enable double quote termination",
-    input: "/* content: \"hi */ color: red;",
-    instruction: {type: "enable", name: "content", value: true, index: 0},
-    expected: "content: \"hi\"; color: red;"
-  },
-  // Termination insertion corner case.
-  {
-    desc: "create double quote termination",
-    input: "content: \"hi",
-    instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
-    expected: "content: \"hi\";color: red;"
-  },
-
-  // Termination insertion corner case.
-  {
-    desc: "enable url termination",
-    input: "/* background-image: url(something.jpg */ color: red;",
-    instruction: {type: "enable", name: "background-image", value: true,
-                  index: 0},
-    expected: "background-image: url(something.jpg); color: red;"
-  },
-  // Termination insertion corner case.
-  {
-    desc: "create url termination",
-    input: "background-image: url(something.jpg",
-    instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
-    expected: "background-image: url(something.jpg);color: red;"
-  },
-
-  // Termination insertion corner case.
-  {
-    desc: "enable url single quote termination",
-    input: "/* background-image: url('something.jpg */ color: red;",
-    instruction: {type: "enable", name: "background-image", value: true,
-                  index: 0},
-    expected: "background-image: url('something.jpg'); color: red;"
-  },
-  // Termination insertion corner case.
-  {
-    desc: "create url single quote termination",
-    input: "background-image: url('something.jpg",
-    instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
-    expected: "background-image: url('something.jpg');color: red;"
-  },
-
-  // Termination insertion corner case.
-  {
-    desc: "create url double quote termination",
-    input: "/* background-image: url(\"something.jpg */ color: red;",
-    instruction: {type: "enable", name: "background-image", value: true,
-                  index: 0},
-    expected: "background-image: url(\"something.jpg\"); color: red;"
-  },
-  // Termination insertion corner case.
-  {
-    desc: "enable url double quote termination",
-    input: "background-image: url(\"something.jpg",
-    instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
-    expected: "background-image: url(\"something.jpg\");color: red;"
-  },
-
-  // Termination insertion corner case.
-  {
-    desc: "create backslash termination",
-    input: "something: \\",
-    instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
-    expected: "something: \\\\;color: red;"
-  },
-
-  // Termination insertion corner case.
-  {
-    desc: "enable backslash single quote termination",
-    input: "something: '\\",
-    instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
-    expected: "something: '\\\\';color: red;"
-  },
-  {
-    desc: "enable backslash double quote termination",
-    input: "something: \"\\",
-    instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
-    expected: "something: \"\\\\\";color: red;"
-  },
-
-  // Termination insertion corner case.
-  {
-    desc: "enable comment termination",
-    input: "something: blah /* comment ",
-    instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
-    expected: "something: blah /* comment*/; color: red;"
-  },
-
-  // Rewrite a "heuristic override" comment.
-  {
-    desc: "enable with heuristic override comment",
-    input: "/*! walrus: zebra; */",
-    instruction: {type: "enable", name: "walrus", value: true, index: 0},
-    expected: "walrus: zebra;"
-  },
-
-  // Sanitize a bad value.
-  {
-    desc: "create sanitize unpaired brace",
-    input: "",
-    instruction: {type: "create", name: "p", value: "}", priority: "",
-                  index: 0},
-    expected: "p: \\};",
-    changed: {0: "\\}"}
-  },
-  // Sanitize a bad value.
-  {
-    desc: "set sanitize unpaired brace",
-    input: "walrus: zebra;",
-    instruction: {type: "set", name: "walrus", value: "{{}}}", priority: "",
-                  index: 0},
-    expected: "walrus: {{}}\\};",
-    changed: {0: "{{}}\\}"}
-  },
-  // Sanitize a bad value.
-  {
-    desc: "enable sanitize unpaired brace",
-    input: "/*! walrus: }*/",
-    instruction: {type: "enable", name: "walrus", value: true, index: 0},
-    expected: "walrus: \\};",
-    changed: {0: "\\}"}
-  },
-
-  // Creating a new declaration does not require an attempt to
-  // terminate a previous commented declaration.
-  {
-    desc: "disabled declaration does not need semicolon insertion",
-    input: "/*! no: semicolon */\n",
-    instruction: {type: "create", name: "walrus", value: "zebra", priority: "",
-                  index: 1},
-    expected: "/*! no: semicolon */\nwalrus: zebra;\n",
-    changed: {}
-  },
-];
-
-function rewriteDeclarations(inputString, instruction, defaultIndentation) {
-  let rewriter = new RuleRewriter(null, inputString);
-  rewriter.defaultIndentation = defaultIndentation;
-
-  switch (instruction.type) {
-    case "rename":
-      rewriter.renameProperty(instruction.index, instruction.name,
-                              instruction.newName);
-      break;
-
-    case "enable":
-      rewriter.setPropertyEnabled(instruction.index, instruction.name,
-                                  instruction.value);
-      break;
-
-    case "create":
-      rewriter.createProperty(instruction.index, instruction.name,
-                              instruction.value, instruction.priority);
-      break;
-
-    case "set":
-      rewriter.setProperty(instruction.index, instruction.name,
-                           instruction.value, instruction.priority);
-      break;
-
-    case "remove":
-      rewriter.removeProperty(instruction.index, instruction.name);
-      break;
-
-    default:
-      throw new Error("unrecognized instruction");
-  }
-
-  return rewriter.getResult();
-}
-
-function run_test() {
-  let i = 0;
-  for (let test of TEST_DATA) {
-    ++i;
-    let {changed, text} = rewriteDeclarations(test.input, test.instruction,
-                                              "\t");
-    equal(text, test.expected, "output for " + test.desc);
-    if ("changed" in test) {
-      deepEqual(changed, test.changed, "changed result for " + test.desc);
-    }
-  }
-}
deleted file mode 100644
--- a/devtools/client/styleinspector/test/unit/xpcshell.ini
+++ /dev/null
@@ -1,12 +0,0 @@
-[DEFAULT]
-tags = devtools
-head =
-tail =
-firefox-appdir = browser
-skip-if = toolkit == 'android' || toolkit == 'gonk'
-
-[test_escapeCSSComment.js]
-[test_parseDeclarations.js]
-[test_parsePseudoClassesAndAttributes.js]
-[test_parseSingleValue.js]
-[test_rewriteDeclarations.js]
--- a/devtools/client/styleinspector/utils.js
+++ b/devtools/client/styleinspector/utils.js
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 const {setTimeout, clearTimeout} =
       Cu.import("resource://gre/modules/Timer.jsm", {});
 const {parseDeclarations} =
-      require("devtools/client/styleinspector/css-parsing-utils");
+      require("devtools/client/shared/css-parsing-utils");
 const promise = require("promise");
 
 loader.lazyServiceGetter(this, "domUtils",
   "@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 /**
--- a/devtools/client/themes/dark-theme.css
+++ b/devtools/client/themes/dark-theme.css
@@ -1,54 +1,14 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
-/* Colors are taken from:
- * https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors.
- * Changes should be kept in sync with commandline.css and commandline.inc.css.
- */
-:root {
-  --theme-body-background: #14171a;
-  --theme-sidebar-background: #181d20;
-  --theme-contrast-background: #b28025;
-
-  --theme-tab-toolbar-background: #252c33;
-  --theme-toolbar-background: #343c45;
-  --theme-selection-background: #1d4f73;
-  --theme-selection-background-semitransparent: rgba(29, 79, 115, .5);
-  --theme-selection-color: #f5f7fa;
-  --theme-splitter-color: black;
-  --theme-comment: #757873;
-
-  --theme-body-color: #8fa1b2;
-  --theme-body-color-alt: #b6babf;
-  --theme-content-color1: #a9bacb;
-  --theme-content-color2: #8fa1b2;
-  --theme-content-color3: #5f7387;
-
-  --theme-highlight-green: #70bf53;
-  --theme-highlight-blue: #46afe3;
-  --theme-highlight-bluegrey: #5e88b0;
-  --theme-highlight-purple: #6b7abb;
-  --theme-highlight-lightorange: #d99b28;
-  --theme-highlight-orange: #d96629;
-  --theme-highlight-red: #eb5368;
-  --theme-highlight-pink: #df80ff;
-
-  /* Colors used in Graphs, like performance tools. Mostly similar to some "highlight-*" colors. */
-  --theme-graphs-green: #70bf53;
-  --theme-graphs-blue: #46afe3;
-  --theme-graphs-bluegrey: #5e88b0;
-  --theme-graphs-purple: #df80ff;
-  --theme-graphs-yellow: #d99b28;
-  --theme-graphs-red: #eb5368;
-  --theme-graphs-grey: #757873;
-}
+@import url(variables.css);
 
 .theme-body {
   background: var(--theme-body-background);
   color: var(--theme-body-color);
 }
 
 .theme-sidebar {
   background: var(--theme-sidebar-background);
--- a/devtools/client/themes/light-theme.css
+++ b/devtools/client/themes/light-theme.css
@@ -1,54 +1,14 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
-/* Colors are taken from:
- * https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors.
- * Changes should be kept in sync with commandline.css and commandline.inc.css.
- */
-:root {
-  --theme-body-background: #fcfcfc;
-  --theme-sidebar-background: #f7f7f7;
-  --theme-contrast-background: #e6b064;
-
-  --theme-tab-toolbar-background: #ebeced;
-  --theme-toolbar-background: #f0f1f2;
-  --theme-selection-background: #4c9ed9;
-  --theme-selection-background-semitransparent: rgba(76, 158, 217, .23);
-  --theme-selection-color: #f5f7fa;
-  --theme-splitter-color: #aaaaaa;
-  --theme-comment: #757873;
-
-  --theme-body-color: #18191a;
-  --theme-body-color-alt: #585959;
-  --theme-content-color1: #292e33;
-  --theme-content-color2: #8fa1b2;
-  --theme-content-color3: #667380;
-
-  --theme-highlight-green: #2cbb0f;
-  --theme-highlight-blue: #0088cc;
-  --theme-highlight-bluegrey: #0072ab;
-  --theme-highlight-purple: #5b5fff;
-  --theme-highlight-lightorange: #d97e00;
-  --theme-highlight-orange: #f13c00;
-  --theme-highlight-red: #ed2655;
-  --theme-highlight-pink: #b82ee5;
-
-  /* Colors used in Graphs, like performance tools. Similar colors to Chrome's timeline. */
-  --theme-graphs-green: #85d175;
-  --theme-graphs-blue: #83b7f6;
-  --theme-graphs-bluegrey: #0072ab;
-  --theme-graphs-purple: #b693eb;
-  --theme-graphs-yellow: #efc052;
-  --theme-graphs-red: #e57180;
-  --theme-graphs-grey: #cccccc;
-}
+@import url(variables.css);
 
 .theme-body {
   background: var(--theme-body-background);
   color: var(--theme-body-color);
 }
 
 .theme-sidebar {
   background: var(--theme-sidebar-background);
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/variables.css
@@ -0,0 +1,91 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+/* Variable declarations for light and dark devtools themes.
+ * Colors are taken from:
+ * https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors.
+ * Changes should be kept in sync with commandline.css and commandline.inc.css.
+ */
+
+/* IMPORTANT NOTE:
+ * This file is parsed in js (see client/shared/theme.js)
+ * so the formatting should be consistent (i.e. no '}' inside a rule).
+ */
+
+:root.theme-light {
+  --theme-body-background: #fcfcfc;
+  --theme-sidebar-background: #f7f7f7;
+  --theme-contrast-background: #e6b064;
+
+  --theme-tab-toolbar-background: #ebeced;
+  --theme-toolbar-background: #f0f1f2;
+  --theme-selection-background: #4c9ed9;
+  --theme-selection-background-semitransparent: rgba(76, 158, 217, .23);
+  --theme-selection-color: #f5f7fa;
+  --theme-splitter-color: #aaaaaa;
+  --theme-comment: #757873;
+
+  --theme-body-color: #18191a;
+  --theme-body-color-alt: #585959;
+  --theme-content-color1: #292e33;
+  --theme-content-color2: #8fa1b2;
+  --theme-content-color3: #667380;
+
+  --theme-highlight-green: #2cbb0f;
+  --theme-highlight-blue: #0088cc;
+  --theme-highlight-bluegrey: #0072ab;
+  --theme-highlight-purple: #5b5fff;
+  --theme-highlight-lightorange: #d97e00;
+  --theme-highlight-orange: #f13c00;
+  --theme-highlight-red: #ed2655;
+  --theme-highlight-pink: #b82ee5;
+
+  /* Colors used in Graphs, like performance tools. Similar colors to Chrome's timeline. */
+  --theme-graphs-green: #85d175;
+  --theme-graphs-blue: #83b7f6;
+  --theme-graphs-bluegrey: #0072ab;
+  --theme-graphs-purple: #b693eb;
+  --theme-graphs-yellow: #efc052;
+  --theme-graphs-red: #e57180;
+  --theme-graphs-grey: #cccccc;
+}
+
+:root.theme-dark {
+  --theme-body-background: #14171a;
+  --theme-sidebar-background: #181d20;
+  --theme-contrast-background: #b28025;
+
+  --theme-tab-toolbar-background: #252c33;
+  --theme-toolbar-background: #343c45;
+  --theme-selection-background: #1d4f73;
+  --theme-selection-background-semitransparent: rgba(29, 79, 115, .5);
+  --theme-selection-color: #f5f7fa;
+  --theme-splitter-color: black;
+  --theme-comment: #757873;
+
+  --theme-body-color: #8fa1b2;
+  --theme-body-color-alt: #b6babf;
+  --theme-content-color1: #a9bacb;
+  --theme-content-color2: #8fa1b2;
+  --theme-content-color3: #5f7387;
+
+  --theme-highlight-green: #70bf53;
+  --theme-highlight-blue: #46afe3;
+  --theme-highlight-bluegrey: #5e88b0;
+  --theme-highlight-purple: #6b7abb;
+  --theme-highlight-lightorange: #d99b28;
+  --theme-highlight-orange: #d96629;
+  --theme-highlight-red: #eb5368;
+  --theme-highlight-pink: #df80ff;
+
+  /* Colors used in Graphs, like performance tools. Mostly similar to some "highlight-*" colors. */
+  --theme-graphs-green: #70bf53;
+  --theme-graphs-blue: #46afe3;
+  --theme-graphs-bluegrey: #5e88b0;
+  --theme-graphs-purple: #df80ff;
+  --theme-graphs-yellow: #d99b28;
+  --theme-graphs-red: #eb5368;
+  --theme-graphs-grey: #757873;
+}
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -2657,17 +2657,33 @@ var WalkerActor = protocol.ActorClass({
    */
   insertBefore: method(function(node, parent, sibling) {
     if (isNodeDead(node) ||
         isNodeDead(parent) ||
         (sibling && isNodeDead(sibling))) {
       return null;
     }
 
-    parent.rawNode.insertBefore(node.rawNode, sibling ? sibling.rawNode : null);
+    let rawNode = node.rawNode;
+    let rawParent = parent.rawNode;
+    let rawSibling = sibling ? sibling.rawNode : null;
+
+    // Don't bother inserting a node if the document position isn't going
+    // to change. This prevents needless iframes reloading and mutations.
+    if (rawNode.parentNode === rawParent) {
+      let currentNextSibling = this.nextSibling(node);
+      currentNextSibling = currentNextSibling ? currentNextSibling.rawNode :
+                                                null;
+
+      if (rawNode === rawSibling || currentNextSibling === rawSibling) {
+        return;
+      }
+    }
+
+    rawParent.insertBefore(rawNode, rawSibling);
   }, {
     request: {
       node: Arg(0, "domnode"),
       parent: Arg(1, "domnode"),
       sibling: Arg(2, "nullable:domnode")
     },
     response: {}
   }),
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -21,17 +21,17 @@ const {UPDATE_PRESERVING_RULES, UPDATE_G
 loader.lazyGetter(this, "CssLogic", () => {
   return require("devtools/shared/styleinspector/css-logic").CssLogic;
 });
 loader.lazyGetter(this, "DOMUtils", () => {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
 
 loader.lazyGetter(this, "RuleRewriter", () => {
-  return require("devtools/client/styleinspector/css-parsing-utils").RuleRewriter;
+  return require("devtools/client/shared/css-parsing-utils").RuleRewriter;
 });
 
 // The PageStyle actor flattens the DOM CSS objects a little bit, merging
 // Rules and their Styles into one actor.  For elements (which have a style
 // but no associated rule) we fake a rule with the following style id.
 const ELEMENT_STYLE = 100;
 exports.ELEMENT_STYLE = ELEMENT_STYLE;
 
--- a/devtools/server/tests/mochitest/test_inspector-insert.html
+++ b/devtools/server/tests/mochitest/test_inspector-insert.html
@@ -34,55 +34,81 @@ addTest(function setup() {
     promiseDone(inspector.getWalker().then(walker => {
       ok(walker, "getWalker() should return an actor.");
       gClient = client;
       gWalker = walker;
     }).then(runNextTest));
   });
 });
 
-addTest(function testRearrange() {
-  let longlist = null;
-  let nodeA = null;
-  let nextNode = null;
+addAsyncTest(function* testRearrange() {
+  let longlist = yield gWalker.querySelector(gWalker.rootNode, "#longlist");
+  let children = yield gWalker.children(longlist);
+  let nodeA = children.nodes[0];
+  is(nodeA.id, "a", "Got the expected node.");
+
+  // Move nodeA to the end of the list.
+  yield gWalker.insertBefore(nodeA, longlist, null);
+  ok(!gInspectee.querySelector("#a").nextSibling, "a should now be at the end of the list.");
+  children = yield gWalker.children(longlist);
+  is(nodeA, children.nodes[children.nodes.length - 1], "a should now be the last returned child.");
+
+  // Now move it to the middle of the list.
+  let nextNode = children.nodes[13];
+  yield gWalker.insertBefore(nodeA, longlist, nextNode);
+  let sibling =
+    new inspector._documentWalker(gInspectee.querySelector("#a"), window).nextSibling();
+  is(sibling, nextNode.rawNode(), "Node should match the expected next node.");
+  children = yield gWalker.children(longlist);
+  is(nodeA, children.nodes[13], "a should be where we expect it.");
+  is(nextNode, children.nodes[14], "next node should be where we expect it.");
+
+  runNextTest();
+});
 
-  promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(listFront => {
-    longlist = listFront;
-  }).then(() => {
-    return gWalker.children(longlist);
-  }).then(response => {
-    nodeA = response.nodes[0];
-    is(nodeA.id, "a", "Got the expected node.");
-    // Move nodeA to the end of the list.
-    return gWalker.insertBefore(nodeA, longlist, null);
-  }).then(() => {
-    ok(!gInspectee.querySelector("#a").nextSibling, "a should now be at the end of the list.");
-    return gWalker.children(longlist);
-  }).then(response => {
-    is(nodeA, response.nodes[response.nodes.length - 1], "a should now be the last returned child.");
-    // Now move it to the middle of the list.
-    nextNode = response.nodes[13];
-    return gWalker.insertBefore(nodeA, longlist, nextNode);
-  }).then(response => {
-    let sibling = new inspector._documentWalker(gInspectee.querySelector("#a"), window).nextSibling();
-    is(sibling, nextNode.rawNode(), "Node should match the expected next node.");
-    return gWalker.children(longlist);
-  }).then(response => {
-    is(nodeA, response.nodes[13], "a should be where we expect it.");
-    is(nextNode, response.nodes[14], "next node should be where we expect it.");
-  }).then(runNextTest));
+addAsyncTest(function* testInsertInvalidInput() {
+  let longlist = yield gWalker.querySelector(gWalker.rootNode, "#longlist");
+  let children = yield gWalker.children(longlist);
+  let nodeA = children.nodes[0];
+  let nextSibling = children.nodes[1];
+
+  // Now move it to the original location and make sure no mutation happens.
+  let hasMutated = false;
+  let observer = new gInspectee.defaultView.MutationObserver(() => {
+    hasMutated = true;
+  });
+  observer.observe(longlist.rawNode(), {
+    childList: true,
+  });
+
+  yield gWalker.insertBefore(nodeA, longlist, nodeA);
+  ok(!hasMutated, "hasn't mutated");
+  hasMutated = false;
+
+  yield gWalker.insertBefore(nodeA, longlist, nextSibling);
+  ok(!hasMutated, "still hasn't mutated after inserting before nextSibling");
+  hasMutated = false;
+
+  yield gWalker.insertBefore(nodeA, longlist);
+  ok(hasMutated, "has mutated after inserting with null sibling");
+  hasMutated = false;
+
+  yield gWalker.insertBefore(nodeA, longlist);
+  ok(!hasMutated, "hasn't mutated after inserting with null sibling again");
+
+  observer.disconnect();
+  runNextTest();
 });
 
 addTest(function cleanup() {
   delete gWalker;
   delete gClient;
   runNextTest();
 });
 
-
   </script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
 <a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
--- a/dom/mobilemessage/android/MobileMessageDatabaseService.cpp
+++ b/dom/mobilemessage/android/MobileMessageDatabaseService.cpp
@@ -1,23 +1,30 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=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/. */
 
 #include "MobileMessageDatabaseService.h"
+
 #include "AndroidBridge.h"
+#include "SmsManager.h"
 
 namespace mozilla {
 namespace dom {
 namespace mobilemessage {
 
 NS_IMPL_ISUPPORTS(MobileMessageDatabaseService, nsIMobileMessageDatabaseService)
 
+MobileMessageDatabaseService::MobileMessageDatabaseService()
+{
+  SmsManager::Init();
+}
+
 NS_IMETHODIMP
 MobileMessageDatabaseService::GetMessageMoz(int32_t aMessageId,
                                             nsIMobileMessageCallback* aRequest)
 {
   if (!AndroidBridge::Bridge()) {
     return NS_OK;
   }
 
@@ -55,34 +62,61 @@ MobileMessageDatabaseService::CreateMess
                                                   uint32_t aNumbersCount,
                                                   const nsAString& aDelivery,
                                                   bool aHasRead,
                                                   bool aRead,
                                                   bool aHasThreadId,
                                                   uint64_t aThreadId,
                                                   bool aReverse,
                                                   nsIMobileMessageCursorCallback* aCallback,
-                                                  nsICursorContinueCallback** aResult)
+                                                  nsICursorContinueCallback** aCursor)
 {
-  return NS_ERROR_NOT_IMPLEMENTED;
+  if (!AndroidBridge::Bridge()) {
+    *aCursor = nullptr;
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsICursorContinueCallback> cursor =
+    AndroidBridge::Bridge()->CreateMessageCursor(aHasStartDate,
+                                                 aStartDate,
+                                                 aHasEndDate,
+                                                 aEndDate,
+                                                 aNumbers,
+                                                 aNumbersCount,
+                                                 aDelivery,
+                                                 aHasRead,
+                                                 aRead,
+                                                 aHasThreadId,
+                                                 aThreadId,
+                                                 aReverse,
+                                                 aCallback);
+  cursor.forget(aCursor);
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 MobileMessageDatabaseService::MarkMessageRead(int32_t aMessageId,
                                               bool aValue,
                                               bool aSendReadReport,
                                               nsIMobileMessageCallback* aRequest)
 {
   // TODO: This would need to be implemented as part of Bug 748391
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MobileMessageDatabaseService::CreateThreadCursor(nsIMobileMessageCursorCallback* aCallback,
-                                                 nsICursorContinueCallback** aResult)
+MobileMessageDatabaseService::CreateThreadCursor(nsIMobileMessageCursorCallback* aRequest,
+                                                 nsICursorContinueCallback** aCursor)
 {
-  NS_NOTYETIMPLEMENTED("Implement me!");
-  return NS_ERROR_NOT_IMPLEMENTED;
+  if (!AndroidBridge::Bridge()) {
+    *aCursor = nullptr;
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsICursorContinueCallback> cursor =
+    AndroidBridge::Bridge()->CreateThreadCursor(aRequest);
+  cursor.forget(aCursor);
+  return NS_OK;
 }
 
 } // namespace mobilemessage
 } // namespace dom
 } // namespace mozilla
--- a/dom/mobilemessage/android/MobileMessageDatabaseService.h
+++ b/dom/mobilemessage/android/MobileMessageDatabaseService.h
@@ -15,16 +15,18 @@ namespace dom {
 namespace mobilemessage {
 
 class MobileMessageDatabaseService final : public nsIMobileMessageDatabaseService
 {
 private:
   ~MobileMessageDatabaseService() {}
 
 public:
+  MobileMessageDatabaseService();
+
   NS_DECL_ISUPPORTS
   NS_DECL_NSIMOBILEMESSAGEDATABASESERVICE
 };
 
 } // namespace mobilemessage
 } // namespace dom
 } // namespace mozilla
 
new file mode 100644
--- /dev/null
+++ b/dom/mobilemessage/android/SmsManager.cpp
@@ -0,0 +1,397 @@
+/* -*- Mode: c++; 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/. */
+
+#include "SmsManager.h"
+
+#include "mozilla/dom/mobilemessage/Constants.h"
+#include "mozilla/dom/mobilemessage/PSms.h"
+#include "mozilla/dom/mobilemessage/SmsParent.h"
+#include "mozilla/dom/mobilemessage/SmsTypes.h"
+#include "mozilla/dom/mobilemessage/Types.h"
+#include "mozilla/dom/MobileMessageThread.h"
+#include "mozilla/dom/SmsMessage.h"
+#include "mozilla/Services.h"
+#include "nsIMobileMessageDatabaseService.h"
+#include "nsIObserverService.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::dom::mobilemessage;
+
+namespace mozilla {
+
+/*static*/
+void
+SmsManager::NotifySmsReceived(jni::String::Param aSender,
+                              jni::String::Param aBody,
+                              int32_t aMessageClass,
+                              int64_t aTimestamp)
+{
+    // TODO Need to correct the message `threadId` parameter value. Bug 859098
+    SmsMessageData message;
+    message.id() = 0;
+    message.threadId() = 0;
+    message.iccId() = EmptyString();
+    message.delivery() = eDeliveryState_Received;
+    message.deliveryStatus() = eDeliveryStatus_Success;
+    message.sender() = aSender ? nsString(aSender) : EmptyString();
+    message.receiver() = EmptyString();
+    message.body() = aBody ? nsString(aBody) : EmptyString();
+    message.messageClass() = static_cast<MessageClass>(aMessageClass);
+    message.timestamp() = aTimestamp;
+    message.sentTimestamp() = aTimestamp;
+    message.deliveryTimestamp() = aTimestamp;
+    message.read() = false;
+
+    nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=] () {
+        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+        if (!obs) {
+            return;
+        }
+
+        nsCOMPtr<nsIDOMMozSmsMessage> domMessage = new SmsMessage(message);
+        obs->NotifyObservers(domMessage, kSmsReceivedObserverTopic, nullptr);
+    });
+    NS_DispatchToMainThread(runnable);
+}
+
+/*static*/
+void
+SmsManager::NotifySmsSent(int32_t aId,
+                          jni::String::Param aReceiver,
+                          jni::String::Param aBody,
+                          int64_t aTimestamp,
+                          int32_t aRequestId)
+{
+    // TODO Need to add the message `messageClass` parameter value. Bug 804476
+    // TODO Need to correct the message `threadId` parameter value. Bug 859098
+    SmsMessageData message;
+    message.id() = aId;
+    message.threadId() = 0;
+    message.iccId() = EmptyString();
+    message.delivery() = eDeliveryState_Sent;
+    message.deliveryStatus() = eDeliveryStatus_Pending;
+    message.sender() = EmptyString();
+    message.receiver() = aReceiver ? nsString(aReceiver) : EmptyString();
+    message.body() = aBody ? nsString(aBody) : EmptyString();
+    message.messageClass() = eMessageClass_Normal;
+    message.timestamp() = aTimestamp;
+    message.sentTimestamp() = aTimestamp;
+    message.deliveryTimestamp() = aTimestamp;
+    message.read() = true;
+
+    nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
+        /*
+         * First, we are going to notify all SmsManager that a message has
+         * been sent. Then, we will notify the SmsRequest object about it.
+         */
+        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+        if (!obs) {
+            return;
+        }
+
+        nsCOMPtr<nsIDOMMozSmsMessage> domMessage = new SmsMessage(message);
+        obs->NotifyObservers(domMessage, kSmsSentObserverTopic, nullptr);
+
+        nsCOMPtr<nsIMobileMessageCallback> request =
+            AndroidBridge::Bridge()->DequeueSmsRequest(aRequestId);
+        if (!request) {
+            return;
+        }
+
+        request->NotifyMessageSent(domMessage);
+    });
+    NS_DispatchToMainThread(runnable);
+}
+
+/*static*/
+void
+SmsManager::NotifySmsDelivery(int32_t aId,
+                              int32_t aDeliveryStatus,
+                              jni::String::Param aReceiver,
+                              jni::String::Param aBody,
+                              int64_t aTimestamp)
+{
+    // TODO Need to add the message `messageClass` parameter value. Bug 804476
+    // TODO Need to correct the message `threadId` parameter value. Bug 859098
+    SmsMessageData message;
+    message.id() = aId;
+    message.threadId() = 0;
+    message.iccId() = EmptyString();
+    message.delivery() = eDeliveryState_Sent;
+    message.deliveryStatus() = static_cast<DeliveryStatus>(aDeliveryStatus);
+    message.sender() = EmptyString();
+    message.receiver() = aReceiver ? nsString(aReceiver) : EmptyString();
+    message.body() = aBody ? nsString(aBody) : EmptyString();
+    message.messageClass() = eMessageClass_Normal;
+    message.timestamp() = aTimestamp;
+    message.sentTimestamp() = aTimestamp;
+    message.deliveryTimestamp() = aTimestamp;
+    message.read() = true;
+
+    nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
+        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+        if (!obs) {
+            return;
+        }
+
+        nsCOMPtr<nsIDOMMozSmsMessage> domMessage = new SmsMessage(message);
+        const char* topic = (message.deliveryStatus() == eDeliveryStatus_Success)
+                            ? kSmsDeliverySuccessObserverTopic
+                            : kSmsDeliveryErrorObserverTopic;
+        obs->NotifyObservers(domMessage, topic, nullptr);
+    });
+    NS_DispatchToMainThread(runnable);
+}
+
+/*static*/
+void
+SmsManager::NotifySmsSendFailed(int32_t aError, int32_t aRequestId)
+{
+    nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
+        nsCOMPtr<nsIMobileMessageCallback> request =
+            AndroidBridge::Bridge()->DequeueSmsRequest(aRequestId);
+        if(!request) {
+            return;
+        }
+
+        request->NotifySendMessageFailed(aError, nullptr);
+    });
+    NS_DispatchToMainThread(runnable);
+}
+
+/*static*/
+void
+SmsManager::NotifyGetSms(int32_t aId,
+                         int32_t aDeliveryStatus,
+                         jni::String::Param aReceiver,
+                         jni::String::Param aSender,
+                         jni::String::Param aBody,
+                         int64_t aTimestamp,
+                         bool aRead,
+                         int32_t aRequestId)
+{
+    nsString receiver(aReceiver);
+    DeliveryState state = receiver.IsEmpty() ? eDeliveryState_Received
+                                             : eDeliveryState_Sent;
+
+    // TODO Need to add the message `messageClass` parameter value. Bug 804476
+    // TODO Need to correct the message `threadId` parameter value. Bug 859098
+    SmsMessageData message;
+    message.id() = aId;
+    message.threadId() = 0;
+    message.iccId() = EmptyString();
+    message.delivery() = state;
+    message.deliveryStatus() = static_cast<DeliveryStatus>(aDeliveryStatus);
+    message.sender() = aSender ? nsString(aSender) : EmptyString();
+    message.receiver() = receiver;
+    message.body() = aBody ? nsString(aBody) : EmptyString();
+    message.messageClass() = eMessageClass_Normal;
+    message.timestamp() = aTimestamp;
+    message.sentTimestamp() = aTimestamp;
+    message.deliveryTimestamp() = aTimestamp;
+    message.read() = aRead;
+
+    nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
+        nsCOMPtr<nsIMobileMessageCallback> request =
+            AndroidBridge::Bridge()->DequeueSmsRequest(aRequestId);
+        if (!request) {
+            return;
+        }
+
+        nsCOMPtr<nsIDOMMozSmsMessage> domMessage = new SmsMessage(message);
+        request->NotifyMessageGot(domMessage);
+    });
+    NS_DispatchToMainThread(runnable);
+}
+
+/*static*/
+void
+SmsManager::NotifyGetSmsFailed(int32_t aError, int32_t aRequestId)
+{
+    nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
+        nsCOMPtr<nsIMobileMessageCallback> request =
+            AndroidBridge::Bridge()->DequeueSmsRequest(aRequestId);
+        if (!request) {
+            return;
+        }
+
+        request->NotifyGetMessageFailed(aError);
+    });
+    NS_DispatchToMainThread(runnable);
+}
+
+/*static*/
+void
+SmsManager::NotifySmsDeleted(bool aDeleted, int32_t aRequestId)
+{
+    nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
+        nsCOMPtr<nsIMobileMessageCallback> request =
+            AndroidBridge::Bridge()->DequeueSmsRequest(aRequestId);
+        if (!request) {
+            return;
+        }
+
+        // For android, we support only single SMS deletion.
+        bool deleted = aDeleted;
+        request->NotifyMessageDeleted(&deleted, 1);
+    });
+    NS_DispatchToMainThread(runnable);
+}
+
+/*static*/
+void
+SmsManager::NotifySmsDeleteFailed(int32_t aError, int32_t aRequestId)
+{
+    nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
+        nsCOMPtr<nsIMobileMessageCallback> request =
+            AndroidBridge::Bridge()->DequeueSmsRequest(aRequestId);
+        if (!request) {
+            return;
+        }
+
+        request->NotifyDeleteMessageFailed(aError);
+    });
+    NS_DispatchToMainThread(runnable);
+}
+
+/*static*/
+void
+SmsManager::NotifyCursorError(int32_t aError, int32_t aRequestId)
+{
+    nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
+        nsCOMPtr<nsIMobileMessageCursorCallback> request =
+            AndroidBridge::Bridge()->DequeueSmsCursorRequest(aRequestId);
+        if (!request) {
+            return;
+        }
+
+        request->NotifyCursorError(aError);
+    });
+    NS_DispatchToMainThread(runnable);
+}
+
+/*static*/
+void
+SmsManager::NotifyThreadCursorResult(int64_t aId,
+                                     jni::String::Param aLastMessageSubject,
+                                     jni::String::Param aBody,
+                                     int64_t aUnreadCount,
+                                     jni::ObjectArray::Param aParticipants,
+                                     int64_t aTimestamp,
+                                     jni::String::Param aLastMessageType,
+                                     int32_t aRequestId)
+{
+    ThreadData thread;
+    thread.id() = aId;
+    thread.lastMessageSubject() = aLastMessageSubject ?
+                                    nsString(aLastMessageSubject) :
+                                    EmptyString();
+    thread.body() = aBody ? nsString(aBody) : EmptyString();
+    thread.unreadCount() = aUnreadCount;
+    thread.timestamp() = aTimestamp;
+    thread.lastMessageType() = eMessageType_SMS;
+
+    JNIEnv* const env = jni::GetEnvForThread();
+
+    jobjectArray participants = aParticipants.Get();
+    jsize length = env->GetArrayLength(participants);
+    for (jsize i = 0; i < length; ++i) {
+        jstring participant =
+            static_cast<jstring>(env->GetObjectArrayElement(participants, i));
+        if (participant) {
+            thread.participants().AppendElement(nsJNIString(participant, env));
+        }
+    }
+
+    nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
+        nsCOMPtr<nsIMobileMessageCursorCallback> request =
+            AndroidBridge::Bridge()->GetSmsCursorRequest(aRequestId);
+        if (!request) {
+            return;
+        }
+
+        nsCOMArray<nsIDOMMozMobileMessageThread> arr;
+        arr.AppendElement(new MobileMessageThread(thread));
+
+        nsIDOMMozMobileMessageThread** elements;
+        int32_t size;
+        size = arr.Forget(&elements);
+
+        request->NotifyCursorResult(reinterpret_cast<nsISupports**>(elements),
+                                    size);
+    });
+    NS_DispatchToMainThread(runnable);
+}
+
+/*static*/
+void
+SmsManager::NotifyMessageCursorResult(int32_t aMessageId,
+                                      int32_t aDeliveryStatus,
+                                      jni::String::Param aReceiver,
+                                      jni::String::Param aSender,
+                                      jni::String::Param aBody,
+                                      int64_t aTimestamp,
+                                      int64_t aThreadId,
+                                      bool aRead,
+                                      int32_t aRequestId)
+{
+    nsString receiver = nsString(aReceiver);
+    DeliveryState state = receiver.IsEmpty() ? eDeliveryState_Received
+                                             : eDeliveryState_Sent;
+
+    // TODO Need to add the message `messageClass` parameter value. Bug 804476
+    SmsMessageData message;
+    message.id() = aMessageId;
+    message.threadId() = aThreadId;
+    message.iccId() = EmptyString();
+    message.delivery() = state;
+    message.deliveryStatus() = static_cast<DeliveryStatus>(aDeliveryStatus);
+    message.sender() = aSender ? nsString(aSender) : EmptyString();
+    message.receiver() = receiver;
+    message.body() = aBody ? nsString(aBody) : EmptyString();
+    message.messageClass() = eMessageClass_Normal;
+    message.timestamp() = aTimestamp;
+    message.sentTimestamp() = aTimestamp;
+    message.deliveryTimestamp() = aTimestamp;
+    message.read() = aRead;
+
+    nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
+        nsCOMPtr<nsIMobileMessageCursorCallback> request =
+            AndroidBridge::Bridge()->GetSmsCursorRequest(aRequestId);
+        if (!request) {
+            return;
+        }
+
+        nsCOMArray<nsIDOMMozSmsMessage> arr;
+        arr.AppendElement(new SmsMessage(message));
+
+        nsIDOMMozSmsMessage** elements;
+        int32_t size;
+        size = arr.Forget(&elements);
+
+        request->NotifyCursorResult(reinterpret_cast<nsISupports**>(elements),
+                                    size);
+    });
+    NS_DispatchToMainThread(runnable);
+}
+
+/*static*/
+void
+SmsManager::NotifyCursorDone(int32_t aRequestId)
+{
+    nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=]() {
+        nsCOMPtr<nsIMobileMessageCursorCallback> request =
+            AndroidBridge::Bridge()->DequeueSmsCursorRequest(aRequestId);
+        if (!request) {
+            return;
+        }
+
+        request->NotifyCursorDone();
+    });
+    NS_DispatchToMainThread(runnable);
+}
+
+} // namespace
+
new file mode 100644
--- /dev/null
+++ b/dom/mobilemessage/android/SmsManager.h
@@ -0,0 +1,73 @@
+/* -*- Mode: c++; 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/. */
+
+#ifndef SmsManager_h__
+#define SmsManager_h__
+
+#include "GeneratedJNINatives.h"
+
+namespace mozilla {
+
+class SmsManager : public widget::GeckoSmsManager::Natives<SmsManager>
+{
+private:
+    SmsManager();
+
+public:
+    static void NotifySmsReceived(jni::String::Param aSender,
+                                  jni::String::Param aBody,
+                                  int32_t aMessageClass,
+                                  int64_t aTimestamp);
+    static void NotifySmsSent(int32_t aId,
+                              jni::String::Param aReceiver,
+                              jni::String::Param aBody,
+                              int64_t aTimestamp,
+                              int32_t aRequestId);
+    static void NotifySmsDelivery(int32_t aId,
+                                  int32_t aDeliveryStatus,
+                                  jni::String::Param aReceiver,
+                                  jni::String::Param aBody,
+                                  int64_t aTimestamp);
+    static void NotifySmsSendFailed(int32_t aError,
+                                    int32_t aRequestId);
+    static void NotifyGetSms(int32_t aId,
+                             int32_t aDeliveryStatus,
+                             jni::String::Param aReceiver,
+                             jni::String::Param aSender,
+                             jni::String::Param aBody,
+                             int64_t aTimestamp,
+                             bool aRead,
+                             int32_t aRequestId);
+    static void NotifyGetSmsFailed(int32_t aError,
+                                   int32_t aRequestId);
+    static void NotifySmsDeleted(bool aDeleted,
+                                 int32_t aRequestId);
+    static void NotifySmsDeleteFailed(int32_t aError,
+                                      int32_t aRequestId);
+    static void NotifyCursorError(int32_t aError,
+                                  int32_t aRequestId);
+    static void NotifyThreadCursorResult(int64_t aId,
+                                         jni::String::Param aLastMessageSubject,
+                                         jni::String::Param aBody,
+                                         int64_t aUnreadCount,
+                                         jni::ObjectArray::Param aParticipants,
+                                         int64_t aTimestamp,
+                                         jni::String::Param aLastMessageType,
+                                         int32_t aRequestId);
+    static void NotifyMessageCursorResult(int32_t aMessageId,
+                                          int32_t aDeliveryStatus,
+                                          jni::String::Param aReceiver,
+                                          jni::String::Param aSender,
+                                          jni::String::Param aBody,
+                                          int64_t aTimestamp,
+                                          int64_t aThreadId,
+                                          bool aRead,
+                                          int32_t aRequestId);
+    static void NotifyCursorDone(int32_t aRequestId);
+};
+
+} // namespace
+
+#endif // SmsManager_h__
--- a/dom/mobilemessage/moz.build
+++ b/dom/mobilemessage/moz.build
@@ -14,16 +14,17 @@ EXPORTS.mozilla.dom.mobilemessage += [
     'ipc/SmsChild.h',
     'ipc/SmsParent.h',
     'Types.h',                # Required by IPDL SmsTypes.h
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     SOURCES += [
         'android/MobileMessageDatabaseService.cpp',
+        'android/SmsManager.cpp',
         'android/SmsService.cpp',
     ]
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and CONFIG['MOZ_B2G_RIL']:
     EXTRA_JS_MODULES += [
         'gonk/mms_consts.js',
         'gonk/MmsPduHelper.jsm',
         'gonk/MobileMessageDB.jsm',
         'gonk/SmsSegmentHelper.jsm',
@@ -41,16 +42,17 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'go
             'gonk/SmsService.js',
             'gonk/SmsService.manifest',
         ]
 
 EXPORTS.mozilla.dom += [
     'DOMMobileMessageError.h',
     'MmsMessage.h',
     'MobileMessageManager.h',
+    'MobileMessageThread.h',
     'SmsMessage.h',
 ]
 
 UNIFIED_SOURCES += [
     'Assertions.cpp',
     'Constants.cpp',
     'DeletedMessageInfo.cpp',
     'DOMMobileMessageError.cpp',
@@ -70,13 +72,14 @@ IPDL_SOURCES += [
     'ipc/PMobileMessageCursor.ipdl',
     'ipc/PSms.ipdl',
     'ipc/PSmsRequest.ipdl',
     'ipc/SmsTypes.ipdlh',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/base',
+    '/widget/android',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
--- a/mobile/android/b2gdroid/app/src/main/AndroidManifest.xml
+++ b/mobile/android/b2gdroid/app/src/main/AndroidManifest.xml
@@ -23,16 +23,24 @@
     <uses-feature android:name="android.hardware.location.gps" android:required="false"/>
     <uses-feature android:name="android.hardware.touchscreen"/>
 
     <!-- Contacts API -->
     <uses-permission android:name="android.permission.READ_CONTACTS"/>
     <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
     <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
 
+    <!-- WebSMS -->
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+
+    <uses-feature android:name="android.hardware.telephony"/>
+
     <!-- Tab Queue -->
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
 
     <!-- Android Beam support -->
     <uses-permission android:name="android.permission.NFC"/>
     <uses-feature android:name="android.hardware.nfc" android:required="false"/>
 
     <uses-permission android:name="android.permission.RECORD_AUDIO"/>
--- a/mobile/android/b2gdroid/confvars.sh
+++ b/mobile/android/b2gdroid/confvars.sh
@@ -117,8 +117,10 @@ if test "$MOZ_OFFICIAL_BRANDING"; then
   if test "$MOZ_UPDATE_CHANNEL" = "beta" -o \
           "$MOZ_UPDATE_CHANNEL" = "release"; then
     MOZ_REQUIRE_SIGNING=1
   fi
 fi
 
 MOZ_JSDOWNLOADS=1
 MOZ_TIME_MANAGER=1
+MOZ_WEBSMS_BACKEND=1
+
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -42,16 +42,18 @@
     <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
 #endif
 #ifdef MOZ_WEBSMS_BACKEND
     <!-- WebSMS -->
     <uses-permission android:name="android.permission.SEND_SMS"/>
     <uses-permission android:name="android.permission.RECEIVE_SMS"/>
     <uses-permission android:name="android.permission.WRITE_SMS"/>
     <uses-permission android:name="android.permission.READ_SMS"/>
+
+    <uses-feature android:name="android.hardware.telephony"/>
 #endif
 
     <uses-feature android:name="android.hardware.location" android:required="false"/>
     <uses-feature android:name="android.hardware.location.gps" android:required="false"/>
     <uses-feature android:name="android.hardware.touchscreen"/>
 
 #ifdef NIGHTLY_BUILD
     <!-- Contacts API -->
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -28,16 +28,17 @@ import org.mozilla.gecko.preferences.Gec
 import org.mozilla.gecko.prompts.PromptService;
 import org.mozilla.gecko.tabqueue.TabQueueHelper;
 import org.mozilla.gecko.updater.UpdateServiceHelper;
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
+import org.mozilla.gecko.util.GeckoRequest;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.PrefUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.webapp.EventListener;
 import org.mozilla.gecko.webapp.UninstallListener;
 import org.mozilla.gecko.widget.ButtonToast;
@@ -2339,41 +2340,67 @@ public abstract class GeckoApp
             return;
         }
 
         if (mLayerView != null && mLayerView.isFullScreen()) {
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FullScreen:Exit", null));
             return;
         }
 
-        Tabs tabs = Tabs.getInstance();
-        Tab tab = tabs.getSelectedTab();
+        final Tabs tabs = Tabs.getInstance();
+        final Tab tab = tabs.getSelectedTab();
         if (tab == null) {
             moveTaskToBack(true);
             return;
         }
 
-        if (tab.doBack())
-            return;
-
-        if (tab.isExternal()) {
-            moveTaskToBack(true);
-            tabs.closeTab(tab);
-            return;
-        }
-
-        int parentId = tab.getParentId();
-        Tab parent = tabs.getTab(parentId);
-        if (parent != null) {
-            // The back button should always return to the parent (not a sibling).
-            tabs.closeTab(tab, parent);
-            return;
-        }
-
-        moveTaskToBack(true);
+        // Give Gecko a chance to handle the back press first, then fallback to the Java UI.
+        GeckoAppShell.sendRequestToGecko(new GeckoRequest("Browser:OnBackPressed", null) {
+            @Override
+            public void onResponse(NativeJSObject nativeJSObject) {
+                if (!nativeJSObject.getBoolean("handled")) {
+                    // Default behavior is Gecko didn't prevent.
+                    onDefault();
+                }
+            }
+
+            @Override
+            public void onError(NativeJSObject error) {
+                // Default behavior is Gecko didn't prevent, via failure.
+                onDefault();
+            }
+
+            // Return from Gecko thread, then back-press through the Java UI.
+            private void onDefault() {
+                ThreadUtils.postToUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (tab.doBack()) {
+                            return;
+                        }
+
+                        if (tab.isExternal()) {
+                            moveTaskToBack(true);
+                            tabs.closeTab(tab);
+                            return;
+                        }
+
+                        final int parentId = tab.getParentId();
+                        final Tab parent = tabs.getTab(parentId);
+                        if (parent != null) {
+                            // The back button should always return to the parent (not a sibling).
+                            tabs.closeTab(tab, parent);
+                            return;
+                        }
+
+                        moveTaskToBack(true);
+                    }
+                });
+            }
+        });
     }
 
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (!ActivityHandlerHelper.handleActivityResult(requestCode, resultCode, data)) {
             super.onActivityResult(requestCode, resultCode, data);
         }
     }
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -2376,41 +2376,52 @@ public class GeckoAppShell
     public static void deleteMessage(int aMessageId, int aRequestId) {
         if (!SmsManager.isEnabled()) {
             return;
         }
 
         SmsManager.getInstance().deleteMessage(aMessageId, aRequestId);
     }
 
-    @WrapForJNI(stubName = "CreateMessageListWrapper")
-    public static void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId) {
+    @WrapForJNI(stubName = "CreateMessageCursorWrapper")
+    public static void createMessageCursor(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, boolean aHasThreadId, long aThreadId, boolean aReverse, int aRequestId) {
+        if (!SmsManager.isEnabled()) {
+            return;
+        }
+
+        SmsManager.getInstance().createMessageCursor(aStartDate, aEndDate, aNumbers, aNumbersCount, aDelivery, aHasRead, aRead, aHasThreadId, aThreadId, aReverse, aRequestId);
+    }
+
+    @WrapForJNI(stubName = "GetNextMessageWrapper")
+    public static void getNextMessage(int aRequestId) {
         if (!SmsManager.isEnabled()) {
             return;
         }
 
-        SmsManager.getInstance().createMessageList(aStartDate, aEndDate, aNumbers, aNumbersCount, aDelivery, aHasRead, aRead, aThreadId, aReverse, aRequestId);
+        SmsManager.getInstance().getNextMessage(aRequestId);
     }
 
-    @WrapForJNI(stubName = "GetNextMessageInListWrapper")
-    public static void getNextMessageInList(int aListId, int aRequestId) {
+    @WrapForJNI(stubName = "CreateThreadCursorWrapper")
+    public static void createThreadCursor(int aRequestId) {
+        Log.i("GeckoAppShell", "CreateThreadCursorWrapper!");
+
         if (!SmsManager.isEnabled()) {
             return;
         }
 
-        SmsManager.getInstance().getNextMessageInList(aListId, aRequestId);
+        SmsManager.getInstance().createThreadCursor(aRequestId);
     }
 
-    @WrapForJNI
-    public static void clearMessageList(int aListId) {
+    @WrapForJNI(stubName = "GetNextThreadWrapper")
+    public static void getNextThread(int aRequestId) {
         if (!SmsManager.isEnabled()) {
             return;
         }
 
-        SmsManager.getInstance().clearMessageList(aListId);
+        SmsManager.getInstance().getNextThread(aRequestId);
     }
 
     /* Called by JNI from AndroidBridge, and by reflection from tests/BaseTest.java.in */
     @WrapForJNI
     @RobocopTarget
     public static boolean isTablet() {
         return HardwareUtils.isTablet();
     }
--- a/mobile/android/base/GeckoSmsManager.java
+++ b/mobile/android/base/GeckoSmsManager.java
@@ -1,37 +1,45 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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;
 
+import org.mozilla.gecko.annotation.WrapForJNI;
+
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Looper;
+import android.os.HandlerThread;
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
 import android.util.Log;
 
 import static android.telephony.SmsMessage.MessageClass;
 import static org.mozilla.gecko.SmsManager.ISmsManager;
 
 import java.util.ArrayList;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * The envelope class contains all information that are needed to keep track of
  * a sent SMS.
  */
 class Envelope
 {
@@ -178,98 +186,82 @@ class Postman
     }
 
     if (mEnvelopes.set(aId, null) == null) {
       Log.e("GeckoSmsManager", "Trying to destroy an empty Envelope!");
     }
   }
 }
 
-class SmsIOThread extends Thread {
+class SmsIOThread extends HandlerThread {
   private final static SmsIOThread sInstance = new SmsIOThread();
 
   private Handler mHandler;
 
   public static SmsIOThread getInstance() {
     return sInstance;
   }
 
-  public boolean execute(Runnable r) {
-    return mHandler.post(r);
+  SmsIOThread() {
+    super("SmsIOThread");
   }
 
   @Override
-  public void run() {
-    Looper.prepare();
+  public void start() {
+    super.start();
+    mHandler = new Handler(getLooper());
+  }
 
-    mHandler = new Handler();
-
-    Looper.loop();
+  public boolean execute(Runnable r) {
+    return mHandler.post(r);
   }
 }
 
 class MessagesListManager
 {
   private static final MessagesListManager sInstance = new MessagesListManager();
 
   public static MessagesListManager getInstance() {
     return sInstance;
   }
 
-  private final ArrayList<Cursor> mCursors = new ArrayList<>();
-
-  public int add(Cursor aCursor) {
-    int size = mCursors.size();
+  private final HashMap<Integer, Cursor> mCursors = new HashMap<>();
 
-    for (int i=0; i<size; ++i) {
-      if (mCursors.get(i) == null) {
-        mCursors.set(i, aCursor);
-        return i;
-      }
+  public void add(int id, Cursor aCursor) {
+    if (mCursors.containsKey(id)) {
+      Log.e("GeckoSmsManager", "Trying to overwrite cursor!");
+      return;
     }
 
-    mCursors.add(aCursor);
-    return size;
+    mCursors.put(id, aCursor);
   }
 
   public Cursor get(int aId) {
-    if (aId < 0 || mCursors.size() <= aId) {
-      Log.e("GeckoSmsManager", "Trying to get an unknown list!");
+    if (!mCursors.containsKey(aId)) {
+      Log.e("GeckoSmsManager", "Cursor doesn't exist!");
       return null;
     }
 
-    Cursor cursor = mCursors.get(aId);
-    if (cursor == null) {
-      Log.e("GeckoSmsManager", "Trying to get an empty list!");
-    }
-
-    return cursor;
+    return mCursors.get(aId);
   }
 
   public void remove(int aId) {
-    if (aId < 0 || mCursors.size() <= aId) {
-      Log.e("GeckoSmsManager", "Trying to destroy an unknown list!");
+    if (!mCursors.containsKey(aId)) {
+      Log.e("GeckoSmsManager", "Cursor doesn't exist!");
       return;
     }
 
-    Cursor cursor = mCursors.set(aId, null);
-    if (cursor == null) {
-      Log.e("GeckoSmsManager", "Trying to destroy an empty list!");
-      return;
-    }
-
-    cursor.close();
+    mCursors.remove(aId);
   }
 
   public void clear() {
-    for (int i=0; i<mCursors.size(); ++i) {
-      Cursor c = mCursors.get(i);
-      if (c != null) {
-        c.close();
-      }
+    Set<Map.Entry<Integer, Cursor>> entries = mCursors.entrySet();
+    Iterator<Map.Entry<Integer, Cursor>> it = entries.iterator();
+    while (it.hasNext()) {
+      it.next().getValue().close();
     }
 
     mCursors.clear();
   }
 }
 
 public class GeckoSmsManager
   extends BroadcastReceiver
@@ -277,17 +269,17 @@ public class GeckoSmsManager
 {
   public final static String ACTION_SMS_RECEIVED  = "android.provider.Telephony.SMS_RECEIVED";
   public final static String ACTION_SMS_SENT      = "org.mozilla.gecko.SMS_SENT";
   public final static String ACTION_SMS_DELIVERED = "org.mozilla.gecko.SMS_DELIVERED";
 
   /*
    * Make sure that the following error codes are in sync with |ErrorType| in:
    * dom/mobilemessage/interfaces/nsIMobileMessageCallback.idl
-   * The error code are owned by the DOM.
+   * The error codes are owned by the DOM.
    */
   public final static int kNoError               = 0;
   public final static int kNoSignalError         = 1;
   public final static int kNotFoundError         = 2;
   public final static int kUnknownError          = 3;
   public final static int kInternalError         = 4;
   public final static int kNoSimCardError        = 5;
   public final static int kRadioDisabledError    = 6;
@@ -300,18 +292,19 @@ public class GeckoSmsManager
   public final static int kGeneralProblemsError = 13;
   public final static int kServiceNotAvailableError      = 14;
   public final static int kMessageTooLongForNetworkError = 15;
   public final static int kServiceNotSupportedError      = 16;
   public final static int kRetryRequiredError   = 17;
 
   private final static int kMaxMessageSize    = 160;
 
-  private final static Uri kSmsContentUri     = Uri.parse("content://sms");
-  private final static Uri kSmsSentContentUri = Uri.parse("content://sms/sent");
+  private final static Uri kSmsContentUri        = Uri.parse("content://sms");
+  private final static Uri kSmsSentContentUri    = Uri.parse("content://sms/sent");
+  private final static Uri kSmsThreadsContentUri = Uri.parse("content://sms/conversations");
 
   private final static int kSmsTypeInbox      = 1;
   private final static int kSmsTypeSentbox    = 2;
 
   /*
    * Keep the following state codes in syng with |DeliveryState| in:
    * dom/mobilemessage/Types.h
    */
@@ -346,17 +339,19 @@ public class GeckoSmsManager
    * dom/mobilemessage/Types.h
    */
   private final static int kMessageClassNormal  = 0;
   private final static int kMessageClassClass0  = 1;
   private final static int kMessageClassClass1  = 2;
   private final static int kMessageClassClass2  = 3;
   private final static int kMessageClassClass3  = 4;
 
-  private final static String[] kRequiredMessageRows = { "_id", "address", "body", "date", "type", "status" };
+  private final static String[] kRequiredMessageRows = { "_id", "address", "body", "date", "type", "status", "read", "thread_id" };
+  private final static String[] kRequiredMessageRowsForThread = { "_id", "address", "body", "read", "subject", "date" };
+  private final static String[] kThreadProjection = { "thread_id" };
 
   // Used to generate monotonically increasing GUIDs.
   private static final AtomicInteger pendingIntentGuid = new AtomicInteger(Integer.MIN_VALUE);
 
   // The maximum value of a 32 bit signed integer. Used to enforce a limit on ids.
   private static final long UNSIGNED_INTEGER_MAX_VALUE = Integer.MAX_VALUE * 2L + 1L;
 
   public GeckoSmsManager() {
@@ -655,21 +650,24 @@ public class GeckoSmsManager
             sender = cursor.getString(cursor.getColumnIndex("address"));
           } else if (type == kSmsTypeSentbox) {
             deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status")));
             receiver = cursor.getString(cursor.getColumnIndex("address"));
           } else {
             throw new InvalidTypeException();
           }
 
+          boolean read = cursor.getInt(cursor.getColumnIndex("read")) != 0;
+
           notifyGetSms(cursor.getInt(cursor.getColumnIndex("_id")),
                        deliveryStatus,
                        receiver, sender,
                        cursor.getString(cursor.getColumnIndex("body")),
                        cursor.getLong(cursor.getColumnIndex("date")),
+                       read,
                        mRequestId);
         } catch (NotFoundException e) {
           Log.i("GeckoSmsManager", "Message id " + mMessageId + " not found");
           notifyGetSmsFailed(kNotFoundError, mRequestId);
         } catch (UnmatchingIdException e) {
           Log.e("GeckoSmsManager", "Requested message id (" + mMessageId +
                                    ") is different from the one we got.");
           notifyGetSmsFailed(kUnknownError, mRequestId);
@@ -731,206 +729,305 @@ public class GeckoSmsManager
     }
 
     if (!SmsIOThread.getInstance().execute(new DeleteMessageRunnable(aMessageId, aRequestId))) {
       Log.e("GeckoSmsManager", "Failed to add GetMessageRunnable to the SmsIOThread");
       notifySmsDeleteFailed(kUnknownError, aRequestId);
     }
   }
 
+  private void getMessageFromCursorAndNotify(Cursor aCursor, int aRequestId) throws Exception {
+    int type = aCursor.getInt(aCursor.getColumnIndex("type"));
+    int deliveryStatus = kDeliveryStateUnknown;
+    String sender = "";
+    String receiver = "";
+
+    if (type == kSmsTypeInbox) {
+      deliveryStatus = kDeliveryStatusSuccess;
+      sender = aCursor.getString(aCursor.getColumnIndex("address"));
+    } else if (type == kSmsTypeSentbox) {
+      deliveryStatus = getGeckoDeliveryStatus(aCursor.getInt(aCursor.getColumnIndex("status")));
+      receiver = aCursor.getString(aCursor.getColumnIndex("address"));
+    } else {
+      throw new Exception("Shouldn't ever get a message here that's not from inbox or sentbox");
+    }
+
+    boolean read = aCursor.getInt(aCursor.getColumnIndex("read")) != 0;
+
+    notifyMessageCursorResult(aCursor.getInt(aCursor.getColumnIndex("_id")),
+                              deliveryStatus,
+                              receiver, sender,
+                              aCursor.getString(aCursor.getColumnIndex("body")),
+                              aCursor.getLong(aCursor.getColumnIndex("date")),
+                              aCursor.getLong(aCursor.getColumnIndex("thread_id")),
+                              read,
+                              aRequestId);
+  }
+
   @Override
-  public void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId) {
-    class CreateMessageListRunnable implements Runnable {
+  public void createMessageCursor(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, boolean aHasThreadId, long aThreadId, boolean aReverse, int aRequestId) {
+    class CreateMessageCursorRunnable implements Runnable {
       private final long     mStartDate;
       private final long     mEndDate;
       private final String[] mNumbers;
       private final int      mNumbersCount;
       private final String   mDelivery;
+      private final boolean  mHasThreadId;
+      private final long     mThreadId;
       private final boolean  mReverse;
       private final int      mRequestId;
 
-      CreateMessageListRunnable(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId) {
+      CreateMessageCursorRunnable(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, boolean aHasThreadId, long aThreadId, boolean aReverse, int aRequestId) {
         mStartDate = aStartDate;
         mEndDate = aEndDate;
         mNumbers = aNumbers;
         mNumbersCount = aNumbersCount;
         mDelivery = aDelivery;
+        mHasThreadId = aHasThreadId;
+        mThreadId = aThreadId;
         mReverse = aReverse;
         mRequestId = aRequestId;
       }
 
       @Override
       public void run() {
         Cursor cursor = null;
         boolean closeCursor = true;
 
         try {
-          // TODO: should use the |selectionArgs| argument in |ContentResolver.query()|.
-          ArrayList<String> restrictions = new ArrayList<String>();
+          StringBuilder restrictions = new StringBuilder();
+          Formatter formatter = new Formatter(restrictions);
 
           if (mStartDate >= 0) {
-            restrictions.add("date >= " + mStartDate);
+            formatter.format("date >= '%d' AND ", mStartDate);
           }
 
           if (mEndDate >= 0) {
-            restrictions.add("date <= " + mEndDate);
+            formatter.format("date <= '%d' AND ", mEndDate);
           }
 
           if (mNumbersCount > 0) {
-            final StringBuilder numberRestriction = new StringBuilder("address IN ('");
-            numberRestriction.append(mNumbers[0]).append("'");
+            formatter.format("address IN ('%s'", mNumbers[0]);
 
-            for (int i=1; i<mNumbersCount; ++i) {
-              numberRestriction.append(", '").append(mNumbers[i]).append("'");
+            for (int i = 1; i < mNumbersCount; ++i) {
+              formatter.format(", '%s'", mNumbers[i]);
             }
-            numberRestriction.append(')');
-
-            restrictions.add(numberRestriction.toString());
+            
+            formatter.format(") AND ");
           }
 
-          if (mDelivery == null) {
-            restrictions.add("type IN ('" + kSmsTypeSentbox + "', '" + kSmsTypeInbox + "')");
+          if (mDelivery == null || mDelivery.isEmpty()) {
+            formatter.format("type IN ('%d', '%d') AND ", kSmsTypeSentbox, kSmsTypeInbox);
           } else if (mDelivery.equals("sent")) {
-            restrictions.add("type = " + kSmsTypeSentbox);
+            formatter.format("type = '%d' AND ", kSmsTypeSentbox);
           } else if (mDelivery.equals("received")) {
-            restrictions.add("type = " + kSmsTypeInbox);
+            formatter.format("type = '%d' AND ", kSmsTypeInbox);
           } else {
-            throw new UnexpectedDeliveryStateException();
+            throw new Exception("Unexpected delivery state: " + mDelivery);
           }
 
-          final StringBuilder restrictionText = new StringBuilder();
-          if (!restrictions.isEmpty()) {
-            restrictionText.append(restrictions.get(0));
+          if (mHasThreadId) {
+            formatter.format("thread_id = '%d' AND ", mThreadId);
           }
 
-          for (int i=1; i<restrictions.size(); ++i) {
-            restrictionText.append(" AND ").append(restrictions.get(i));
-          }
+          // Final 'AND 1' is a no-op so we don't have to special case the last
+          // condition.
+          formatter.format("1");
 
           ContentResolver cr = GeckoAppShell.getContext().getContentResolver();
-          cursor = cr.query(kSmsContentUri, kRequiredMessageRows, restrictionText.toString(), null,
+          cursor = cr.query(kSmsContentUri,
+                            kRequiredMessageRows,
+                            restrictions.toString(),
+                            null,
                             mReverse ? "date DESC" : "date ASC");
 
           if (cursor.getCount() == 0) {
-            notifyNoMessageInList(mRequestId);
+            notifyCursorDone(mRequestId);
             return;
           }
 
-          cursor.moveToFirst();
+          MessagesListManager.getInstance().add(mRequestId, cursor);
 
-          int type = cursor.getInt(cursor.getColumnIndex("type"));
-          int deliveryStatus;
-          String sender = "";
-          String receiver = "";
+          cursor.moveToFirst();
+          getMessageFromCursorAndNotify(cursor, mRequestId);
+          closeCursor = false;
+        } catch (Exception e) {
+          Log.e("GeckoSmsManager", "Error while trying to create a message list cursor", e);
+          notifyCursorError(kUnknownError, mRequestId);
+        } finally {
+          if (closeCursor && cursor != null) {
+            cursor.close();
+          }
+        }
+      }
+    }
 
-          if (type == kSmsTypeInbox) {
-            deliveryStatus = kDeliveryStatusSuccess;
-            sender = cursor.getString(cursor.getColumnIndex("address"));
-          } else if (type == kSmsTypeSentbox) {
-            deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status")));
-            receiver = cursor.getString(cursor.getColumnIndex("address"));
-          } else {
-            throw new UnexpectedDeliveryStateException();
+    if (!SmsIOThread.getInstance().execute(new CreateMessageCursorRunnable(aStartDate, aEndDate, aNumbers, aNumbersCount, aDelivery, aHasRead, aRead, aHasThreadId, aThreadId, aReverse, aRequestId))) {
+      Log.e("GeckoSmsManager", "Failed to add CreateMessageCursorRunnable to the SmsIOThread");
+      notifyCursorError(kUnknownError, aRequestId);
+    }
+  }
+
+  @Override
+  public void getNextMessage(int aRequestId) {
+    class GetNextMessageRunnable implements Runnable {
+      private final int mRequestId;
+
+      GetNextMessageRunnable(int aRequestId) {
+        mRequestId = aRequestId;
+      }
+
+      @Override
+      public void run() {
+        Cursor cursor = null;
+        boolean closeCursor = true;
+        try {
+          cursor = MessagesListManager.getInstance().get(mRequestId);
+
+          if (!cursor.moveToNext()) {
+            MessagesListManager.getInstance().remove(mRequestId);
+            notifyCursorDone(mRequestId);
+            return;
           }
 
-          int listId = MessagesListManager.getInstance().add(cursor);
+          getMessageFromCursorAndNotify(cursor, mRequestId);
           closeCursor = false;
-          notifyListCreated(listId,
-                            cursor.getInt(cursor.getColumnIndex("_id")),
-                            deliveryStatus,
-                            receiver, sender,
-                            cursor.getString(cursor.getColumnIndex("body")),
-                            cursor.getLong(cursor.getColumnIndex("date")),
-                            mRequestId);
-        } catch (UnexpectedDeliveryStateException e) {
-          Log.e("GeckoSmsManager", "Unexcepted delivery state type", e);
-          notifyReadingMessageListFailed(kUnknownError, mRequestId);
         } catch (Exception e) {
-          Log.e("GeckoSmsManager", "Error while trying to create a message list cursor", e);
-          notifyReadingMessageListFailed(kUnknownError, mRequestId);
+          Log.e("GeckoSmsManager", "Error while trying to get the next message of a list", e);
+          notifyCursorError(kUnknownError, mRequestId);
         } finally {
-          // Close the cursor if MessagesListManager isn't taking care of it.
-          // We could also just check if it is in the MessagesListManager list but
-          // that would be less efficient.
-          if (cursor != null && closeCursor) {
+          if (closeCursor) {
             cursor.close();
           }
         }
       }
     }
 
-    if (!SmsIOThread.getInstance().execute(new CreateMessageListRunnable(aStartDate, aEndDate, aNumbers, aNumbersCount, aDelivery, aHasRead, aRead, aThreadId, aReverse, aRequestId))) {
-      Log.e("GeckoSmsManager", "Failed to add CreateMessageListRunnable to the SmsIOThread");
-      notifyReadingMessageListFailed(kUnknownError, aRequestId);
+    if (!SmsIOThread.getInstance().execute(new GetNextMessageRunnable(aRequestId))) {
+      Log.e("GeckoSmsManager", "Failed to add GetNextMessageRunnable to the SmsIOThread");
+      notifyCursorError(kUnknownError, aRequestId);
+    }
+  }
+
+  private void getThreadFromCursorAndNotify(Cursor aCursor, int aRequestId) throws Exception {
+    ContentResolver cr = GeckoAppShell.getContext().getContentResolver();
+
+    long id = aCursor.getLong(aCursor.getColumnIndex("thread_id"));
+    Cursor msgCursor = cr.query(kSmsContentUri,
+                                kRequiredMessageRowsForThread,
+                                "thread_id = " + id,
+                                null,
+                                "date DESC");
+
+    if (msgCursor == null || msgCursor.getCount() == 0) {
+      throw new Exception("Empty thread " + id);
     }
+
+    msgCursor.moveToFirst();
+
+    String lastMessageSubject = msgCursor.getString(msgCursor.getColumnIndex("subject"));
+    String body = msgCursor.getString(msgCursor.getColumnIndex("body"));
+    long timestamp = msgCursor.getLong(msgCursor.getColumnIndex("date"));
+
+    HashSet<String> participants = new HashSet<>();
+    do {
+      String p = msgCursor.getString(msgCursor.getColumnIndex("address"));
+      participants.add(p);
+    } while (msgCursor.moveToNext());
+
+    //TODO: handle MMS
+    String lastMessageType = "sms";
+
+    msgCursor = cr.query(kSmsContentUri,
+                         kRequiredMessageRowsForThread,
+                         "thread_id = " + id + " AND read = 0",
+                         null,
+                         null);
+
+    if (msgCursor == null) {
+      Log.e("GeckoSmsManager", "We should never get here, should have errored before");
+      throw new Exception("Empty thread " + id);
+    }
+
+    long unreadCount = msgCursor.getCount();
+
+    notifyThreadCursorResult(id, lastMessageSubject, body, unreadCount, participants.toArray(), timestamp, lastMessageType, aRequestId);
   }
 
   @Override
-  public void getNextMessageInList(int aListId, int aRequestId) {
-    class GetNextMessageInListRunnable implements Runnable {
-      private final int mListId;
+  public void createThreadCursor(int aRequestId) {
+    class CreateThreadCursorRunnable implements Runnable {
       private final int mRequestId;
 
-      GetNextMessageInListRunnable(int aListId, int aRequestId) {
-        mListId = aListId;
+      CreateThreadCursorRunnable(int aRequestId) {
         mRequestId = aRequestId;
       }
 
       @Override
       public void run() {
         try {
-          Cursor cursor = MessagesListManager.getInstance().get(mListId);
-
-          if (!cursor.moveToNext()) {
-            MessagesListManager.getInstance().remove(mListId);
-            notifyNoMessageInList(mRequestId);
+          ContentResolver cr = GeckoAppShell.getContext().getContentResolver();
+          Cursor cursor = cr.query(kSmsThreadsContentUri,
+                                   kThreadProjection,
+                                   null,
+                                   null,
+                                   "date DESC");
+          if (cursor == null || !cursor.moveToFirst()) {
+            notifyCursorDone(mRequestId);
             return;
           }
 
-          int type = cursor.getInt(cursor.getColumnIndex("type"));
-          int deliveryStatus;
-          String sender = "";
-          String receiver = "";
+          MessagesListManager.getInstance().add(mRequestId, cursor);
 
-          if (type == kSmsTypeInbox) {
-            deliveryStatus = kDeliveryStatusSuccess;
-            sender = cursor.getString(cursor.getColumnIndex("address"));
-          } else if (type == kSmsTypeSentbox) {
-            deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status")));
-            receiver = cursor.getString(cursor.getColumnIndex("address"));
-          } else {
-            throw new UnexpectedDeliveryStateException();
-          }
-
-          int listId = MessagesListManager.getInstance().add(cursor);
-          notifyGotNextMessage(cursor.getInt(cursor.getColumnIndex("_id")),
-                               deliveryStatus,
-                               receiver, sender,
-                               cursor.getString(cursor.getColumnIndex("body")),
-                               cursor.getLong(cursor.getColumnIndex("date")),
-                               mRequestId);
-        } catch (UnexpectedDeliveryStateException e) {
-          Log.e("GeckoSmsManager", "Unexcepted delivery state type", e);
-          notifyReadingMessageListFailed(kUnknownError, mRequestId);
+          getThreadFromCursorAndNotify(cursor, mRequestId);
         } catch (Exception e) {
-          Log.e("GeckoSmsManager", "Error while trying to get the next message of a list", e);
-          notifyReadingMessageListFailed(kUnknownError, mRequestId);
+          Log.e("GeckoSmsManager", "Error while trying to create thread cursor: " + e);
+          notifyCursorError(kUnknownError, mRequestId);
         }
       }
     }
 
-    if (!SmsIOThread.getInstance().execute(new GetNextMessageInListRunnable(aListId, aRequestId))) {
-      Log.e("GeckoSmsManager", "Failed to add GetNextMessageInListRunnable to the SmsIOThread");
-      notifyReadingMessageListFailed(kUnknownError, aRequestId);
+    if (!SmsIOThread.getInstance().execute(new CreateThreadCursorRunnable(aRequestId))) {
+      Log.e("GeckoSmsManager", "Failed to add CreateThreadCursorRunnable to the SmsIOThread");
+      notifyCursorError(kUnknownError, aRequestId);
     }
   }
 
   @Override
-  public void clearMessageList(int aListId) {
-    MessagesListManager.getInstance().remove(aListId);
+  public void getNextThread(int aRequestId) {
+    class GetNextThreadRunnable implements Runnable {
+      private final int mRequestId;
+
+      GetNextThreadRunnable(int aRequestId) {
+        mRequestId = aRequestId;
+      }
+
+      @Override
+      public void run() {
+        try {
+          Cursor cursor = MessagesListManager.getInstance().get(mRequestId);
+
+          if (!cursor.moveToNext()) {
+            MessagesListManager.getInstance().remove(mRequestId);
+            notifyCursorDone(mRequestId);
+            return;
+          }
+
+          getThreadFromCursorAndNotify(cursor, mRequestId);
+        } catch (Exception e) {
+          Log.e("GeckoSmsManager", "Error while trying to create thread cursor: " + e);
+          notifyCursorError(kUnknownError, mRequestId);
+        }
+      }
+    }
+
+    if (!SmsIOThread.getInstance().execute(new GetNextThreadRunnable(aRequestId))) {
+      Log.e("GeckoSmsManager", "Failed to add GetNextThreadRunnable to the SmsIOThread");
+      notifyCursorError(kUnknownError, aRequestId);
+    }
   }
 
   @Override
   public void stop() {
     GeckoAppShell.getContext().unregisterReceiver(this);
   }
 
   @Override
@@ -978,29 +1075,37 @@ public class GeckoSmsManager
   static class NotFoundException extends Exception {
     private static final long serialVersionUID = 1940676816633984L;
   }
 
   static class TooManyResultsException extends Exception {
     private static final long serialVersionUID = 51883196784325305L;
   }
 
-  static class UnexpectedDeliveryStateException extends Exception {
-    private static final long serialVersionUID = 494122763684005716L;
-  }
-
   static class UnmatchingIdException extends Exception {
     private static final long serialVersionUID = 158467542575633280L;
   }
 
+  @WrapForJNI
   private static native void notifySmsReceived(String aSender, String aBody, int aMessageClass, long aTimestamp);
+  @WrapForJNI
   private static native void notifySmsSent(int aId, String aReceiver, String aBody, long aTimestamp, int aRequestId);
+  @WrapForJNI
   private static native void notifySmsDelivery(int aId, int aDeliveryStatus, String aReceiver, String aBody, long aTimestamp);
+  @WrapForJNI
   private static native void notifySmsSendFailed(int aError, int aRequestId);
-  private static native void notifyGetSms(int aId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, int aRequestId);
+  @WrapForJNI
+  private static native void notifyGetSms(int aId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, boolean aRead, int aRequestId);
+  @WrapForJNI
   private static native void notifyGetSmsFailed(int aError, int aRequestId);
+  @WrapForJNI
   private static native void notifySmsDeleted(boolean aDeleted, int aRequestId);
+  @WrapForJNI
   private static native void notifySmsDeleteFailed(int aError, int aRequestId);
-  private static native void notifyNoMessageInList(int aRequestId);
-  private static native void notifyListCreated(int aListId, int aMessageId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, int aRequestId);
-  private static native void notifyGotNextMessage(int aMessageId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, int aRequestId);
-  private static native void notifyReadingMessageListFailed(int aError, int aRequestId);
+  @WrapForJNI
+  private static native void notifyCursorError(int aError, int aRequestId);
+  @WrapForJNI
+  private static native void notifyThreadCursorResult(long aId, String aLastMessageSubject, String aBody, long aUnreadCount, Object[] aParticipants, long aTimestamp, String aLastMessageType, int aRequestId);
+  @WrapForJNI
+  private static native void notifyMessageCursorResult(int aMessageId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, long aThreadId, boolean aRead, int aRequestId);
+  @WrapForJNI
+  private static native void notifyCursorDone(int aRequestId);
 }
--- a/mobile/android/base/SmsManager.java
+++ b/mobile/android/base/SmsManager.java
@@ -26,14 +26,15 @@ public class SmsManager {
     public interface ISmsManager {
         void start();
         void stop();
         void shutdown();
 
         void send(String aNumber, String aMessage, int aRequestId);
         void getMessage(int aMessageId, int aRequestId);
         void deleteMessage(int aMessageId, int aRequestId);
-        void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, long aThreadId, boolean aReverse, int aRequestId);
-        void getNextMessageInList(int aListId, int aRequestId);
-        void clearMessageList(int aListId);
+        void createMessageCursor(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, boolean aHasThreadId, long aThreadId, boolean aReverse, int aRequestId);
+        void createThreadCursor(int aRequestId);
+        void getNextThread(int aRequestId);
+        void getNextMessage(int aRequestId);
     }
 }
 
--- a/mobile/android/base/home/BrowserSearch.java
+++ b/mobile/android/base/home/BrowserSearch.java
@@ -9,17 +9,16 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Locale;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
-import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SuggestClient;
 import org.mozilla.gecko.Tab;
@@ -530,24 +529,21 @@ public class BrowserSearch extends HomeF
         }
 
         // Suggestions from search engine
         if (mSearchEngineSuggestionLoaderCallbacks == null) {
             mSearchEngineSuggestionLoaderCallbacks = new SearchEngineSuggestionLoaderCallbacks();
         }
         getLoaderManager().restartLoader(LOADER_ID_SUGGESTION, null, mSearchEngineSuggestionLoaderCallbacks);
 
-        // Start search history suggestions query only in nightly. Bug 1201325
-        if (AppConstants.NIGHTLY_BUILD) {
-            // Saved suggestions
-            if (mSearchHistorySuggestionLoaderCallback == null) {
-                mSearchHistorySuggestionLoaderCallback = new SearchHistorySuggestionLoaderCallbacks();
-            }
-            getLoaderManager().restartLoader(LOADER_ID_SAVED_SUGGESTION, null, mSearchHistorySuggestionLoaderCallback);
+        // Saved suggestions
+        if (mSearchHistorySuggestionLoaderCallback == null) {
+            mSearchHistorySuggestionLoaderCallback = new SearchHistorySuggestionLoaderCallbacks();
         }
+        getLoaderManager().restartLoader(LOADER_ID_SAVED_SUGGESTION, null, mSearchHistorySuggestionLoaderCallback);
     }
 
     private void setSuggestions(ArrayList<String> suggestions) {
         ThreadUtils.assertOnUiThread();
 
         mSearchEngines.get(0).setSuggestions(suggestions);
         mAdapter.notifyDataSetChanged();
     }
--- a/mobile/android/base/home/SearchEngineRow.java
+++ b/mobile/android/base/home/SearchEngineRow.java
@@ -1,16 +1,15 @@
 /* -*- 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.home;
 
-import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.home.BrowserSearch.OnEditSuggestionListener;
 import org.mozilla.gecko.home.BrowserSearch.OnSearchListener;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.preferences.GeckoPreferences;
@@ -70,19 +69,16 @@ class SearchEngineRow extends AnimatedHe
 
     // android:backgroundTint only works in Android 21 and higher so we can't do this statically in the xml
     private Drawable mSearchHistorySuggestionIcon;
 
     // Maximums for suggestions
     private int mMaxSavedSuggestions;
     private int mMaxSearchSuggestions;
 
-    // Remove this default limit value in Bug 1201325
-    private static final int SUGGESTIONS_MAX = 4;
-
     public SearchEngineRow(Context context) {
         this(context, null);
     }
 
     public SearchEngineRow(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
@@ -264,24 +260,20 @@ class SearchEngineRow extends AnimatedHe
      * Displays suggestions supplied by the search engine, relative to number of suggestions from search history.
      *
      * @param animate whether or not to animate suggestions for visual polish
      * @param recycledSuggestionCount How many suggestion "button" views we could recycle from previous calls
      * @param savedSuggestionCount how many saved searches this searchTerm has
      * @return the global count of how many suggestions have been bound/shown in the search engine row
      */
     private int updateFromSearchEngine(boolean animate, int recycledSuggestionCount, int savedSuggestionCount) {
-        // Remove this default limit value in Bug 1201325
-        int maxSuggestions = SUGGESTIONS_MAX;
-        if (AppConstants.NIGHTLY_BUILD) {
-            maxSuggestions = mMaxSearchSuggestions;
-            // If there are less than max saved searches on phones, fill the space with more search engine suggestions
-            if (!HardwareUtils.isTablet() && savedSuggestionCount < mMaxSavedSuggestions) {
-                maxSuggestions += mMaxSavedSuggestions - savedSuggestionCount;
-            }
+        int maxSuggestions = mMaxSearchSuggestions;
+        // If there are less than max saved searches on phones, fill the space with more search engine suggestions
+        if (!HardwareUtils.isTablet() && savedSuggestionCount < mMaxSavedSuggestions) {
+            maxSuggestions += mMaxSavedSuggestions - savedSuggestionCount;
         }
 
         int suggestionCounter = 0;
         for (String suggestion : mSearchEngine.getSuggestions()) {
             if (suggestionCounter == maxSuggestions) {
                 break;
             }
 
@@ -315,23 +307,16 @@ class SearchEngineRow extends AnimatedHe
      **/
     public void updateSuggestions(boolean searchSuggestionsEnabled, SearchEngine searchEngine, List<String> searchHistorySuggestions, boolean animate) {
         mSearchEngine = searchEngine;
         // Set the search engine icon (e.g., Google) for the row.
         mIconView.updateAndScaleImage(mSearchEngine.getIcon(), mSearchEngine.getEngineIdentifier());
         // Set the initial content description.
         setDescriptionOnSuggestion(mUserEnteredTextView, mUserEnteredTextView.getText().toString());
 
-        if (!AppConstants.NIGHTLY_BUILD) {
-            if (searchSuggestionsEnabled) {
-                updateFromSearchEngine(animate, mSuggestionView.getChildCount(), 0);
-            }
-            return;
-        }
-
         final int recycledSuggestionCount = mSuggestionView.getChildCount();
         final SharedPreferences prefs = GeckoSharedPrefs.forApp(getContext());
         final boolean savedSearchesEnabled = prefs.getBoolean(GeckoPreferences.PREFS_HISTORY_SAVED_SEARCH, true);
 
         if (searchSuggestionsEnabled && savedSearchesEnabled) {
             final int savedSearchCount = (searchHistorySuggestions != null) ? searchHistorySuggestions.size() : 0;
             final int suggestionViewCount = updateFromSearchEngine(animate, recycledSuggestionCount, savedSearchCount);
             updateFromSavedSearches(searchHistorySuggestions, animate, suggestionViewCount, recycledSuggestionCount);
--- a/mobile/android/base/preferences/GeckoPreferences.java
+++ b/mobile/android/base/preferences/GeckoPreferences.java
@@ -762,23 +762,16 @@ OnSharedPreferenceChangeListener
                     }
                 } else if (PREFS_OPEN_URLS_IN_PRIVATE.equals(key)) {
                     // Remove UI for opening external links in private browsing on non-Nightly builds.
                     if (!AppConstants.NIGHTLY_BUILD || !RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_PRIVATE_BROWSING)) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
-                } else if (PREFS_HISTORY_SAVED_SEARCH.equals(key)) {
-                    // Remove settings UI if not on Nightly
-                    if (!AppConstants.NIGHTLY_BUILD) {
-                        preferences.removePreference(pref);
-                        i--;
-                        continue;
-                    }
                 } else if (PREFS_TRACKING_PROTECTION.equals(key)) {
                     // Remove UI for global TP pref in non-Nightly builds.
                     if (!AppConstants.NIGHTLY_BUILD) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
                 } else if (PREFS_TRACKING_PROTECTION_PB.equals(key)) {
--- a/mobile/android/chrome/content/Reader.js
+++ b/mobile/android/chrome/content/Reader.js
@@ -15,16 +15,49 @@ var Reader = {
   STATUS_FETCH_FAILED_UNSUPPORTED_FORMAT: 3,
   STATUS_FETCHED_ARTICLE: 4,
 
   get _hasUsedToolbar() {
     delete this._hasUsedToolbar;
     return this._hasUsedToolbar = Services.prefs.getBoolPref("reader.has_used_toolbar");
   },
 
+  /**
+   * BackPressListener (listeners / ReaderView Ids).
+   */
+  _backPressListeners: [],
+  _backPressViewIds: [],
+
+  /**
+   * Set a backPressListener for this tabId / ReaderView Id pair.
+   */
+  _addBackPressListener: function(tabId, viewId, listener) {
+    this._backPressListeners[tabId] = listener;
+    this._backPressViewIds[viewId] = tabId;
+  },
+
+  /**
+   * Remove a backPressListener for this ReaderView Id.
+   */
+  _removeBackPressListener: function(viewId) {
+    let tabId = this._backPressViewIds[viewId];
+    if (tabId != undefined) {
+      this._backPressListeners[tabId] = null;
+      delete this._backPressViewIds[viewId];
+    }
+  },
+
+  /**
+   * If the requested tab has a backPress listener, return its results, else false.
+   */
+  onBackPress: function(tabId) {
+    let listener = this._backPressListeners[tabId];
+    return { handled: (listener ? listener() : false) };
+  },
+
   observe: function Reader_observe(aMessage, aTopic, aData) {
     switch (aTopic) {
       case "Reader:FetchContent": {
         let data = JSON.parse(aData);
         this._fetchContent(data.url, data.id);
         break;
       }
 
@@ -61,16 +94,39 @@ var Reader = {
           }
         }, e => {
           if (e && e.newURL) {
             message.target.loadURI("about:reader?url=" + encodeURIComponent(e.newURL));
           }
         });
         break;
 
+      // On DropdownClosed in ReaderView, we cleanup / clear existing BackPressListener.
+      case "Reader:DropdownClosed": {
+        this._removeBackPressListener(message.data);
+        break;
+      }
+
+      // On DropdownOpened in ReaderView, we add BackPressListener to handle a subsequent BACK request.
+      case "Reader:DropdownOpened": {
+        let tabId = BrowserApp.selectedTab.id;
+        this._addBackPressListener(tabId, message.data, () => {
+          // User hit BACK key while ReaderView has the banner font-dropdown opened.
+          // Close it and return prevent-default.
+          if (message.target.messageManager) {
+            message.target.messageManager.sendAsyncMessage("Reader:CloseDropdown");
+            return true;
+          }
+          // We can assume ReaderView banner's font-dropdown doesn't need to be closed.
+          return false;
+        });
+
+        break;
+      }
+
       case "Reader:FaviconRequest": {
         Messaging.sendRequestForResult({
           type: "Reader:FaviconRequest",
           url: message.data.url
         }).then(data => {
           message.target.messageManager.sendAsyncMessage("Reader:FaviconReturn", JSON.parse(data));
         });
         break;
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -183,44 +183,56 @@ lazilyLoadedObserverScripts.forEach(func
   notifications.forEach((notification) => {
     Services.obs.addObserver(observer, notification, false);
   });
 });
 
 // Lazily-loaded browser scripts that use message listeners.
 [
   ["Reader", [
-    "Reader:AddToList",
-    "Reader:ArticleGet",
-    "Reader:FaviconRequest",
-    "Reader:ListStatusRequest",
-    "Reader:RemoveFromList",
-    "Reader:Share",
-    "Reader:ToolbarHidden",
-    "Reader:SystemUIVisibility",
-    "Reader:UpdateReaderButton",
-    "Reader:SetIntPref",
-    "Reader:SetCharPref",
+    ["Reader:AddToList", false],
+    ["Reader:ArticleGet", false],
+    ["Reader:DropdownClosed", true], // 'true' allows us to survive mid-air cycle-collection.
+    ["Reader:DropdownOpened", false],
+    ["Reader:FaviconRequest", false],
+    ["Reader:ListStatusRequest", false],
+    ["Reader:RemoveFromList", false],
+    ["Reader:Share", false],
+    ["Reader:ToolbarHidden", false],
+    ["Reader:SystemUIVisibility", false],
+    ["Reader:UpdateReaderButton", false],
+    ["Reader:SetIntPref", false],
+    ["Reader:SetCharPref", false],
   ], "chrome://browser/content/Reader.js"],
 ].forEach(aScript => {
   let [name, messages, script] = aScript;
   XPCOMUtils.defineLazyGetter(window, name, function() {
     let sandbox = {};
     Services.scriptloader.loadSubScript(script, sandbox);
     return sandbox[name];
   });
 
   let mm = window.getGroupMessageManager("browsers");
   let listener = (message) => {
     mm.removeMessageListener(message.name, listener);
-    mm.addMessageListener(message.name, window[name]);
+    let listenAfterClose = false;
+    for (let [name, laClose] of messages) {
+      if (message.name === name) {
+        listenAfterClose = laClose;
+        break;
+      }
+    }
+
+    mm.addMessageListener(message.name, window[name], listenAfterClose);
     window[name].receiveMessage(message);
   };
+
   messages.forEach((message) => {
-    mm.addMessageListener(message, listener);
+    let [name, listenAfterClose] = message;
+    mm.addMessageListener(name, listener, listenAfterClose);
   });
 });
 
 // Lazily-loaded JS modules that use observer notifications
 [
   ["Home", ["HomeBanner:Get", "HomePanels:Get", "HomePanels:Authenticate", "HomePanels:RefreshView",
             "HomePanels:Installed", "HomePanels:Uninstalled"], "resource://gre/modules/Home.jsm"],
 ].forEach(module => {
@@ -4726,16 +4738,21 @@ var BrowserEventHandler = {
     Services.obs.addObserver(this, "dom-touch-listener-added", false);
 
     BrowserApp.deck.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport, false);
     BrowserApp.deck.addEventListener("touchstart", this, true);
     BrowserApp.deck.addEventListener("MozMouseHittest", this, true);
 
     InitLater(() => BrowserApp.deck.addEventListener("click", InputWidgetHelper, true));
     InitLater(() => BrowserApp.deck.addEventListener("click", SelectHelper, true));
+
+    // ReaderViews support backPress listeners.
+    Messaging.addListener(() => {
+      return Reader.onBackPress(BrowserApp.selectedTab.id);
+    }, "Browser:OnBackPressed");
   },
 
   handleEvent: function(aEvent) {
     switch (aEvent.type) {
       case 'touchstart':
         this._handleTouchStart(aEvent);
         break;
       case 'MozMouseHittest':
--- a/testing/taskcluster/tasks/branches/base_jobs.yml
+++ b/testing/taskcluster/tasks/branches/base_jobs.yml
@@ -23,16 +23,24 @@ builds:
   emulator-kk:
     platforms:
       - b2g
     types:
       opt:
         task: tasks/builds/b2g_emulator_kk_opt.yml
       debug:
         task: tasks/builds/b2g_emulator_kk_debug.yml
+  emulator-x86-kk:
+    platforms:
+      - b2g
+    types:
+      opt:
+        task: tasks/builds/b2g_emulator_x86_kk_opt.yml
+      debug:
+        task: tasks/builds/b2g_emulator_x86_kk_debug.yml
   emulator-l:
     platforms:
       - b2g
     types:
       opt:
         task: tasks/builds/b2g_emulator_l_opt.yml
       debug:
         task: tasks/builds/b2g_emulator_l_debug.yml
--- a/testing/taskcluster/tasks/branches/mozilla-central/job_flags.yml
+++ b/testing/taskcluster/tasks/branches/mozilla-central/job_flags.yml
@@ -1,15 +1,20 @@
 ---
 # For complete sample of all build and test jobs,
 # see <gecko>/testing/taskcluster/tasks/branches/base_job_flags.yml
 
 $inherits:
   from: tasks/branches/base_jobs.yml
 
+# Flags specific to this branch
+flags:
+  post-build:
+    - simulator
+
 builds:
   android-api-11:
     platforms:
       - Android
     types:
       opt:
         task: tasks/builds/android_api_11.yml
   aries-dogfood:
@@ -29,8 +34,14 @@ builds:
   flame-kk-ota:
     platforms:
       - b2g
     types:
       opt:
         task: tasks/builds/b2g_flame_kk_ota_opt.yml
       debug:
         task: tasks/builds/b2g_flame_kk_ota_debug.yml
+
+post-build:
+  simulator:
+    allowed_build_tasks:
+      - tasks/builds/mulet_linux.yml
+    task: tasks/post-builds/mulet_simulator.yml
--- a/testing/taskcluster/tasks/branches/try/job_flags.yml
+++ b/testing/taskcluster/tasks/branches/try/job_flags.yml
@@ -4,17 +4,16 @@
 
 $inherits:
   from: tasks/branches/base_job_flags.yml
 
 # Flags specific to this branch
 flags:
   post-build:
     - upload-symbols
-    - simulator
 
 builds:
   linux64_gecko:
     platforms:
       - b2g
     types:
       opt:
         task: tasks/builds/b2g_desktop_opt.yml
@@ -220,20 +219,16 @@ builds:
 post-build:
   upload-symbols:
     allowed_build_tasks:
       - tasks/builds/opt_linux64.yml
       - tasks/builds/opt_linux64_st-an.yml
       - tasks/builds/dbg_linux64.yml
       - tasks/builds/android_api_11.yml
     task: tasks/post-builds/upload_symbols.yml
-  simulator:
-    allowed_build_tasks:
-      - tasks/builds/mulet_linux.yml
-    task: tasks/post-builds/mulet_simulator.yml
 
 tests:
   cppunit:
     allowed_build_tasks:
       tasks/builds/b2g_emulator_ics_opt.yml:
         task: tasks/tests/b2g_emulator_cpp_unit.yml
       tasks/builds/b2g_emulator_ics_debug.yml:
         task: tasks/tests/b2g_emulator_cpp_unit.yml
--- a/toolkit/components/extensions/ext-cookies.js
+++ b/toolkit/components/extensions/ext-cookies.js
@@ -1,8 +1,9 @@
+const { interfaces: Ci, utils: Cu } = Components;
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
   ignoreEvent,
   runSafe,
 } = ExtensionUtils;
 
 // Cookies from private tabs currently can't be enumerated.
--- a/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm
+++ b/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm
@@ -70,17 +70,17 @@ this.InsecurePasswordUtils = {
    *   or 3) it is a request sent without any response that could alter
    *   the behavior of the page. It was decided to include the same logic
    *   here both to be consistent with MCB and to make sure we cover all
    *   "safe" protocols. Eventually, the code here and the code in MCB
    *   will be moved to a common location that will be referenced from
    *   both places. Look at
    *   https://bugzilla.mozilla.org/show_bug.cgi?id=899099 for more info.
    */
-  _checkIfURIisSecure : function(uri) {
+  checkIfURIisSecure : function(uri) {
     let isSafe = false;
     let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
     let ph = Ci.nsIProtocolHandler;
 
     if (netutil.URIChainHasFlags(uri, ph.URI_IS_LOCAL_RESOURCE) ||
         netutil.URIChainHasFlags(uri, ph.URI_DOES_NOT_RETURN_DATA) ||
         netutil.URIChainHasFlags(uri, ph.URI_INHERITS_SECURITY_CONTEXT) ||
         netutil.URIChainHasFlags(uri, ph.URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT)) {
@@ -104,17 +104,17 @@ this.InsecurePasswordUtils = {
    * inside https).
    */
   _checkForInsecureNestedDocuments : function(domDoc) {
     let uri = domDoc.documentURIObject;
     if (domDoc.defaultView == domDoc.defaultView.parent) {
       // We are at the top, nothing to check here
       return false;
     }
-    if (!this._checkIfURIisSecure(uri)) {
+    if (!this.checkIfURIisSecure(uri)) {
       // We are insecure
       return true;
     }
     // I am secure, but check my parent
     return this._checkForInsecureNestedDocuments(domDoc.defaultView.parent.document);
   },
 
 
@@ -122,17 +122,17 @@ this.InsecurePasswordUtils = {
    * Checks if there are insecure password fields present on the form's document
    * i.e. passwords inside forms with http action, inside iframes with http src,
    * or on insecure web pages. If insecure password fields are present,
    * a log message is sent to the web console to warn developers.
    */
   checkForInsecurePasswords : function (aForm) {
     var domDoc = aForm.ownerDocument;
     let pageURI = domDoc.defaultView.top.document.documentURIObject;
-    let isSafePage = this._checkIfURIisSecure(pageURI);
+    let isSafePage = this.checkIfURIisSecure(pageURI);
 
     if (!isSafePage) {
       this._sendWebConsoleMessage("InsecurePasswordsPresentOnPage", domDoc);
     }
 
     // Check if we are on an iframe with insecure src, or inside another
     // insecure iframe or document.
     if (this._checkForInsecureNestedDocuments(domDoc)) {
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -10,23 +10,25 @@ this.EXPORTED_SYMBOLS = [ "LoginManagerC
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 const PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS = 1;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
+                                  "resource://gre/modules/DeferredTask.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
+                                  "resource://gre/modules/InsecurePasswordUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+                                  "resource://gre/modules/LoginHelper.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoginRecipesContent",
                                   "resource://gre/modules/LoginRecipes.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
-                                  "resource://gre/modules/LoginHelper.jsm");
-
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let logger = LoginHelper.createLogger("LoginManagerContent");
   return logger.log.bind(logger);
 });
 
 // These mirror signon.* prefs.
 var gEnabled, gAutofillForms, gStoreWhenAutocompleteOff;
 
@@ -409,25 +411,38 @@ var LoginManagerContent = {
         let loginForm = getFirstLoginForm(frame);
         if (loginForm) {
           return loginForm;
         }
       }
       return null;
     };
 
+    // Returns true if this window or any subframes have insecure login forms.
+    let hasInsecureLoginForms = (thisWindow, parentIsInsecure) => {
+      let doc = thisWindow.document;
+      let isInsecure =
+          parentIsInsecure ||
+          !InsecurePasswordUtils.checkIfURIisSecure(doc.documentURIObject);
+      let hasLoginForm = !!this.stateForDocument(doc).loginForm;
+      return (hasLoginForm && isInsecure) ||
+             Array.some(thisWindow.frames,
+                        frame => hasInsecureLoginForms(frame, isInsecure));
+    };
+
     // Store the actual form to use on the state for the top-level document.
     let topState = this.stateForDocument(topWindow.document);
     topState.loginFormForFill = getFirstLoginForm(topWindow);
 
     // Determine whether to show the anchor icon for the current tab.
     let messageManager = messageManagerFromWindow(topWindow);
     messageManager.sendAsyncMessage("RemoteLogins:updateLoginFormPresence", {
       loginFormOrigin,
       loginFormPresent: !!topState.loginFormForFill,
+      hasInsecureLoginForms: hasInsecureLoginForms(topWindow, false),
     });
   },
 
   /**
    * Perform a password fill upon user request coming from the parent process.
    * The fill will be in the form previously identified during page navigation.
    *
    * @param An object with the following properties:
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -565,31 +565,47 @@ var LoginManagerParent = {
     if (!loginFormState) {
       loginFormState = {};
       this.loginFormStateByBrowser.set(browser, loginFormState);
     }
     return loginFormState;
   },
 
   /**
+   * Returns true if the page currently loaded in the given browser element has
+   * insecure login forms. This state may be updated asynchronously, in which
+   * case a custom event named InsecureLoginFormsStateChange will be dispatched
+   * on the browser element.
+   */
+  hasInsecureLoginForms(browser) {
+    return !!this.stateForBrowser(browser).hasInsecureLoginForms;
+  },
+
+  /**
    * Called to indicate whether a login form on the currently loaded page is
    * present or not. This is one of the factors used to control the visibility
    * of the password fill doorhanger.
    */
-  updateLoginFormPresence(browser, { loginFormOrigin, loginFormPresent }) {
+  updateLoginFormPresence(browser, { loginFormOrigin, loginFormPresent,
+                                     hasInsecureLoginForms }) {
     const ANCHOR_DELAY_MS = 200;
 
     let state = this.stateForBrowser(browser);
 
     // Update the data to use to the latest known values. Since messages are
     // processed in order, this will always be the latest version to use.
     state.loginFormOrigin = loginFormOrigin;
     state.loginFormPresent = loginFormPresent;
+    state.hasInsecureLoginForms = hasInsecureLoginForms;
 
-    // Apply the data to the currently displayed icon later.
+    // Report the insecure login form state immediately.
+    browser.dispatchEvent(new browser.ownerDocument.defaultView
+                                 .CustomEvent("InsecureLoginFormsStateChange"));
+
+    // Apply the data to the currently displayed login fill icon later.
     if (!state.anchorDeferredTask) {
       state.anchorDeferredTask = new DeferredTask(
         () => this.updateLoginAnchor(browser),
         ANCHOR_DELAY_MS
       );
     }
     state.anchorDeferredTask.arm();
   },
--- a/toolkit/components/passwordmgr/test/browser/browser.ini
+++ b/toolkit/components/passwordmgr/test/browser/browser.ini
@@ -1,17 +1,20 @@
 [DEFAULT]
 support-files =
   authenticate.sjs
   form_basic.html
+  insecure_test.html
+  insecure_test_subframe.html
   multiple_forms.html
 
 [browser_DOMFormHasPassword.js]
 [browser_DOMInputPasswordAdded.js]
 [browser_filldoorhanger.js]
+[browser_hasInsecureLoginForms.js]
 [browser_notifications.js]
 skip-if = true # Intermittent failures: Bug 1182296, bug 1148771
 [browser_passwordmgr_editing.js]
 skip-if = os == "linux"
 [browser_context_menu.js]
 skip-if = os == "linux"
 [browser_passwordmgr_contextmenu.js]
 [browser_passwordmgr_fields.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/browser_hasInsecureLoginForms.js
@@ -0,0 +1,93 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource://gre/modules/LoginManagerParent.jsm", this);
+
+const testUrlPath =
+      "://example.com/browser/toolkit/components/passwordmgr/test/browser/";
+
+/**
+ * Waits for the given number of occurrences of InsecureLoginFormsStateChange
+ * on the given browser element.
+ */
+function waitForInsecureLoginFormsStateChange(browser, count) {
+  return BrowserTestUtils.waitForEvent(browser, "InsecureLoginFormsStateChange",
+                                       false, () => --count == 0);
+}
+
+/**
+ * Checks that hasInsecureLoginForms is true for a simple HTTP page and false
+ * for a simple HTTPS page.
+ */
+add_task(function* test_simple() {
+  for (let scheme of ["http", "https"]) {
+    let tab = gBrowser.addTab(scheme + testUrlPath + "form_basic.html");
+    let browser = tab.linkedBrowser;
+    yield Promise.all([
+      BrowserTestUtils.switchTab(gBrowser, tab),
+      BrowserTestUtils.browserLoaded(browser),
+      // One event is triggered by pageshow and one by DOMFormHasPassword.
+      waitForInsecureLoginFormsStateChange(browser, 2),
+    ]);
+
+    Assert.equal(LoginManagerParent.hasInsecureLoginForms(browser),
+                 scheme == "http");
+
+    gBrowser.removeTab(tab);
+  }
+});
+
+/**
+ * Checks that hasInsecureLoginForms is true if a password field is present in
+ * an HTTP page loaded as a subframe of a top-level HTTPS page, when mixed
+ * active content blocking is disabled.
+ *
+ * When the subframe is navigated to an HTTPS page, hasInsecureLoginForms should
+ * be set to false.
+ *
+ * Moving back in history should set hasInsecureLoginForms to true again.
+ */
+add_task(function* test_subframe_navigation() {
+  yield new Promise(resolve => SpecialPowers.pushPrefEnv({
+    "set": [["security.mixed_content.block_active_content", false]],
+  }, resolve));
+
+  // Load the page with the subframe in a new tab.
+  let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html");
+  let browser = tab.linkedBrowser;
+  yield Promise.all([
+    BrowserTestUtils.switchTab(gBrowser, tab),
+    BrowserTestUtils.browserLoaded(browser),
+    // Two events are triggered by pageshow and one by DOMFormHasPassword.
+    waitForInsecureLoginFormsStateChange(browser, 3),
+  ]);
+
+  Assert.ok(LoginManagerParent.hasInsecureLoginForms(browser));
+
+  // Navigate the subframe to a secure page.
+  let promiseSubframeReady = Promise.all([
+    BrowserTestUtils.browserLoaded(browser, true),
+    // One event is triggered by pageshow and one by DOMFormHasPassword.
+    waitForInsecureLoginFormsStateChange(browser, 2),
+  ]);
+  yield ContentTask.spawn(browser, null, function* () {
+    content.document.getElementById("test-iframe")
+           .contentDocument.getElementById("test-link").click();
+  });
+  yield promiseSubframeReady;
+
+  Assert.ok(!LoginManagerParent.hasInsecureLoginForms(browser));
+
+  // Navigate back to the insecure page. We only have to wait for the
+  // InsecureLoginFormsStateChange event that is triggered by pageshow.
+  let promise = waitForInsecureLoginFormsStateChange(browser, 1);
+  yield ContentTask.spawn(browser, null, function* () {
+    content.document.getElementById("test-iframe")
+           .contentWindow.history.back();
+  });
+  yield promise;
+
+  Assert.ok(LoginManagerParent.hasInsecureLoginForms(browser));
+
+  gBrowser.removeTab(tab);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/insecure_test.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<!-- This frame is initially loaded over HTTP. -->
+<iframe id="test-iframe"
+        src="http://example.org/browser/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html"/>
+
+</body></html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<form>
+  <input name="password" type="password">
+</form>
+
+<!-- Link to reload this page over HTTPS. -->
+<a id="test-link"
+   href="https://example.org/browser/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html">HTTPS</a>
+
+</body></html>
--- a/toolkit/components/reader/AboutReader.jsm
+++ b/toolkit/components/reader/AboutReader.jsm
@@ -33,16 +33,17 @@ var AboutReader = function(mm, win, arti
 
   let doc = win.document;
 
   this._mm = mm;
   this._mm.addMessageListener("Reader:Added", this);
   this._mm.addMessageListener("Reader:Removed", this);
   this._mm.addMessageListener("Sidebar:VisibilityChange", this);
   this._mm.addMessageListener("ReadingList:VisibilityStatus", this);
+  this._mm.addMessageListener("Reader:CloseDropdown", this);
 
   this._docRef = Cu.getWeakReference(doc);
   this._winRef = Cu.getWeakReference(win);
 
   this._article = null;
 
   if (articlePromise) {
     this._articlePromise = articlePromise;
@@ -173,28 +174,45 @@ AboutReader.prototype = {
 
   get _isToolbarVertical() {
     if (this._toolbarVertical !== undefined) {
       return this._toolbarVertical;
     }
     return this._toolbarVertical = Services.prefs.getBoolPref("reader.toolbar.vertical");
   },
 
+  // Provides unique view Id.
+  get viewId() {
+    let _viewId = Cc["@mozilla.org/uuid-generator;1"].
+      getService(Ci.nsIUUIDGenerator).generateUUID().toString();
+    Object.defineProperty(this, "viewId", { value: _viewId });
+
+    return _viewId;
+  },
+
   receiveMessage: function (message) {
     switch (message.name) {
       case "Reader:Added": {
         // Page can be added by long-press pageAction, or by tap on banner icon.
         if (message.data.url == this._article.url) {
           if (this._isReadingListItem != 1) {
             this._isReadingListItem = 1;
             this._updateToggleButton();
           }
         }
         break;
       }
+
+      // Triggered by Android user pressing BACK while the banner font-dropdown is open.
+      case "Reader:CloseDropdown": {
+        // Just close it.
+        this._closeDropdown();
+        break;
+      }
+
       case "Reader:Removed": {
         if (message.data.url == this._article.url) {
           if (this._isReadingListItem != 0) {
             this._isReadingListItem = 0;
             this._updateToggleButton();
           }
         }
         break;
@@ -241,20 +259,24 @@ AboutReader.prototype = {
         this._handleDeviceLight(aEvent.value);
         break;
 
       case "visibilitychange":
         this._handleVisibilityChange();
         break;
 
       case "unload":
+        // Close the Banners Font-dropdown, cleanup Android BackPressListener.
+        this._closeDropdown();
+
         this._mm.removeMessageListener("Reader:Added", this);
         this._mm.removeMessageListener("Reader:Removed", this);
         this._mm.removeMessageListener("Sidebar:VisibilityChange", this);
         this._mm.removeMessageListener("ReadingList:VisibilityStatus", this);
+        this._mm.removeMessageListener("Reader:CloseDropdown", this);
         this._windowUnloaded = true;
         break;
     }
   },
 
   _updateToggleButton: function() {
     let button = this._doc.getElementById("toggle-button");
 
@@ -579,18 +601,17 @@ AboutReader.prototype = {
     });
   },
 
   _getToolbarVisibility: function() {
     return this._toolbarElement.hasAttribute("visible");
   },
 
   _setToolbarVisibility: function(visible) {
-    let dropdown = this._doc.getElementById("style-dropdown");
-    dropdown.classList.remove("open");
+    this._closeDropdown();
 
     if (this._getToolbarVisibility() === visible) {
       return;
     }
 
     if (visible) {
       this._toolbarElement.setAttribute("visible", true);
     } else {
@@ -938,18 +959,46 @@ AboutReader.prototype = {
     dropdownToggle.setAttribute("title", gStrings.GetStringFromName("aboutReader.toolbar.typeControls"));
     dropdownToggle.addEventListener("click", event => {
       if (!event.isTrusted)
         return;
 
       event.stopPropagation();
 
       if (dropdown.classList.contains("open")) {
-        dropdown.classList.remove("open");
+        this._closeDropdown();
       } else {
-        dropdown.classList.add("open");
+        this._openDropdown();
         if (this._isToolbarVertical) {
           updatePopupPosition();
         }
       }
     }, true);
   },
+
+  /*
+   * If the ReaderView banner font-dropdown is closed, open it.
+   */
+  _openDropdown: function() {
+    let dropdown = this._doc.getElementById("style-dropdown");
+    if (dropdown.classList.contains("open")) {
+      return;
+    }
+
+    // Trigger BackPressListener initialization in Android.
+    dropdown.classList.add("open");
+    this._mm.sendAsyncMessage("Reader:DropdownOpened", this.viewId);
+  },
+
+  /*
+   * If the ReaderView banner font-dropdown is opened, close it.
+   */
+  _closeDropdown: function() {
+    let dropdown = this._doc.getElementById("style-dropdown");
+    if (!dropdown.classList.contains("open")) {
+      return;
+    }
+
+    // Trigger BackPressListener cleanup in Android.
+    dropdown.classList.remove("open");
+    this._mm.sendAsyncMessage("Reader:DropdownClosed", this.viewId);
+  },
 };
--- a/toolkit/components/telemetry/TelemetryEnvironment.jsm
+++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm
@@ -33,16 +33,19 @@ if (AppConstants.platform !== "gonk") {
 }
 XPCOMUtils.defineLazyModuleGetter(this, "ProfileAge",
                                   "resource://gre/modules/ProfileAge.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
                                   "resource://gre/modules/UpdateUtils.jsm");
 
 const CHANGE_THROTTLE_INTERVAL_MS = 5 * 60 * 1000;
 
+// The maximum length of a string (e.g. description) in the addons section.
+const MAX_ADDON_STRING_LENGTH = 100;
+
 /**
  * This is a policy object used to override behavior for testing.
  */
 var Policy = {
   now: () => new Date(),
 };
 
 var gGlobalEnvironment;
@@ -245,16 +248,31 @@ function getGfxField(aPropertyName, aDef
       return gfxProp;
     }
   } catch (e) {}
 
   return aDefault;
 }
 
 /**
+ * Returns a substring of the input string.
+ *
+ * @param {String} aString The input string.
+ * @param {Integer} aMaxLength The maximum length of the returned substring. If this is
+ *        greater than the length of the input string, we return the whole input string.
+ * @return {String} The substring or null if the input string is null.
+ */
+function limitStringToLength(aString, aMaxLength) {
+  if (aString === null) {
+    return null;
+  }
+  return aString.substring(0, aMaxLength);
+}
+
+/**
  * Get the information about a graphic adapter.
  *
  * @param aSuffix A suffix to add to the properties names.
  * @return An object containing the adapter properties.
  */
 function getGfxAdapter(aSuffix = "") {
   // Note that gfxInfo, and so getGfxField, might return "Unknown" for the RAM on failures,
   // not null.
@@ -495,21 +513,21 @@ EnvironmentAddonBuilder.prototype = {
       }
 
       // Make sure to have valid dates.
       let installDate = new Date(Math.max(0, addon.installDate));
       let updateDate = new Date(Math.max(0, addon.updateDate));
 
       activeAddons[addon.id] = {
         blocklisted: (addon.blocklistState !== Ci.nsIBlocklistService.STATE_NOT_BLOCKED),
-        description: addon.description,
-        name: addon.name,
+        description: limitStringToLength(addon.description, MAX_ADDON_STRING_LENGTH),
+        name: limitStringToLength(addon.name, MAX_ADDON_STRING_LENGTH),
         userDisabled: addon.userDisabled,
         appDisabled: addon.appDisabled,
-        version: addon.version,
+        version: limitStringToLength(addon.version, MAX_ADDON_STRING_LENGTH),
         scope: addon.scope,
         type: addon.type,
         foreignInstall: addon.foreignInstall,
         hasBinaryComponents: addon.hasBinaryComponents,
         installDay: Utils.millisecondsToDays(installDate.getTime()),
         updateDay: Utils.millisecondsToDays(updateDate.getTime()),
         signedState: addon.signedState,
       };
@@ -535,21 +553,21 @@ EnvironmentAddonBuilder.prototype = {
     if (theme) {
       // Make sure to have valid dates.
       let installDate = new Date(Math.max(0, theme.installDate));
       let updateDate = new Date(Math.max(0, theme.updateDate));
 
       activeTheme = {
         id: theme.id,
         blocklisted: (theme.blocklistState !== Ci.nsIBlocklistService.STATE_NOT_BLOCKED),
-        description: theme.description,
-        name: theme.name,
+        description: limitStringToLength(theme.description, MAX_ADDON_STRING_LENGTH),
+        name: limitStringToLength(theme.name, MAX_ADDON_STRING_LENGTH),
         userDisabled: theme.userDisabled,
         appDisabled: theme.appDisabled,
-        version: theme.version,
+        version: limitStringToLength(theme.version, MAX_ADDON_STRING_LENGTH),
         scope: theme.scope,
         foreignInstall: theme.foreignInstall,
         hasBinaryComponents: theme.hasBinaryComponents,
         installDay: Utils.millisecondsToDays(installDate.getTime()),
         updateDay: Utils.millisecondsToDays(updateDate.getTime()),
       };
     }
 
@@ -570,19 +588,19 @@ EnvironmentAddonBuilder.prototype = {
       if (tag.disabled) {
         continue;
       }
 
       // Make sure to have a valid date.
       let updateDate = new Date(Math.max(0, tag.lastModifiedTime));
 
       activePlugins.push({
-        name: tag.name,
-        version: tag.version,
-        description: tag.description,
+        name: limitStringToLength(tag.name, MAX_ADDON_STRING_LENGTH),
+        version: limitStringToLength(tag.version, MAX_ADDON_STRING_LENGTH),
+        description: limitStringToLength(tag.description, MAX_ADDON_STRING_LENGTH),
         blocklisted: tag.blocklisted,
         disabled: tag.disabled,
         clicktoplay: tag.clicktoplay,
         mimeTypes: tag.getMimeTypes({}),
         updateDay: Utils.millisecondsToDays(updateDate.getTime()),
       });
     }
 
--- a/toolkit/components/telemetry/docs/environment.rst
+++ b/toolkit/components/telemetry/docs/environment.rst
@@ -294,8 +294,13 @@ The following is a partial list of colle
 
 - ``browser.search.suggest.enabled``: The "master switch" for search suggestions everywhere in Firefox (search bar, urlbar, etc.). Defaults to true.
 
 - ``browser.urlbar.suggest.searches``: True if search suggestions are enabled in the urlbar. Defaults to false.
 
 - ``browser.urlbar.unifiedcomplete``: True if the urlbar's UnifiedComplete back-end is enabled.
 
 - ``browser.urlbar.userMadeSearchSuggestionsChoice``: True if the user has clicked Yes or No in the urlbar's opt-in notification. Defaults to false.
+
+activeAddons
+~~~~~~~~~~~~
+
+Starting from Firefox 44, the length of the following string fields: ``name``, ``description`` and ``version`` is limited to 100 characters. The same limitation applies to the same fields in ``theme`` and ``activePlugins``.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/addons/long-fields/install.rdf
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>tel-longfields-xpi@tests.mozilla.org</em:id>
+    <em:version>This is a really long addon version, that will get limited to 100 characters. We're much longer, we're at about 116.</em:version>
+
+    <em:targetApplication>
+      <Description>
+        <em:id>toolkit@mozilla.org</em:id>
+        <em:minVersion>0</em:minVersion>
+        <em:maxVersion>*</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+    <!-- Front End MetaData -->
+    <em:name>This is a really long addon name, that will get limited to 100 characters. We're much longer, we're at about 219. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus nullam sodales. Yeah, Latin placeholder.</em:name>
+    <em:description>This is a really long addon description, that will get limited to 100 characters. We're much longer, we're at about 200. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus nullam sodales.</em:description>
+    <em:bootstrap>true</em:bootstrap>
+
+  </Description>
+</RDF>
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
@@ -1012,16 +1012,47 @@ add_task(function* test_signedAddon() {
   // Check addon data.
   Assert.ok(ADDON_ID in data.addons.activeAddons, "Add-on should be in the environment.");
   let targetAddon = data.addons.activeAddons[ADDON_ID];
   for (let f in EXPECTED_ADDON_DATA) {
     Assert.equal(targetAddon[f], EXPECTED_ADDON_DATA[f], f + " must have the correct value.");
   }
 });
 
+add_task(function* test_addonsFieldsLimit() {
+  const ADDON_INSTALL_URL = gDataRoot + "long-fields.xpi";
+  const ADDON_ID = "tel-longfields-xpi@tests.mozilla.org";
+
+  // Set the clock in the future so our changes don't get throttled.
+  gNow = fakeNow(futureDate(gNow, 10 * MILLISECONDS_PER_MINUTE));
+
+  // Install the addon and wait for the TelemetryEnvironment to pick it up.
+  let deferred = PromiseUtils.defer();
+  TelemetryEnvironment.registerChangeListener("test_longFieldsAddon", deferred.resolve);
+  yield AddonTestUtils.installXPIFromURL(ADDON_INSTALL_URL);
+  yield deferred.promise;
+  TelemetryEnvironment.unregisterChangeListener("test_longFieldsAddon");
+
+  let data = TelemetryEnvironment.currentEnvironment;
+  checkEnvironmentData(data);
+
+  // Check that the addon is available and that the string fields are limited.
+  Assert.ok(ADDON_ID in data.addons.activeAddons, "Add-on should be in the environment.");
+  let targetAddon = data.addons.activeAddons[ADDON_ID];
+
+  // TelemetryEnvironment limits the length of string fields for activeAddons to 100 chars,
+  // to mitigate misbehaving addons.
+  Assert.lessOrEqual(targetAddon.version.length, 100,
+               "The version string must have been limited");
+  Assert.lessOrEqual(targetAddon.name.length, 100,
+               "The name string must have been limited");
+  Assert.lessOrEqual(targetAddon.description.length, 100,
+               "The description string must have been limited");
+});
+
 add_task(function* test_changeThrottling() {
   const PREF_TEST = "toolkit.telemetry.test.pref1";
   const PREFS_TO_WATCH = new Map([
     [PREF_TEST, {what: TelemetryEnvironment.RECORD_PREF_STATE}],
   ]);
   Preferences.reset(PREF_TEST);
 
   gNow = futureDate(gNow, 10 * MILLISECONDS_PER_MINUTE);
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -1096,62 +1096,101 @@ AndroidBridge::DeleteMessage(int32_t aMe
 
     uint32_t requestId;
     if (!QueueSmsRequest(aRequest, &requestId))
         return;
 
     GeckoAppShell::DeleteMessageWrapper(aMessageId, requestId);
 }
 
-void
-AndroidBridge::CreateMessageList(const dom::mobilemessage::SmsFilterData& aFilter, bool aReverse,
-                                 nsIMobileMessageCallback* aRequest)
+NS_IMPL_ISUPPORTS0(MessageCursorContinueCallback)
+
+NS_IMETHODIMP
+MessageCursorContinueCallback::HandleContinue()
 {
-    ALOG_BRIDGE("AndroidBridge::CreateMessageList");
+    GeckoAppShell::GetNextMessageWrapper(mRequestId);
+    return NS_OK;
+}
+
+already_AddRefed<nsICursorContinueCallback>
+AndroidBridge::CreateMessageCursor(bool aHasStartDate,
+                                   uint64_t aStartDate,
+                                   bool aHasEndDate,
+                                   uint64_t aEndDate,
+                                   const char16_t** aNumbers,
+                                   uint32_t aNumbersCount,
+                                   const nsAString& aDelivery,
+                                   bool aHasRead,
+                                   bool aRead,
+                                   bool aHasThreadId,
+                                   uint64_t aThreadId,
+                                   bool aReverse,
+                                   nsIMobileMessageCursorCallback* aRequest)
+{
+    ALOG_BRIDGE("AndroidBridge::CreateMessageCursor");
 
     JNIEnv* const env = jni::GetGeckoThreadEnv();
 
     uint32_t requestId;
-    if (!QueueSmsRequest(aRequest, &requestId))
-        return;
+    if (!QueueSmsCursorRequest(aRequest, &requestId))
+        return nullptr;
 
     AutoLocalJNIFrame jniFrame(env, 2);
 
     jobjectArray numbers =
-        (jobjectArray)env->NewObjectArray(aFilter.numbers().Length(),
+        (jobjectArray)env->NewObjectArray(aNumbersCount,
                                           jStringClass,
                                           NewJavaString(&jniFrame, EmptyString()));
 
-    for (uint32_t i = 0; i < aFilter.numbers().Length(); ++i) {
-        jstring elem = NewJavaString(&jniFrame, aFilter.numbers()[i]);
+    for (uint32_t i = 0; i < aNumbersCount; ++i) {
+        jstring elem = NewJavaString(&jniFrame, nsDependentString(aNumbers[i]));
         env->SetObjectArrayElement(numbers, i, elem);
         env->DeleteLocalRef(elem);
     }
 
-    int64_t startDate = aFilter.hasStartDate() ? aFilter.startDate() : -1;
-    int64_t endDate = aFilter.hasEndDate() ? aFilter.endDate() : -1;
-    GeckoAppShell::CreateMessageListWrapper(startDate, endDate,
-                                            ObjectArray::Ref::From(numbers),
-                                            aFilter.numbers().Length(),
-                                            aFilter.delivery(),
-                                            aFilter.hasRead(), aFilter.read(),
-                                            aFilter.threadId(),
-                                            aReverse, requestId);
+    int64_t startDate = aHasStartDate ? aStartDate : -1;
+    int64_t endDate = aHasEndDate ? aEndDate : -1;
+    GeckoAppShell::CreateMessageCursorWrapper(startDate, endDate,
+                                              ObjectArray::Ref::From(numbers),
+                                              aNumbersCount,
+                                              aDelivery,
+                                              aHasRead, aRead,
+                                              aHasThreadId, aThreadId,
+                                              aReverse,
+                                              requestId);
+
+    nsCOMPtr<nsICursorContinueCallback> callback = 
+       new MessageCursorContinueCallback(requestId);
+    return callback.forget();
 }
 
-void
-AndroidBridge::GetNextMessageInList(int32_t aListId, nsIMobileMessageCallback* aRequest)
+NS_IMPL_ISUPPORTS0(ThreadCursorContinueCallback)
+
+NS_IMETHODIMP
+ThreadCursorContinueCallback::HandleContinue()
 {
-    ALOG_BRIDGE("AndroidBridge::GetNextMessageInList");
+    GeckoAppShell::GetNextThreadWrapper(mRequestId);
+    return NS_OK;
+}
+
+already_AddRefed<nsICursorContinueCallback>
+AndroidBridge::CreateThreadCursor(nsIMobileMessageCursorCallback* aRequest)
+{
+    ALOG_BRIDGE("AndroidBridge::CreateThreadCursor");
 
     uint32_t requestId;
-    if (!QueueSmsRequest(aRequest, &requestId))
-        return;
+    if (!QueueSmsCursorRequest(aRequest, &requestId)) {
+        return nullptr;
+    }
 
-    GeckoAppShell::GetNextMessageInListWrapper(aListId, requestId);
+    GeckoAppShell::CreateThreadCursorWrapper(requestId);
+
+    nsCOMPtr<nsICursorContinueCallback> callback =
+        new ThreadCursorContinueCallback(requestId);
+    return callback.forget();
 }
 
 bool
 AndroidBridge::QueueSmsRequest(nsIMobileMessageCallback* aRequest, uint32_t* aRequestIdOut)
 {
     MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
     MOZ_ASSERT(aRequest && aRequestIdOut);
 
@@ -1179,16 +1218,67 @@ AndroidBridge::DequeueSmsRequest(uint32_
     MOZ_ASSERT(aRequestId < mSmsRequests.Length());
     if (aRequestId >= mSmsRequests.Length()) {
         return nullptr;
     }
 
     return mSmsRequests[aRequestId].forget();
 }
 
+bool
+AndroidBridge::QueueSmsCursorRequest(nsIMobileMessageCursorCallback* aRequest,
+                                     uint32_t* aRequestIdOut)
+{
+    MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+    MOZ_ASSERT(aRequest && aRequestIdOut);
+
+    const uint32_t length = mSmsCursorRequests.Length();
+    for (uint32_t i = 0; i < length; i++) {
+        if (!(mSmsCursorRequests)[i]) {
+            (mSmsCursorRequests)[i] = aRequest;
+            *aRequestIdOut = i;
+            return true;
+        }
+    }
+
+    mSmsCursorRequests.AppendElement(aRequest);
+
+    // After AppendElement(), previous `length` points to the new tail element.
+    *aRequestIdOut = length;
+    return true;
+}
+
+nsCOMPtr<nsIMobileMessageCursorCallback>
+AndroidBridge::GetSmsCursorRequest(uint32_t aRequestId)
+{
+    MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+
+    MOZ_ASSERT(aRequestId < mSmsCursorRequests.Length());
+    if (aRequestId >= mSmsCursorRequests.Length()) {
+        return nullptr;
+    }
+
+    // TODO: remove on final dequeue
+    return mSmsCursorRequests[aRequestId];
+}
+
+already_AddRefed<nsIMobileMessageCursorCallback>
+AndroidBridge::DequeueSmsCursorRequest(uint32_t aRequestId)
+{
+    MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+
+    MOZ_ASSERT(aRequestId < mSmsCursorRequests.Length());
+    if (aRequestId >= mSmsCursorRequests.Length()) {
+        return nullptr;
+    }
+
+    // TODO: remove on final dequeue
+    return mSmsCursorRequests[aRequestId].forget();
+}
+
 void
 AndroidBridge::GetCurrentNetworkInformation(hal::NetworkInformation* aNetworkInfo)
 {
     ALOG_BRIDGE("AndroidBridge::GetCurrentNetworkInformation");
 
     // To prevent calling too many methods through JNI, the Java method returns
     // an array of double even if we actually want an integer, a boolean, and an integer.
 
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -20,16 +20,18 @@
 #include "nsIMutableArray.h"
 #include "nsIMIMEInfo.h"
 #include "nsColor.h"
 #include "gfxRect.h"
 #include "mozilla/gfx/Point.h"
 
 #include "nsIAndroidBridge.h"
 #include "nsIMobileMessageCallback.h"
+#include "nsIMobileMessageCursorCallback.h"
+#include "nsIDOMDOMCursor.h"
 
 #include "mozilla/Likely.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Types.h"
 #include "mozilla/jni/Utils.h"
 
 // Some debug #defines
@@ -69,25 +71,16 @@ typedef struct AndroidSystemColors {
     nscolor textColorTertiaryInverse;
     nscolor textColorHighlight;
     nscolor colorForeground;
     nscolor colorBackground;
     nscolor panelColorForeground;
     nscolor panelColorBackground;
 } AndroidSystemColors;
 
-class nsFilePickerCallback : nsISupports {
-public:
-    NS_DECL_THREADSAFE_ISUPPORTS
-    virtual void handleResult(nsAString& filePath) = 0;
-    nsFilePickerCallback() {}
-protected:
-    virtual ~nsFilePickerCallback() {}
-};
-
 class DelayedTask {
 public:
     DelayedTask(Task* aTask, int aDelayMs) {
         mTask = aTask;
         mRunTime = mozilla::TimeStamp::Now() + mozilla::TimeDuration::FromMilliseconds(aDelayMs);
     }
 
     bool IsEarlierThan(DelayedTask *aOther) {
@@ -103,16 +96,52 @@ public:
         return mTask;
     }
 
 private:
     Task* mTask;
     mozilla::TimeStamp mRunTime;
 };
 
+class ThreadCursorContinueCallback : public nsICursorContinueCallback
+{
+public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSICURSORCONTINUECALLBACK
+
+    ThreadCursorContinueCallback(int aRequestId)
+        : mRequestId(aRequestId)
+    {
+    }
+private:
+    virtual ~ThreadCursorContinueCallback()
+    {
+    }
+
+    int mRequestId;
+};
+
+class MessageCursorContinueCallback : public nsICursorContinueCallback
+{
+public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSICURSORCONTINUECALLBACK
+
+    MessageCursorContinueCallback(int aRequestId)
+        : mRequestId(aRequestId)
+    {
+    }
+private:
+    virtual ~MessageCursorContinueCallback()
+    {
+    }
+
+    int mRequestId;
+};
+
 class AndroidBridge final
 {
 public:
     enum {
         // Values for NotifyIME, in addition to values from the Gecko
         // IMEMessage enum; use negative values here to prevent conflict
         NOTIFY_IME_OPEN_VKB = -2,
         NOTIFY_IME_REPLY_EVENT = -1,
@@ -236,20 +265,35 @@ public:
     void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo);
 
     nsresult GetSegmentInfoForText(const nsAString& aText,
                                    nsIMobileMessageCallback* aRequest);
     void SendMessage(const nsAString& aNumber, const nsAString& aText,
                      nsIMobileMessageCallback* aRequest);
     void GetMessage(int32_t aMessageId, nsIMobileMessageCallback* aRequest);
     void DeleteMessage(int32_t aMessageId, nsIMobileMessageCallback* aRequest);
-    void CreateMessageList(const dom::mobilemessage::SmsFilterData& aFilter,
-                           bool aReverse, nsIMobileMessageCallback* aRequest);
-    void GetNextMessageInList(int32_t aListId, nsIMobileMessageCallback* aRequest);
+    already_AddRefed<nsICursorContinueCallback>
+    CreateMessageCursor(bool aHasStartDate,
+                        uint64_t aStartDate,
+                        bool aHasEndDate,
+                        uint64_t aEndDate,
+                        const char16_t** aNumbers,
+                        uint32_t aNumbersCount,
+                        const nsAString& aDelivery,
+                        bool aHasRead,
+                        bool aRead,
+                        bool aHasThreadId,
+                        uint64_t aThreadId,
+                        bool aReverse,
+                        nsIMobileMessageCursorCallback* aRequest);
+    already_AddRefed<nsICursorContinueCallback>
+    CreateThreadCursor(nsIMobileMessageCursorCallback* aRequest);
     already_AddRefed<nsIMobileMessageCallback> DequeueSmsRequest(uint32_t aRequestId);
+    nsCOMPtr<nsIMobileMessageCursorCallback> GetSmsCursorRequest(uint32_t aRequestId);
+    already_AddRefed<nsIMobileMessageCursorCallback> DequeueSmsCursorRequest(uint32_t aRequestId);
 
     void GetCurrentNetworkInformation(hal::NetworkInformation* aNetworkInfo);
 
     void SetFirstPaintViewport(const LayerIntPoint& aOffset, const CSSToLayerScale& aZoom, const CSSRect& aCssPageRect);
     void SetPageRect(const CSSRect& aCssPageRect);
     void SyncViewportInfo(const LayerIntRect& aDisplayPort, const CSSToLayerScale& aDisplayResolution,
                           bool aLayersUpdated, int32_t aPaintSyncId, ParentLayerRect& aScrollRect, CSSToParentLayerScale& aScale,
                           ScreenMargin& aFixedLayerMargins);
@@ -308,17 +352,18 @@ public:
 
     static nsresult GetExternalPublicDirectory(const nsAString& aType, nsAString& aPath);
 
 protected:
     static nsDataHashtable<nsStringHashKey, nsString> sStoragePaths;
 
     static pthread_t sJavaUiThread;
     static AndroidBridge* sBridge;
-    nsTArray<nsCOMPtr<nsIMobileMessageCallback> > mSmsRequests;
+    nsTArray<nsCOMPtr<nsIMobileMessageCallback>> mSmsRequests;
+    nsTArray<nsCOMPtr<nsIMobileMessageCursorCallback>> mSmsCursorRequests;
 
     widget::GeckoLayerClient::GlobalRef mLayerClient;
 
     // the android.telephony.SmsMessage class
     jclass mAndroidSmsMessageClass;
 
     AndroidBridge();
     ~AndroidBridge();
@@ -329,16 +374,17 @@ protected:
 
     bool mHasNativeBitmapAccess;
     bool mHasNativeWindowAccess;
     bool mHasNativeWindowFallback;
 
     int mAPIVersion;
 
     bool QueueSmsRequest(nsIMobileMessageCallback* aRequest, uint32_t* aRequestIdOut);
+    bool QueueSmsCursorRequest(nsIMobileMessageCursorCallback* aRequest, uint32_t* aRequestIdOut);
 
     // intput stream
     jclass jReadableByteChannel;
     jclass jChannels;
     jmethodID jChannelCreate;
     jmethodID jByteBufferRead;
 
     jclass jInputStream;
--- a/widget/android/AndroidJNI.cpp
+++ b/widget/android/AndroidJNI.cpp
@@ -28,30 +28,23 @@
 #include "nsICrashReporter.h"
 #include "nsExceptionHandler.h"
 #endif
 
 #include "mozilla/unused.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/UniquePtr.h"
 
-#include "mozilla/dom/SmsMessage.h"
-#include "mozilla/dom/mobilemessage/Constants.h"
-#include "mozilla/dom/mobilemessage/Types.h"
-#include "mozilla/dom/mobilemessage/PSms.h"
-#include "mozilla/dom/mobilemessage/SmsParent.h"
 #include "mozilla/layers/APZCTreeManager.h"
-#include "nsIMobileMessageDatabaseService.h"
 #include "nsPluginInstanceOwner.h"
 #include "AndroidSurfaceTexture.h"
 #include "nsMemoryPressure.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
-using namespace mozilla::dom::mobilemessage;
 using namespace mozilla::layers;
 using namespace mozilla::widget;
 using namespace mozilla::widget::android;
 
 /* Forward declare all the JNI methods as extern "C" */
 
 extern "C" {
 /*
@@ -159,509 +152,16 @@ Java_org_mozilla_gecko_GeckoAppShell_not
       bool   mCharging;
       double mRemainingTime;
     };
 
     nsCOMPtr<nsIRunnable> runnable = new NotifyBatteryChangeRunnable(aLevel, aCharging, aRemainingTime);
     NS_DispatchToMainThread(runnable);
 }
 
-#ifdef MOZ_WEBSMS_BACKEND
-
-NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoSmsManager_notifySmsReceived(JNIEnv* jenv, jclass,
-                                                         jstring aSender,
-                                                         jstring aBody,
-                                                         jint aMessageClass,
-                                                         jlong aTimestamp)
-{
-    class NotifySmsReceivedRunnable : public nsRunnable {
-    public:
-      NotifySmsReceivedRunnable(const SmsMessageData& aMessageData)
-        : mMessageData(aMessageData)
-      {}
-
-      NS_IMETHODIMP Run() {
-        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-        if (!obs) {
-          return NS_OK;
-        }
-
-        nsCOMPtr<nsIDOMMozSmsMessage> message = new SmsMessage(mMessageData);
-        obs->NotifyObservers(message, kSmsReceivedObserverTopic, nullptr);
-        return NS_OK;
-      }
-
-    private:
-      SmsMessageData mMessageData;
-    };
-
-    // TODO Need to correct the message `threadId` parameter value. Bug 859098
-    SmsMessageData message(0, 0, eDeliveryState_Received, eDeliveryStatus_Success,
-                           nsJNIString(aSender, jenv), EmptyString(),
-                           nsJNIString(aBody, jenv),
-                           static_cast<MessageClass>(aMessageClass),
-                           aTimestamp, false);
-
-    nsCOMPtr<nsIRunnable> runnable = new NotifySmsReceivedRunnable(message);
-    NS_DispatchToMainThread(runnable);
-}
-
-NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoSmsManager_notifySmsSent(JNIEnv* jenv, jclass,
-                                                     jint aId,
-                                                     jstring aReceiver,
-                                                     jstring aBody,
-                                                     jlong aTimestamp,
-                                                     jint aRequestId)
-{
-    class NotifySmsSentRunnable : public nsRunnable {
-    public:
-      NotifySmsSentRunnable(const SmsMessageData& aMessageData,
-                            int32_t aRequestId)
-        : mMessageData(aMessageData)
-        , mRequestId(aRequestId)
-      {}
-
-      NS_IMETHODIMP Run() {
-        /*
-         * First, we are going to notify all SmsManager that a message has
-         * been sent. Then, we will notify the SmsRequest object about it.
-         */
-        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-        if (!obs) {
-          return NS_OK;
-        }
-
-        nsCOMPtr<nsIDOMMozSmsMessage> message = new SmsMessage(mMessageData);
-        obs->NotifyObservers(message, kSmsSentObserverTopic, nullptr);
-
-        nsCOMPtr<nsIMobileMessageCallback> request =
-          AndroidBridge::Bridge()->DequeueSmsRequest(mRequestId);
-        NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
-
-        request->NotifyMessageSent(message);
-        return NS_OK;
-      }
-
-    private:
-      SmsMessageData mMessageData;
-      int32_t        mRequestId;
-    };
-
-    // TODO Need to add the message `messageClass` parameter value. Bug 804476
-    // TODO Need to correct the message `threadId` parameter value. Bug 859098
-    SmsMessageData message(aId, 0, eDeliveryState_Sent, eDeliveryStatus_Pending,
-                           EmptyString(), nsJNIString(aReceiver, jenv),
-                           nsJNIString(aBody, jenv), eMessageClass_Normal,
-                           aTimestamp, true);
-
-    nsCOMPtr<nsIRunnable> runnable = new NotifySmsSentRunnable(message, aRequestId);
-    NS_DispatchToMainThread(runnable);
-}
-
-NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoSmsManager_notifySmsDelivery(JNIEnv* jenv, jclass,
-                                                         jint aId,
-                                                         jint aDeliveryStatus,
-                                                         jstring aReceiver,
-                                                         jstring aBody,
-                                                         jlong aTimestamp)
-{
-    class NotifySmsDeliveredRunnable : public nsRunnable {
-    public:
-      NotifySmsDeliveredRunnable(const SmsMessageData& aMessageData)
-        : mMessageData(aMessageData)
-      {}
-
-      NS_IMETHODIMP Run() {
-        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-        if (!obs) {
-          return NS_OK;
-        }
-
-        nsCOMPtr<nsIDOMMozSmsMessage> message = new SmsMessage(mMessageData);
-        const char* topic = (mMessageData.deliveryStatus() == eDeliveryStatus_Success)
-                            ? kSmsDeliverySuccessObserverTopic
-                            : kSmsDeliveryErrorObserverTopic;
-        obs->NotifyObservers(message, topic, nullptr);
-
-        return NS_OK;
-      }
-
-    private:
-      SmsMessageData mMessageData;
-    };
-
-    // TODO Need to add the message `messageClass` parameter value. Bug 804476
-    // TODO Need to correct the message `threadId` parameter value. Bug 859098
-    SmsMessageData message(aId, 0, eDeliveryState_Sent,
-                           static_cast<DeliveryStatus>(aDeliveryStatus),
-                           EmptyString(), nsJNIString(aReceiver, jenv),
-                           nsJNIString(aBody, jenv), eMessageClass_Normal,
-                           aTimestamp, true);
-
-    nsCOMPtr<nsIRunnable> runnable = new NotifySmsDeliveredRunnable(message);
-    NS_DispatchToMainThread(runnable);
-}
-
-NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoSmsManager_notifySmsSendFailed(JNIEnv* jenv, jclass,
-                                                           jint aError,
-                                                           jint aRequestId)
-{
-    class NotifySmsSendFailedRunnable : public nsRunnable {
-    public:
-      NotifySmsSendFailedRunnable(int32_t aError,
-                                  int32_t aRequestId)
-        : mError(aError)
-        , mRequestId(aRequestId)
-      {}
-
-      NS_IMETHODIMP Run() {
-        nsCOMPtr<nsIMobileMessageCallback> request =
-          AndroidBridge::Bridge()->DequeueSmsRequest(mRequestId);
-        NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
-
-        request->NotifySendMessageFailed(mError);
-        return NS_OK;
-      }
-
-    private:
-      int32_t  mError;
-      int32_t  mRequestId;
-    };
-
-
-    nsCOMPtr<nsIRunnable> runnable =
-      new NotifySmsSendFailedRunnable(aError, aRequestId);
-    NS_DispatchToMainThread(runnable);
-}
-
-NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoSmsManager_notifyGetSms(JNIEnv* jenv, jclass,
-                                                    jint aId,
-                                                    jint aDeliveryStatus,
-                                                    jstring aReceiver,
-                                                    jstring aSender,
-                                                    jstring aBody,
-                                                    jlong aTimestamp,
-                                                    jint aRequestId)
-{
-    class NotifyGetSmsRunnable : public nsRunnable {
-    public:
-      NotifyGetSmsRunnable(const SmsMessageData& aMessageData,
-                           int32_t aRequestId)
-        : mMessageData(aMessageData)
-        , mRequestId(aRequestId)
-      {}
-
-      NS_IMETHODIMP Run() {
-        nsCOMPtr<nsIMobileMessageCallback> request =
-          AndroidBridge::Bridge()->DequeueSmsRequest(mRequestId);
-        NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
-
-        nsCOMPtr<nsIDOMMozSmsMessage> message = new SmsMessage(mMessageData);
-        request->NotifyMessageGot(message);
-        return NS_OK;
-      }
-
-    private:
-      SmsMessageData mMessageData;
-      int32_t        mRequestId;
-    };
-
-    nsJNIString receiver = nsJNIString(aReceiver, jenv);
-    DeliveryState state = receiver.IsEmpty() ? eDeliveryState_Received
-                                             : eDeliveryState_Sent;
-
-    // TODO Need to add the message `read` parameter value. Bug 748391
-    // TODO Need to add the message `messageClass` parameter value. Bug 804476
-    // TODO Need to correct the message `threadId` parameter value. Bug 859098
-    SmsMessageData message(aId, 0, state,
-                           static_cast<DeliveryStatus>(aDeliveryStatus),
-                           nsJNIString(aSender, jenv), receiver,
-                           nsJNIString(aBody, jenv), eMessageClass_Normal,
-                           aTimestamp, true);
-
-    nsCOMPtr<nsIRunnable> runnable = new NotifyGetSmsRunnable(message, aRequestId);
-    NS_DispatchToMainThread(runnable);
-}
-
-NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoSmsManager_notifyGetSmsFailed(JNIEnv* jenv, jclass,
-                                                          jint aError,
-                                                          jint aRequestId)
-{
-    class NotifyGetSmsFailedRunnable : public nsRunnable {
-    public:
-      NotifyGetSmsFailedRunnable(int32_t aError,
-                                 int32_t aRequestId)
-        : mError(aError)
-        , mRequestId(aRequestId)
-      {}
-
-      NS_IMETHODIMP Run() {
-        nsCOMPtr<nsIMobileMessageCallback> request =
-          AndroidBridge::Bridge()->DequeueSmsRequest(mRequestId);
-        NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
-
-        request->NotifyGetMessageFailed(mError);
-        return NS_OK;
-      }
-
-    private:
-      int32_t  mError;
-      int32_t  mRequestId;
-    };
-
-
-    nsCOMPtr<nsIRunnable> runnable =
-      new NotifyGetSmsFailedRunnable(aError, aRequestId);
-    NS_DispatchToMainThread(runnable);
-}
-
-NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoSmsManager_notifySmsDeleted(JNIEnv* jenv, jclass,
-                                                        jboolean aDeleted,
-                                                        jint aRequestId)
-{
-    class NotifySmsDeletedRunnable : public nsRunnable {
-    public:
-      NotifySmsDeletedRunnable(bool aDeleted, int32_t aRequestId)
-        : mDeleted(aDeleted)
-        , mRequestId(aRequestId)
-      {}
-
-      NS_IMETHODIMP Run() {
-        nsCOMPtr<nsIMobileMessageCallback> request =
-          AndroidBridge::Bridge()->DequeueSmsRequest(mRequestId);
-        NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
-
-        // For android, we support only single SMS deletion.
-        request->NotifyMessageDeleted(&mDeleted, 1);
-        return NS_OK;
-      }
-
-    private:
-      bool      mDeleted;
-      int32_t   mRequestId;
-    };
-
-
-    nsCOMPtr<nsIRunnable> runnable =
-      new NotifySmsDeletedRunnable(aDeleted, aRequestId);
-    NS_DispatchToMainThread(runnable);
-}
-
-NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoSmsManager_notifySmsDeleteFailed(JNIEnv* jenv, jclass,
-                                                             jint aError,
-                                                             jint aRequestId)
-{
-    class NotifySmsDeleteFailedRunnable : public nsRunnable {
-    public:
-      NotifySmsDeleteFailedRunnable(int32_t aError,
-                                    int32_t aRequestId)
-        : mError(aError)
-        , mRequestId(aRequestId)
-      {}
-
-      NS_IMETHODIMP Run() {
-        nsCOMPtr<nsIMobileMessageCallback> request =
-          AndroidBridge::Bridge()->DequeueSmsRequest(mRequestId);
-        NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
-
-        request->NotifyDeleteMessageFailed(mError);
-        return NS_OK;
-      }
-
-    private:
-      int32_t  mError;
-      int32_t  mRequestId;
-    };
-
-
-    nsCOMPtr<nsIRunnable> runnable =
-      new NotifySmsDeleteFailedRunnable(aError, aRequestId);
-    NS_DispatchToMainThread(runnable);
-}
-
-NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoSmsManager_notifyNoMessageInList(JNIEnv* jenv, jclass,
-                                                             jint aRequestId)
-{
-    class NotifyNoMessageInListRunnable : public nsRunnable {
-    public:
-      NotifyNoMessageInListRunnable(int32_t aRequestId)
-        : mRequestId(aRequestId)
-      {}
-
-      NS_IMETHODIMP Run() {
-        nsCOMPtr<nsIMobileMessageCallback> request =
-          AndroidBridge::Bridge()->DequeueSmsRequest(mRequestId);
-        NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
-
-        request->NotifyNoMessageInList();
-        return NS_OK;
-      }
-
-    private:
-      int32_t               mRequestId;
-    };
-
-
-    nsCOMPtr<nsIRunnable> runnable =
-      new NotifyNoMessageInListRunnable(aRequestId);
-    NS_DispatchToMainThread(runnable);
-}
-
-NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoSmsManager_notifyListCreated(JNIEnv* jenv, jclass,
-                                                         jint aListId,
-                                                         jint aMessageId,
-                                                         jint aDeliveryStatus,
-                                                         jstring aReceiver,
-                                                         jstring aSender,
-                                                         jstring aBody,
-                                                         jlong aTimestamp,
-                                                         jint aRequestId)
-{
-    class NotifyCreateMessageListRunnable : public nsRunnable {
-    public:
-      NotifyCreateMessageListRunnable(int32_t aListId,
-                                      const SmsMessageData& aMessageData,
-                                      int32_t aRequestId)
-        : mListId(aListId)
-        , mMessageData(aMessageData)
-        , mRequestId(aRequestId)
-      {}
-
-      NS_IMETHODIMP Run() {
-        nsCOMPtr<nsIMobileMessageCallback> request =
-          AndroidBridge::Bridge()->DequeueSmsRequest(mRequestId);
-        NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
-
-        nsCOMPtr<nsIDOMMozSmsMessage> message = new SmsMessage(mMessageData);
-        request->NotifyMessageListCreated(mListId, message);
-        return NS_OK;
-      }
-
-    private:
-      int32_t        mListId;
-      SmsMessageData mMessageData;
-      int32_t        mRequestId;
-    };
-
-
-    nsJNIString receiver = nsJNIString(aReceiver, jenv);
-    DeliveryState state = receiver.IsEmpty() ? eDeliveryState_Received
-                                             : eDeliveryState_Sent;
-
-    // TODO Need to add the message `read` parameter value. Bug 748391
-    // TODO Need to add the message `messageClass` parameter value. Bug 804476
-    // TODO Need to correct the message `threadId` parameter value. Bug 859098
-    SmsMessageData message(aMessageId, 0, state,
-                           static_cast<DeliveryStatus>(aDeliveryStatus),
-                           nsJNIString(aSender, jenv), receiver,
-                           nsJNIString(aBody, jenv), eMessageClass_Normal,
-                           aTimestamp, true);
-
-    nsCOMPtr<nsIRunnable> runnable =
-      new NotifyCreateMessageListRunnable(aListId, message, aRequestId);
-    NS_DispatchToMainThread(runnable);
-}
-
-NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoSmsManager_notifyGotNextMessage(JNIEnv* jenv, jclass,
-                                                            jint aMessageId,
-                                                            jint aDeliveryStatus,
-                                                            jstring aReceiver,
-                                                            jstring aSender,
-                                                            jstring aBody,
-                                                            jlong aTimestamp,
-                                                            jint aRequestId)
-{
-    class NotifyGotNextMessageRunnable : public nsRunnable {
-    public:
-      NotifyGotNextMessageRunnable(const SmsMessageData& aMessageData,
-                                   int32_t aRequestId)
-        : mMessageData(aMessageData)
-        , mRequestId(aRequestId)
-      {}
-
-      NS_IMETHODIMP Run() {
-        nsCOMPtr<nsIMobileMessageCallback> request =
-          AndroidBridge::Bridge()->DequeueSmsRequest(mRequestId);
-        NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
-
-        nsCOMPtr<nsIDOMMozSmsMessage> message = new SmsMessage(mMessageData);
-        request->NotifyNextMessageInListGot(message);
-        return NS_OK;
-      }
-
-    private:
-      SmsMessageData mMessageData;
-      int32_t        mRequestId;
-    };
-
-
-    nsJNIString receiver = nsJNIString(aReceiver, jenv);
-    DeliveryState state = receiver.IsEmpty() ? eDeliveryState_Received
-                                             : eDeliveryState_Sent;
-
-    // TODO Need to add the message `read` parameter value. Bug 748391
-    // TODO Need to add the message `messageClass` parameter value. Bug 804476
-    // TODO Need to correct the message `threadId` parameter value. Bug 859098
-    SmsMessageData message(aMessageId, 0, state,
-                           static_cast<DeliveryStatus>(aDeliveryStatus),
-                           nsJNIString(aSender, jenv), receiver,
-                           nsJNIString(aBody, jenv), eMessageClass_Normal,
-                           aTimestamp, true);
-
-    nsCOMPtr<nsIRunnable> runnable =
-      new NotifyGotNextMessageRunnable(message, aRequestId);
-    NS_DispatchToMainThread(runnable);
-}
-
-NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoSmsManager_notifyReadingMessageListFailed(JNIEnv* jenv, jclass,
-                                                                      jint aError,
-                                                                      jint aRequestId)
-{
-    class NotifyReadListFailedRunnable : public nsRunnable {
-    public:
-      NotifyReadListFailedRunnable(int32_t aError,
-                                   int32_t aRequestId)
-        : mError(aError)
-        , mRequestId(aRequestId)
-      {}
-
-      NS_IMETHODIMP Run() {
-        nsCOMPtr<nsIMobileMessageCallback> request =
-          AndroidBridge::Bridge()->DequeueSmsRequest(mRequestId);
-        NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
-
-        request->NotifyReadMessageListFailed(mError);
-        return NS_OK;
-      }
-
-    private:
-      int32_t  mError;
-      int32_t  mRequestId;
-    };
-
-
-    nsCOMPtr<nsIRunnable> runnable =
-      new NotifyReadListFailedRunnable(aError, aRequestId);
-    NS_DispatchToMainThread(runnable);
-}
-
-#endif  // MOZ_WEBSMS_BACKEND
-
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_invalidateAndScheduleComposite(JNIEnv*, jclass)
 {
     nsWindow::InvalidateAndScheduleComposite();
 }
 
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_scheduleResumeComposition(JNIEnv*, jclass, jint width, jint height)
@@ -670,41 +170,16 @@ Java_org_mozilla_gecko_GeckoAppShell_sch
 }
 
 NS_EXPORT float JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_computeRenderIntegrity(JNIEnv*, jclass)
 {
     return nsWindow::ComputeRenderIntegrity();
 }
 
-NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult(JNIEnv* jenv, jclass, jstring filePath, jlong callback)
-{
-    class NotifyFilePickerResultRunnable : public nsRunnable {
-    public:
-        NotifyFilePickerResultRunnable(nsString& fileDir, long callback) :
-            mFileDir(fileDir), mCallback(callback) {}
-
-        NS_IMETHODIMP Run() {
-            nsFilePickerCallback* handler = (nsFilePickerCallback*)mCallback;
-            handler->handleResult(mFileDir);
-            handler->Release();
-            return NS_OK;
-        }
-    private:
-        nsString mFileDir;
-        long mCallback;
-    };
-    nsString path = nsJNIString(filePath, jenv);
-
-    nsCOMPtr<nsIRunnable> runnable =
-        new NotifyFilePickerResultRunnable(path, (long)callback);
-    NS_DispatchToMainThread(runnable);
-}
-
 #define MAX_LOCK_ATTEMPTS 10
 
 static bool LockWindowWithRetry(void* window, unsigned char** bits, int* width, int* height, int* format, int* stride)
 {
   int count = 0;
 
   while (count < MAX_LOCK_ATTEMPTS) {
       if (AndroidBridge::Bridge()->LockWindow(window, bits, width, height, format, stride))
@@ -868,17 +343,17 @@ Java_org_mozilla_gecko_gfx_NativePanZoom
     if (!AndroidBridge::Bridge()) {
         return;
     }
 
     const auto& newRef = NativePanZoomController::Ref::From(instance);
     NativePanZoomController::LocalRef oldRef =
             AndroidContentController::SetNativePanZoomController(newRef);
 
-    MOZ_ASSERT(!oldRef, "Registering a new NPZC when we already have one");
+    // MOZ_ASSERT(!oldRef, "Registering a new NPZC when we already have one");
 }
 
 NS_EXPORT jboolean JNICALL
 Java_org_mozilla_gecko_gfx_NativePanZoomController_handleTouchEvent(JNIEnv* env, jobject instance, jobject event)
 {
     APZCTreeManager *controller = nsWindow::GetAPZCTreeManager();
     if (!controller) {
         return false;
--- a/widget/android/GeneratedJNINatives.h
+++ b/widget/android/GeneratedJNINatives.h
@@ -62,16 +62,75 @@ public:
                 ::template Wrap<&Impl::GetProfilerTime>)
     };
 };
 
 template<class Impl>
 constexpr JNINativeMethod GeckoJavaSampler::Natives<Impl>::methods[];
 
 template<class Impl>
+class GeckoSmsManager::Natives : public mozilla::jni::NativeImpl<GeckoSmsManager, Impl>
+{
+public:
+    static constexpr JNINativeMethod methods[] = {
+
+        mozilla::jni::MakeNativeMethod<GeckoSmsManager::NotifyCursorDone_t>(
+                mozilla::jni::NativeStub<GeckoSmsManager::NotifyCursorDone_t, Impl>
+                ::template Wrap<&Impl::NotifyCursorDone>),
+
+        mozilla::jni::MakeNativeMethod<GeckoSmsManager::NotifyCursorError_t>(
+                mozilla::jni::NativeStub<GeckoSmsManager::NotifyCursorError_t, Impl>
+                ::template Wrap<&Impl::NotifyCursorError>),
+
+        mozilla::jni::MakeNativeMethod<GeckoSmsManager::NotifyGetSms_t>(
+                mozilla::jni::NativeStub<GeckoSmsManager::NotifyGetSms_t, Impl>
+                ::template Wrap<&Impl::NotifyGetSms>),
+
+        mozilla::jni::MakeNativeMethod<GeckoSmsManager::NotifyGetSmsFailed_t>(
+                mozilla::jni::NativeStub<GeckoSmsManager::NotifyGetSmsFailed_t, Impl>
+                ::template Wrap<&Impl::NotifyGetSmsFailed>),
+
+        mozilla::jni::MakeNativeMethod<GeckoSmsManager::NotifyMessageCursorResult_t>(
+                mozilla::jni::NativeStub<GeckoSmsManager::NotifyMessageCursorResult_t, Impl>
+                ::template Wrap<&Impl::NotifyMessageCursorResult>),
+
+        mozilla::jni::MakeNativeMethod<GeckoSmsManager::NotifySmsDeleteFailed_t>(
+                mozilla::jni::NativeStub<GeckoSmsManager::NotifySmsDeleteFailed_t, Impl>
+                ::template Wrap<&Impl::NotifySmsDeleteFailed>),
+
+        mozilla::jni::MakeNativeMethod<GeckoSmsManager::NotifySmsDeleted_t>(
+                mozilla::jni::NativeStub<GeckoSmsManager::NotifySmsDeleted_t, Impl>
+                ::template Wrap<&Impl::NotifySmsDeleted>),
+
+        mozilla::jni::MakeNativeMethod<GeckoSmsManager::NotifySmsDelivery_t>(
+                mozilla::jni::NativeStub<GeckoSmsManager::NotifySmsDelivery_t, Impl>
+                ::template Wrap<&Impl::NotifySmsDelivery>),
+
+        mozilla::jni::MakeNativeMethod<GeckoSmsManager::NotifySmsReceived_t>(
+                mozilla::jni::NativeStub<GeckoSmsManager::NotifySmsReceived_t, Impl>
+                ::template Wrap<&Impl::NotifySmsReceived>),
+
+        mozilla::jni::MakeNativeMethod<GeckoSmsManager::NotifySmsSendFailed_t>(
+                mozilla::jni::NativeStub<GeckoSmsManager::NotifySmsSendFailed_t, Impl>
+                ::template Wrap<&Impl::NotifySmsSendFailed>),
+
+        mozilla::jni::MakeNativeMethod<GeckoSmsManager::NotifySmsSent_t>(
+                mozilla::jni::NativeStub<GeckoSmsManager::NotifySmsSent_t, Impl>
+                ::template Wrap<&Impl::NotifySmsSent>),
+
+        mozilla::jni::MakeNativeMethod<GeckoSmsManager::NotifyThreadCursorResult_t>(
+                mozilla::jni::NativeStub<GeckoSmsManager::NotifyThreadCursorResult_t, Impl>
+                ::template Wrap<&Impl::NotifyThreadCursorResult>)
+    };
+};
+
+template<class Impl>
+constexpr JNINativeMethod GeckoSmsManager::Natives<Impl>::methods[];
+
+template<class Impl>
 class GeckoThread::Natives : public mozilla::jni::NativeImpl<GeckoThread, Impl>
 {
 public:
     static constexpr JNINativeMethod methods[] = {
 
         mozilla::jni::MakeNativeMethod<GeckoThread::SpeculativeConnect_t>(
                 mozilla::jni::NativeStub<GeckoThread::SpeculativeConnect_t, Impl>
                 ::template Wrap<&Impl::SpeculativeConnect>)
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -73,24 +73,16 @@ auto GeckoAppShell::CancelVibrate() -> v
 constexpr char GeckoAppShell::CheckURIVisited_t::name[];
 constexpr char GeckoAppShell::CheckURIVisited_t::signature[];
 
 auto GeckoAppShell::CheckURIVisited(mozilla::jni::String::Param a0) -> void
 {
     return mozilla::jni::Method<CheckURIVisited_t>::Call(nullptr, nullptr, a0);
 }
 
-constexpr char GeckoAppShell::ClearMessageList_t::name[];
-constexpr char GeckoAppShell::ClearMessageList_t::signature[];
-
-auto GeckoAppShell::ClearMessageList(int32_t a0) -> void
-{
-    return mozilla::jni::Method<ClearMessageList_t>::Call(nullptr, nullptr, a0);
-}
-
 constexpr char GeckoAppShell::CloseCamera_t::name[];
 constexpr char GeckoAppShell::CloseCamera_t::signature[];
 
 auto GeckoAppShell::CloseCamera() -> void
 {
     return mozilla::jni::Method<CloseCamera_t>::Call(nullptr, nullptr);
 }
 
@@ -113,32 +105,40 @@ auto GeckoAppShell::ConnectionGetMimeTyp
 constexpr char GeckoAppShell::CreateInputStream_t::name[];
 constexpr char GeckoAppShell::CreateInputStream_t::signature[];
 
 auto GeckoAppShell::CreateInputStream(mozilla::jni::Object::Param a0) -> mozilla::jni::Object::LocalRef
 {
     return mozilla::jni::Method<CreateInputStream_t>::Call(nullptr, nullptr, a0);
 }
 
-constexpr char GeckoAppShell::CreateMessageListWrapper_t::name[];
-constexpr char GeckoAppShell::CreateMessageListWrapper_t::signature[];
+constexpr char GeckoAppShell::CreateMessageCursorWrapper_t::name[];
+constexpr char GeckoAppShell::CreateMessageCursorWrapper_t::signature[];
 
-auto GeckoAppShell::CreateMessageListWrapper(int64_t a0, int64_t a1, mozilla::jni::ObjectArray::Param a2, int32_t a3, mozilla::jni::String::Param a4, bool a5, bool a6, int64_t a7, bool a8, int32_t a9) -> void
+auto GeckoAppShell::CreateMessageCursorWrapper(int64_t a0, int64_t a1, mozilla::jni::ObjectArray::Param a2, int32_t a3, mozilla::jni::String::Param a4, bool a5, bool a6, bool a7, int64_t a8, bool a9, int32_t a10) -> void
 {
-    return mozilla::jni::Method<CreateMessageListWrapper_t>::Call(nullptr, nullptr, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9);
+    return mozilla::jni::Method<CreateMessageCursorWrapper_t>::Call(nullptr, nullptr, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
 }
 
 constexpr char GeckoAppShell::CreateShortcut_t::name[];
 constexpr char GeckoAppShell::CreateShortcut_t::signature[];
 
 auto GeckoAppShell::CreateShortcut(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1, mozilla::jni::String::Param a2) -> void
 {
     return mozilla::jni::Method<CreateShortcut_t>::Call(nullptr, nullptr, a0, a1, a2);
 }
 
+constexpr char GeckoAppShell::CreateThreadCursorWrapper_t::name[];
+constexpr char GeckoAppShell::CreateThreadCursorWrapper_t::signature[];
+
+auto GeckoAppShell::CreateThreadCursorWrapper(int32_t a0) -> void
+{
+    return mozilla::jni::Method<CreateThreadCursorWrapper_t>::Call(nullptr, nullptr, a0);
+}
+
 constexpr char GeckoAppShell::DeleteMessageWrapper_t::name[];
 constexpr char GeckoAppShell::DeleteMessageWrapper_t::signature[];
 
 auto GeckoAppShell::DeleteMessageWrapper(int32_t a0, int32_t a1) -> void
 {
     return mozilla::jni::Method<DeleteMessageWrapper_t>::Call(nullptr, nullptr, a0, a1);
 }
 
@@ -361,22 +361,30 @@ auto GeckoAppShell::GetMessageWrapper(in
 constexpr char GeckoAppShell::GetMimeTypeFromExtensionsWrapper_t::name[];
 constexpr char GeckoAppShell::GetMimeTypeFromExtensionsWrapper_t::signature[];
 
 auto GeckoAppShell::GetMimeTypeFromExtensionsWrapper(mozilla::jni::String::Param a0) -> mozilla::jni::String::LocalRef
 {
     return mozilla::jni::Method<GetMimeTypeFromExtensionsWrapper_t>::Call(nullptr, nullptr, a0);
 }
 
-constexpr char GeckoAppShell::GetNextMessageInListWrapper_t::name[];
-constexpr char GeckoAppShell::GetNextMessageInListWrapper_t::signature[];
+constexpr char GeckoAppShell::GetNextMessageWrapper_t::name[];
+constexpr char GeckoAppShell::GetNextMessageWrapper_t::signature[];
 
-auto GeckoAppShell::GetNextMessageInListWrapper(int32_t a0, int32_t a1) -> void
+auto GeckoAppShell::GetNextMessageWrapper(int32_t a0) -> void
 {
-    return mozilla::jni::Method<GetNextMessageInListWrapper_t>::Call(nullptr, nullptr, a0, a1);
+    return mozilla::jni::Method<GetNextMessageWrapper_t>::Call(nullptr, nullptr, a0);
+}
+
+constexpr char GeckoAppShell::GetNextThreadWrapper_t::name[];
+constexpr char GeckoAppShell::GetNextThreadWrapper_t::signature[];
+
+auto GeckoAppShell::GetNextThreadWrapper(int32_t a0) -> void
+{
+    return mozilla::jni::Method<GetNextThreadWrapper_t>::Call(nullptr, nullptr, a0);
 }
 
 constexpr char GeckoAppShell::GetProxyForURIWrapper_t::name[];
 constexpr char GeckoAppShell::GetProxyForURIWrapper_t::signature[];
 
 auto GeckoAppShell::GetProxyForURIWrapper(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1, mozilla::jni::String::Param a2, int32_t a3) -> mozilla::jni::String::LocalRef
 {
     return mozilla::jni::Method<GetProxyForURIWrapper_t>::Call(nullptr, nullptr, a0, a1, a2, a3);
@@ -790,16 +798,54 @@ auto GeckoJavaSampler::StopJavaProfiling
 constexpr char GeckoJavaSampler::UnpauseJavaProfiling_t::name[];
 constexpr char GeckoJavaSampler::UnpauseJavaProfiling_t::signature[];
 
 auto GeckoJavaSampler::UnpauseJavaProfiling() -> void
 {
     return mozilla::jni::Method<UnpauseJavaProfiling_t>::Call(nullptr, nullptr);
 }
 
+constexpr char GeckoSmsManager::name[];
+
+constexpr char GeckoSmsManager::NotifyCursorDone_t::name[];
+constexpr char GeckoSmsManager::NotifyCursorDone_t::signature[];
+
+constexpr char GeckoSmsManager::NotifyCursorError_t::name[];
+constexpr char GeckoSmsManager::NotifyCursorError_t::signature[];
+
+constexpr char GeckoSmsManager::NotifyGetSms_t::name[];
+constexpr char GeckoSmsManager::NotifyGetSms_t::signature[];
+
+constexpr char GeckoSmsManager::NotifyGetSmsFailed_t::name[];
+constexpr char GeckoSmsManager::NotifyGetSmsFailed_t::signature[];
+
+constexpr char GeckoSmsManager::NotifyMessageCursorResult_t::name[];
+constexpr char GeckoSmsManager::NotifyMessageCursorResult_t::signature[];
+
+constexpr char GeckoSmsManager::NotifySmsDeleteFailed_t::name[];
+constexpr char GeckoSmsManager::NotifySmsDeleteFailed_t::signature[];
+
+constexpr char GeckoSmsManager::NotifySmsDeleted_t::name[];
+constexpr char GeckoSmsManager::NotifySmsDeleted_t::signature[];
+
+constexpr char GeckoSmsManager::NotifySmsDelivery_t::name[];
+constexpr char GeckoSmsManager::NotifySmsDelivery_t::signature[];
+
+constexpr char GeckoSmsManager::NotifySmsReceived_t::name[];
+constexpr char GeckoSmsManager::NotifySmsReceived_t::signature[];
+
+constexpr char GeckoSmsManager::NotifySmsSendFailed_t::name[];
+constexpr char GeckoSmsManager::NotifySmsSendFailed_t::signature[];
+
+constexpr char GeckoSmsManager::NotifySmsSent_t::name[];
+constexpr char GeckoSmsManager::NotifySmsSent_t::signature[];
+
+constexpr char GeckoSmsManager::NotifyThreadCursorResult_t::name[];
+constexpr char GeckoSmsManager::NotifyThreadCursorResult_t::signature[];
+
 constexpr char GeckoThread::name[];
 
 constexpr char GeckoThread::PumpMessageLoop_t::name[];
 constexpr char GeckoThread::PumpMessageLoop_t::signature[];
 
 auto GeckoThread::PumpMessageLoop(mozilla::jni::Object::Param a0) -> bool
 {
     return mozilla::jni::Method<PumpMessageLoop_t>::Call(nullptr, nullptr, a0);
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -250,34 +250,16 @@ public:
         static const bool isMultithreaded = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     static auto CheckURIVisited(mozilla::jni::String::Param) -> void;
 
 public:
-    struct ClearMessageList_t {
-        typedef GeckoAppShell Owner;
-        typedef void ReturnType;
-        typedef void SetterType;
-        typedef mozilla::jni::Args<
-                int32_t> Args;
-        static constexpr char name[] = "clearMessageList";
-        static constexpr char signature[] =
-                "(I)V";
-        static const bool isStatic = true;
-        static const bool isMultithreaded = false;
-        static const mozilla::jni::ExceptionMode exceptionMode =
-                mozilla::jni::ExceptionMode::ABORT;
-    };
-
-    static auto ClearMessageList(int32_t) -> void;
-
-public:
     struct CloseCamera_t {
         typedef GeckoAppShell Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "closeCamera";
         static constexpr char signature[] =
                 "()V";
@@ -339,41 +321,42 @@ public:
         static const bool isMultithreaded = true;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     static auto CreateInputStream(mozilla::jni::Object::Param) -> mozilla::jni::Object::LocalRef;
 
 public:
-    struct CreateMessageListWrapper_t {
+    struct CreateMessageCursorWrapper_t {
         typedef GeckoAppShell Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int64_t,
                 int64_t,
                 mozilla::jni::ObjectArray::Param,
                 int32_t,
                 mozilla::jni::String::Param,
                 bool,
                 bool,
+                bool,
                 int64_t,
                 bool,
                 int32_t> Args;
-        static constexpr char name[] = "createMessageList";
-        static constexpr char signature[] =
-                "(JJ[Ljava/lang/String;ILjava/lang/String;ZZJZI)V";
+        static constexpr char name[] = "createMessageCursor";
+        static constexpr char signature[] =
+                "(JJ[Ljava/lang/String;ILjava/lang/String;ZZZJZI)V";
         static const bool isStatic = true;
         static const bool isMultithreaded = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
-    static auto CreateMessageListWrapper(int64_t, int64_t, mozilla::jni::ObjectArray::Param, int32_t, mozilla::jni::String::Param, bool, bool, int64_t, bool, int32_t) -> void;
+    static auto CreateMessageCursorWrapper(int64_t, int64_t, mozilla::jni::ObjectArray::Param, int32_t, mozilla::jni::String::Param, bool, bool, bool, int64_t, bool, int32_t) -> void;
 
 public:
     struct CreateShortcut_t {
         typedef GeckoAppShell Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 mozilla::jni::String::Param,
@@ -386,16 +369,34 @@ public:
         static const bool isMultithreaded = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     static auto CreateShortcut(mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param) -> void;
 
 public:
+    struct CreateThreadCursorWrapper_t {
+        typedef GeckoAppShell Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                int32_t> Args;
+        static constexpr char name[] = "createThreadCursor";
+        static constexpr char signature[] =
+                "(I)V";
+        static const bool isStatic = true;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+    static auto CreateThreadCursorWrapper(int32_t) -> void;
+
+public:
     struct DeleteMessageWrapper_t {
         typedef GeckoAppShell Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
                 int32_t> Args;
         static constexpr char name[] = "deleteMessage";
@@ -899,33 +900,50 @@ public:
         static const bool isMultithreaded = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     static auto GetMimeTypeFromExtensionsWrapper(mozilla::jni::String::Param) -> mozilla::jni::String::LocalRef;
 
 public:
-    struct GetNextMessageInListWrapper_t {
+    struct GetNextMessageWrapper_t {