Merge m-c to inbound on a CLOSED TREE. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 03 Jul 2014 17:05:19 -0400
changeset 213062 dc28da9f9d80643688cd9266ed4a857cd1eb2dfc
parent 213061 2b018836f449b166738fc4d62e0d6c05ca848d35 (current diff)
parent 213032 06e9a27a6fcc5e665588fa4f670d141fd9bac694 (diff)
child 213063 7e1af3a64216466a47186884821eaf579e9860eb
push id3857
push userraliiev@mozilla.com
push dateTue, 02 Sep 2014 16:39:23 +0000
treeherdermozilla-beta@5638b907b505 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone33.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 inbound on a CLOSED TREE. a=merge
CLOBBER
browser/components/loop/content/shared/js/client.js
browser/components/loop/content/shared/libs/hawk-browser-2.2.1.js
browser/components/loop/content/shared/libs/sjcl-dev20140604.js
browser/components/loop/content/shared/libs/token.js
browser/components/loop/test/shared/client_test.js
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,11 +17,10 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 800200: Removing the old JavaScript debugging API, js/jsd. I'm advised
-that our build system doesn't cope well with deletions, and that a spoonful
-of clobber helps the medicine go down (in a most delightful way).
+Bustage pile-up on inbound that appears to need clobbering to go away for good.
+
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -991,12 +991,13 @@ pref("touchcaret.enabled", false);
 pref("selectioncaret.enabled", false);
 
 // Enable sync and mozId with Firefox Accounts.
 #ifdef MOZ_SERVICES_FXACCOUNTS
 pref("services.sync.fxaccounts.enabled", true);
 pref("identity.fxaccounts.enabled", true);
 #endif
 
-pref("services.mobileid.server.uri", "http://msisdn.dev.mozaws.net");
+// Mobile Identity API.
+pref("services.mobileid.server.uri", "https://msisdn-dev.stage.mozaws.net");
 
 // Enable mapped array buffer
 pref("dom.mapped_arraybuffer.enabled", true);
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -10,21 +10,21 @@
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--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="2a165bebfa19b11b697837409f9550dd2917c46c">
+  <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="d7a517f0bde32072f1799e4a47ea34c6d786c287"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="7a32f4ce7f922ed174946cfe322f3d6df40f18ea"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="3326b51017252e09ccdd715dec6c5e12a7d1ecfe"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
@@ -93,16 +93,16 @@
   <project name="platform/system/bluetooth" path="system/bluetooth" revision="507e46e553586bec971551322f20d066c80a0788"/>
   <project name="platform/system/core" path="system/core" revision="91e5551f88aea5aa64e1b4f8b4b52d7be2b28b64"/>
   <project name="platform/system/extras" path="system/extras" revision="0205c49fedf29620165c6b4e6db3d13739c93396"/>
   <project name="platform/system/media" path="system/media" revision="7f17e3995d1588cfcc309b56525652794b6513ef"/>
   <project name="platform/system/netd" path="system/netd" revision="3d298fde142bee3fc4f07f63f16f2d8ce42339c0"/>
   <project name="platform/system/vold" path="system/vold" revision="919829940468066a32f403980b43f6ebfee5d314"/>
   <!-- Emulator specific things -->
   <project name="android-development" path="development" remote="b2g" revision="9abf0ab68376afae3e1c7beefa3e9cbee2fde202"/>
-  <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="3cae7eee1f7f00ba1eb53c733a8779f345055c6e"/>
+  <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="3c42f55d85fd311de2ed9268a59ba1db30be2b21"/>
   <project name="platform/external/iproute2" path="external/iproute2" revision="c66c5716d5335e450f7a7b71ccc6a604fb2f41d2"/>
   <project name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="d2685281e2e54ca14d1df304867aa82c37b27162"/>
   <project name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="627f9b20fc518937b93747a7ff1ed4f5ed46e06f"/>
   <project name="platform/prebuilts/tools" path="prebuilts/tools" revision="acba00cdb4596c6dcb61ed06f14cf4ec89623539"/>
-  <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="bce69473f3d63ab927442f4301f5fb4833d20008"/>
+  <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="02c32feb2fe97037be0ac4dace3a6a5025ac895d"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="4f46930827957afbce500a4a920755a218bf3155"/>
 </manifest>
--- 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="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="d7a517f0bde32072f1799e4a47ea34c6d786c287"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="7a32f4ce7f922ed174946cfe322f3d6df40f18ea"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- 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"/>
@@ -107,17 +107,17 @@
   <project name="platform/frameworks/wilhelm" path="frameworks/wilhelm" revision="4f330e2d671e230c106a3c41ecddbced8ff5d826"/>
   <project name="platform/hardware/libhardware" path="hardware/libhardware" revision="bb653159145842bd86a3522073fcbf5d6450c598"/>
   <project name="platform/hardware/libhardware_legacy" path="hardware/libhardware_legacy" revision="7044afe8fa0a992a2926370e7abe9d06cc9df67b"/>
   <project name="platform/libcore" path="libcore" revision="3552ed1686d04a65b85e56ccc24ff3fcf77725e6"/>
   <project name="platform/libnativehelper" path="libnativehelper" revision="4792069e90385889b0638e97ae62c67cdf274e22"/>
   <project name="platform/ndk" path="ndk" revision="7666b97bbaf1d645cdd6b4430a367b7a2bb53369"/>
   <project name="platform/prebuilts/misc" path="prebuilts/misc" revision="f6ab40b3257abc07741188fd173ac392575cc8d2"/>
   <project name="platform/prebuilts/ndk" path="prebuilts/ndk" revision="e52099755d0bd3a579130eefe8e58066cc6c0cb6"/>
-  <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="bce69473f3d63ab927442f4301f5fb4833d20008"/>
+  <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="02c32feb2fe97037be0ac4dace3a6a5025ac895d"/>
   <project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="842e33e43a55ea44833b9e23e4d180fa17c843af"/>
   <project name="platform/prebuilts/tools" path="prebuilts/tools" revision="5db24726f0f42124304195a6bdea129039eeeaeb"/>
   <project name="platform/system/bluetooth" path="system/bluetooth" revision="930ae098543881f47eac054677726ee4b998b2f8"/>
   <project name="platform_system_core" path="system/core" remote="b2g" revision="542d1f59dc331b472307e5bd043101d14d5a3a3e"/>
   <project name="platform/system/extras" path="system/extras" revision="18c1180e848e7ab8691940481f5c1c8d22c37b3e"/>
   <project name="platform/system/media" path="system/media" revision="d90b836f66bf1d9627886c96f3a2d9c3007fbb80"/>
   <project name="platform/system/netd" path="system/netd" revision="56112dd7b811301b718d0643a82fd5cac9522073"/>
   <project name="platform/system/security" path="system/security" revision="f48ff68fedbcdc12b570b7699745abb6e7574907"/>
--- 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="276ce45e78b09c4a4ee643646f691d22804754c1">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="d7a517f0bde32072f1799e4a47ea34c6d786c287"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="7a32f4ce7f922ed174946cfe322f3d6df40f18ea"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -10,21 +10,21 @@
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--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="2a165bebfa19b11b697837409f9550dd2917c46c">
+  <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="d7a517f0bde32072f1799e4a47ea34c6d786c287"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="7a32f4ce7f922ed174946cfe322f3d6df40f18ea"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="3326b51017252e09ccdd715dec6c5e12a7d1ecfe"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
@@ -93,16 +93,16 @@
   <project name="platform/system/bluetooth" path="system/bluetooth" revision="507e46e553586bec971551322f20d066c80a0788"/>
   <project name="platform/system/core" path="system/core" revision="91e5551f88aea5aa64e1b4f8b4b52d7be2b28b64"/>
   <project name="platform/system/extras" path="system/extras" revision="0205c49fedf29620165c6b4e6db3d13739c93396"/>
   <project name="platform/system/media" path="system/media" revision="7f17e3995d1588cfcc309b56525652794b6513ef"/>
   <project name="platform/system/netd" path="system/netd" revision="3d298fde142bee3fc4f07f63f16f2d8ce42339c0"/>
   <project name="platform/system/vold" path="system/vold" revision="919829940468066a32f403980b43f6ebfee5d314"/>
   <!-- Emulator specific things -->
   <project name="android-development" path="development" remote="b2g" revision="9abf0ab68376afae3e1c7beefa3e9cbee2fde202"/>
-  <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="3cae7eee1f7f00ba1eb53c733a8779f345055c6e"/>
+  <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="3c42f55d85fd311de2ed9268a59ba1db30be2b21"/>
   <project name="platform/external/iproute2" path="external/iproute2" revision="c66c5716d5335e450f7a7b71ccc6a604fb2f41d2"/>
   <project name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="d2685281e2e54ca14d1df304867aa82c37b27162"/>
   <project name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="627f9b20fc518937b93747a7ff1ed4f5ed46e06f"/>
   <project name="platform/prebuilts/tools" path="prebuilts/tools" revision="acba00cdb4596c6dcb61ed06f14cf4ec89623539"/>
-  <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="bce69473f3d63ab927442f4301f5fb4833d20008"/>
+  <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="02c32feb2fe97037be0ac4dace3a6a5025ac895d"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="4f46930827957afbce500a4a920755a218bf3155"/>
 </manifest>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/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="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="d7a517f0bde32072f1799e4a47ea34c6d786c287"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="7a32f4ce7f922ed174946cfe322f3d6df40f18ea"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
@@ -115,17 +115,17 @@
   <project name="platform/system/security" path="system/security" revision="360f51f7af191316cd739f229db1c5f7233be063"/>
   <project name="platform/system/vold" path="system/vold" revision="153df4d067a4149c7d78f1c92fed2ce2bd6a272e"/>
   <!--original fetch url was git://github.com/t2m-foxfone/-->
   <remote fetch="https://git.mozilla.org/external/t2m-foxfone" name="t2m"/>
   <default remote="caf" revision="jb_3.2" sync-j="4"/>
   <!-- Flame specific things -->
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="e8a318f7690092e639ba88891606f4183e846d3f"/>
   <project name="device/qcom/common" path="device/qcom/common" revision="34ed8345250bb97262d70a052217a92e83444ede"/>
-  <project name="device-flame" path="device/t2m/flame" remote="b2g" revision="33faca8033c6f8cda0c383ab40d69c7a45e6db38"/>
+  <project name="device-flame" path="device/t2m/flame" remote="b2g" revision="a928d2ae29244dfec3753ea695e6b98e38e8849a"/>
   <project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="30a441fb7275fc5bc347f84ccb29e977a7eca34e"/>
   <project name="kernel_lk" path="bootable/bootloader/lk" remote="b2g" revision="2b1d8b5b7a760230f4c94c02e733e3929f44253a"/>
   <project name="platform_bootable_recovery" path="bootable/recovery" remote="b2g" revision="e81502511cda303c803e63f049574634bc96f9f2"/>
   <project name="platform/external/bluetooth/bluedroid" path="external/bluetooth/bluedroid" revision="81c4a859d75d413ad688067829d21b7ba9205f81"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="f0689ac1914cdbc59e53bdc9edd9013dc157c299"/>
   <project name="platform/external/bluetooth/glib" path="external/bluetooth/glib" revision="dd925f76e4f149c3d5571b80e12f7e24bbe89c59"/>
   <project name="platform/external/dbus" path="external/dbus" revision="ea87119c843116340f5df1d94eaf8275e1055ae8"/>
   <project name="platform_external_libnfc-nci" path="external/libnfc-nci" remote="t2m" revision="4186bdecb4dae911b39a8202252cc2310d91b0be"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "c560e79619f1e741655d2e1447cb92469a0419c0", 
+    "revision": "dd04ca4b91cdb034a4b90a924b14cd066677b05b", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -8,21 +8,21 @@
   <!--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"/>
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
-  <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
+  <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="d7a517f0bde32072f1799e4a47ea34c6d786c287"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="7a32f4ce7f922ed174946cfe322f3d6df40f18ea"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -6,21 +6,21 @@
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--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"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
-  <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
+  <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="d7a517f0bde32072f1799e4a47ea34c6d786c287"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="7a32f4ce7f922ed174946cfe322f3d6df40f18ea"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/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="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="d7a517f0bde32072f1799e4a47ea34c6d786c287"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="7a32f4ce7f922ed174946cfe322f3d6df40f18ea"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- 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"/>
@@ -107,17 +107,17 @@
   <project name="platform/frameworks/wilhelm" path="frameworks/wilhelm" revision="4f330e2d671e230c106a3c41ecddbced8ff5d826"/>
   <project name="platform/hardware/libhardware" path="hardware/libhardware" revision="bb653159145842bd86a3522073fcbf5d6450c598"/>
   <project name="platform/hardware/libhardware_legacy" path="hardware/libhardware_legacy" revision="7044afe8fa0a992a2926370e7abe9d06cc9df67b"/>
   <project name="platform/libcore" path="libcore" revision="3552ed1686d04a65b85e56ccc24ff3fcf77725e6"/>
   <project name="platform/libnativehelper" path="libnativehelper" revision="4792069e90385889b0638e97ae62c67cdf274e22"/>
   <project name="platform/ndk" path="ndk" revision="7666b97bbaf1d645cdd6b4430a367b7a2bb53369"/>
   <project name="platform/prebuilts/misc" path="prebuilts/misc" revision="f6ab40b3257abc07741188fd173ac392575cc8d2"/>
   <project name="platform/prebuilts/ndk" path="prebuilts/ndk" revision="e52099755d0bd3a579130eefe8e58066cc6c0cb6"/>
-  <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="bce69473f3d63ab927442f4301f5fb4833d20008"/>
+  <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="02c32feb2fe97037be0ac4dace3a6a5025ac895d"/>
   <project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="842e33e43a55ea44833b9e23e4d180fa17c843af"/>
   <project name="platform/prebuilts/tools" path="prebuilts/tools" revision="5db24726f0f42124304195a6bdea129039eeeaeb"/>
   <project name="platform/system/bluetooth" path="system/bluetooth" revision="930ae098543881f47eac054677726ee4b998b2f8"/>
   <project name="platform_system_core" path="system/core" remote="b2g" revision="542d1f59dc331b472307e5bd043101d14d5a3a3e"/>
   <project name="platform/system/extras" path="system/extras" revision="18c1180e848e7ab8691940481f5c1c8d22c37b3e"/>
   <project name="platform/system/media" path="system/media" revision="d90b836f66bf1d9627886c96f3a2d9c3007fbb80"/>
   <project name="platform/system/netd" path="system/netd" revision="56112dd7b811301b718d0643a82fd5cac9522073"/>
   <project name="platform/system/security" path="system/security" revision="f48ff68fedbcdc12b570b7699745abb6e7574907"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -8,21 +8,21 @@
   <!--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"/>
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
-  <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
+  <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="d7a517f0bde32072f1799e4a47ea34c6d786c287"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="7a32f4ce7f922ed174946cfe322f3d6df40f18ea"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
new file mode 100644
--- /dev/null
+++ b/b2g/dev/config/mozconfigs/macosx-universal/mulet
@@ -0,0 +1,3 @@
+. "$topsrcdir/browser/config/mozconfigs/macosx-universal/nightly"
+
+ac_add_options --enable-application=b2g/dev
--- a/browser/base/content/newtab/sites.js
+++ b/browser/base/content/newtab/sites.js
@@ -153,17 +153,17 @@ Site.prototype = {
    * Refreshes the thumbnail for the site.
    */
   refreshThumbnail: function Site_refreshThumbnail() {
     let thumbnail = this._querySelector(".newtab-thumbnail");
     if (this.link.bgColor) {
       thumbnail.style.backgroundColor = this.link.bgColor;
     }
     let uri = this.link.imageURI || PageThumbs.getThumbnailURL(this.url);
-    thumbnail.style.backgroundImage = "url(" + uri + ")";
+    thumbnail.style.backgroundImage = 'url("' + uri + '")';
   },
 
   /**
    * Adds event handlers for the site and its buttons.
    */
   _addEventHandlers: function Site_addEventHandlers() {
     // Register drag-and-drop event handlers.
     this._node.addEventListener("dragstart", this, false);
--- a/browser/components/loop/MozLoopAPI.jsm
+++ b/browser/components/loop/MozLoopAPI.jsm
@@ -38,29 +38,16 @@ function injectLoopAPI(targetWindow) {
         return MozLoopService.doNotDisturb;
       },
       set: function(aFlag) {
         MozLoopService.doNotDisturb = aFlag;
       }
     },
 
     /**
-     * Returns the url for the Loop server from preferences.
-     *
-     * @return {String} The Loop server url
-     */
-    serverUrl: {
-      enumerable: true,
-      configurable: true,
-      get: function() {
-        return Services.prefs.getCharPref("loop.server");
-      }
-    },
-
-    /**
      * Returns the current locale of the browser.
      *
      * @returns {String} The locale string
      */
     locale: {
       enumerable: true,
       configurable: true,
       get: function() {
@@ -209,17 +196,50 @@ function injectLoopAPI(targetWindow) {
                                                     ringerStopper);
           ringerStopper = null;
         }
         if (ringer) {
           ringer.pause();
           ringer = null;
         }
       }
-    }
+    },
+
+    /**
+     * Performs a hawk based request to the loop server.
+     *
+     * Callback parameters:
+     *  - {Object|null} null if success. Otherwise an object:
+     *    {
+     *      code: 401,
+     *      errno: 401,
+     *      error: "Request failed",
+     *      message: "invalid token"
+     *    }
+     *  - {String} The body of the response.
+     *
+     * @param {String} path The path to make the request to.
+     * @param {String} method The request method, e.g. 'POST', 'GET'.
+     * @param {Object} payloadObj An object which is converted to JSON and
+     *                            transmitted with the request.
+     * @param {Function} callback Called when the request completes.
+     */
+    hawkRequest: {
+      enumerable: true,
+      configurable: true,
+      writable: true,
+      value: function(path, method, payloadObj, callback) {
+        // XXX Should really return a DOM promise here.
+        return MozLoopService.hawkRequest(path, method, payloadObj).then((response) => {
+          callback(null, response.body);
+        }, (error) => {
+          callback(Cu.cloneInto(error, targetWindow));
+        });
+      }
+    },
   };
 
   let contentObj = Cu.createObjectIn(targetWindow);
   Object.defineProperties(contentObj, api);
   Cu.makeObjectPropsNormal(contentObj);
 
   targetWindow.navigator.wrappedJSObject.__defineGetter__("mozLoop", function() {
     // We do this in a getter, so that we create these objects
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -142,32 +142,39 @@ let MozLoopServiceInternal = {
 
   /**
    * Performs a hawk based request to the loop server.
    *
    * @param {String} path The path to make the request to.
    * @param {String} method The request method, e.g. 'POST', 'GET'.
    * @param {Object} payloadObj An object which is converted to JSON and
    *                            transmitted with the request.
+   * @returns {Promise}
+   *        Returns a promise that resolves to the response of the API call,
+   *        or is rejected with an error.  If the server response can be parsed
+   *        as JSON and contains an 'error' property, the promise will be
+   *        rejected with this JSON-parsed response.
    */
   hawkRequest: function(path, method, payloadObj) {
     if (!this._hawkClient) {
       this._hawkClient = new HawkClient(this.loopServerUri);
     }
 
     let sessionToken;
     try {
       sessionToken = Services.prefs.getCharPref("loop.hawk-session-token");
     } catch (x) {
       // It is ok for this not to exist, we'll default to sending no-creds
     }
 
     let credentials;
     if (sessionToken) {
-      credentials = deriveHawkCredentials(sessionToken, "sessionToken", 2 * 32);
+      // true = use a hex key, as required by the server (see bug 1032738).
+      credentials = deriveHawkCredentials(sessionToken, "sessionToken",
+                                          2 * 32, true);
     }
 
     return this._hawkClient.request(path, method, credentials, payloadObj);
   },
 
   /**
    * Used to store a session token from a request if it exists in the headers.
    *
@@ -476,10 +483,27 @@ this.MozLoopService = {
   getLoopCharPref: function(prefName) {
     try {
       return Services.prefs.getCharPref("loop." + prefName);
     } catch (ex) {
       console.log("getLoopCharPref had trouble getting " + prefName +
         "; exception: " + ex);
       return null;
     }
-  }
+  },
+
+  /**
+   * Performs a hawk based request to the loop server.
+   *
+   * @param {String} path The path to make the request to.
+   * @param {String} method The request method, e.g. 'POST', 'GET'.
+   * @param {Object} payloadObj An object which is converted to JSON and
+   *                            transmitted with the request.
+   * @returns {Promise}
+   *        Returns a promise that resolves to the response of the API call,
+   *        or is rejected with an error.  If the server response can be parsed
+   *        as JSON and contains an 'error' property, the promise will be
+   *        rejected with this JSON-parsed response.
+   */
+  hawkRequest: function(path, method, payloadObj) {
+    return MozLoopServiceInternal.hawkRequest(path, method, payloadObj);
+  },
 };
--- a/browser/components/loop/content/conversation.html
+++ b/browser/components/loop/content/conversation.html
@@ -16,20 +16,17 @@
 
     <div id="main"></div>
 
     <script type="text/javascript" src="loop/libs/l10n.js"></script>
     <script type="text/javascript" src="loop/libs/sdk.js"></script>
     <script type="text/javascript" src="loop/shared/libs/jquery-2.1.0.js"></script>
     <script type="text/javascript" src="loop/shared/libs/lodash-2.4.1.js"></script>
     <script type="text/javascript" src="loop/shared/libs/backbone-1.1.2.js"></script>
-    <script type="text/javascript" src="loop/shared/libs/sjcl-dev20140604.js"></script>
-    <script type="text/javascript" src="loop/shared/libs/token.js"></script>
-    <script type="text/javascript" src="loop/shared/libs/hawk-browser-2.2.1.js"></script>
 
-    <script type="text/javascript" src="loop/shared/js/client.js"></script>
     <script type="text/javascript" src="loop/shared/js/models.js"></script>
     <script type="text/javascript" src="loop/shared/js/router.js"></script>
     <script type="text/javascript" src="loop/shared/js/views.js"></script>
+    <script type="text/javascript" src="loop/js/client.js"></script>
     <script type="text/javascript" src="loop/js/desktopRouter.js"></script>
     <script type="text/javascript" src="loop/js/conversation.js"></script>
   </body>
 </html>
rename from browser/components/loop/content/shared/js/client.js
rename to browser/components/loop/content/js/client.js
--- a/browser/components/loop/content/shared/js/client.js
+++ b/browser/components/loop/content/js/client.js
@@ -1,30 +1,30 @@
 /* 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/. */
 
 /* global loop:true, hawk, deriveHawkCredentials */
 
 var loop = loop || {};
-loop.shared = loop.shared || {};
-loop.shared.Client = (function($) {
+loop.Client = (function($) {
   "use strict";
 
+  // The expected properties to be returned from the POST /call-url/ request.
+  const expectedCallUrlProperties = ["callUrl", "expiresAt"];
+
+  // The expected properties to be returned from the GET /calls request.
+  const expectedCallProperties = ["calls"];
+
   /**
    * Loop server client.
    *
    * @param {Object} settings Settings object.
    */
-  function Client(settings) {
-    settings = settings || {};
-    if (!settings.hasOwnProperty("baseServerUrl") ||
-        !settings.baseServerUrl) {
-      throw new Error("missing required baseServerUrl");
-    }
+  function Client(settings = {}) {
 
     // allowing an |in| test rather than a more type || allows us to dependency
     // inject a non-existent mozLoop
     if ("mozLoop" in settings) {
       this.mozLoop = settings.mozLoop;
     } else {
       this.mozLoop = navigator.mozLoop;
     }
@@ -55,158 +55,86 @@ loop.shared.Client = (function($) {
 
       properties.forEach(function (property) {
         if (!data.hasOwnProperty(property)) {
           throw new Error("Invalid data received from server - missing " +
             property);
         }
       });
 
-      if (properties.length <= 1) {
+      if (properties.length == 1) {
         return data[properties[0]];
       }
 
       return data;
     },
 
     /**
      * Generic handler for XHR failures.
      *
      * @param {Function} cb Callback(err)
-     * @param jqXHR See jQuery docs
-     * @param textStatus See jQuery docs
-     * @param errorThrown See jQuery docs
+     * @param {Object} error See MozLoopAPI.hawkRequest
      */
-    _failureHandler: function(cb, jqXHR, textStatus, errorThrown) {
-      var error = "Unknown error.",
-          jsonRes = jqXHR && jqXHR.responseJSON || {};
-      // Received error response format:
-      // { "status": "errors",
-      //   "errors": [{
-      //      "location": "url",
-      //      "name": "token",
-      //      "description": "invalid token"
-      // }]}
-      if (jsonRes.status === "errors" && Array.isArray(jsonRes.errors)) {
-        error = "Details: " + jsonRes.errors.map(function(err) {
-          return Object.keys(err).map(function(field) {
-            return field + ": " + err[field];
-          }).join(", ");
-        }).join("; ");
-      }
-      var message = "HTTP " + jqXHR.status + " " + errorThrown +
-          "; " + error;
+    _failureHandler: function(cb, error) {
+      var message = "HTTP " + error.code + " " + error.error + "; " + error.message;
       console.error(message);
       cb(new Error(message));
     },
 
     /**
      * Ensures the client is registered with the push server.
      *
      * Callback parameters:
      * - err null on successful registration, non-null otherwise.
      *
      * @param {Function} cb Callback(err)
      */
     _ensureRegistered: function(cb) {
-      navigator.mozLoop.ensureRegistered(function(err) {
-        cb(err);
-      }.bind(this));
-    },
-
-    /**
-     * Ensures that the client picks up the hawk-session-token
-     * put in preferences by the LoopService registration code,
-     * derives hawk credentials from them, and saves them in
-     * this._credentials.
-     *
-     * @param {Function} cb Callback(err)
-     *  if err is set to null in the callback, that indicates that the
-     *  credentials have been successfully attached to this object.
-     *
-     * @private
-     *
-     * @note That as currently written, this is only ever expected to be called
-     * from browser UI code (ie it relies on mozLoop).
-     */
-    _ensureCredentials: function(cb) {
-      if (this._credentials) {
-        cb(null);
-        return;
-      }
-
-      var hawkSessionToken =
-        this.mozLoop.getLoopCharPref("hawk-session-token");
-      if (!hawkSessionToken) {
-        var msg = "loop.hawk-session-token pref not found";
-        console.warn(msg);
-        cb(new Error(msg));
-        return;
-      }
-
-      // XXX do we want to use any of the other hawk params (eg to track clock
-      // skew, etc)?
-      var serverDerivedKeyLengthInBytes = 2 * 32;
-      deriveHawkCredentials(hawkSessionToken, "sessionToken",
-        serverDerivedKeyLengthInBytes, function (hawkCredentials) {
-          this._credentials = hawkCredentials;
-          cb(null);
-        }.bind(this));
+      this.mozLoop.ensureRegistered(cb);
     },
 
     /**
      * Internal handler for requesting a call url from the server.
      *
      * Callback parameters:
      * - err null on successful registration, non-null otherwise.
      * - callUrlData an object of the obtained call url data if successful:
      * -- call_url: The url of the call
      * -- expiresAt: The amount of hours until expiry of the url
      *
      * @param  {String} simplepushUrl a registered Simple Push URL
      * @param  {string} nickname the nickname of the future caller
      * @param  {Function} cb Callback(err, callUrlData)
      */
     _requestCallUrlInternal: function(nickname, cb) {
-      var endpoint = this.settings.baseServerUrl + "/call-url/",
-          reqData  = {callerId: nickname};
+      this.mozLoop.hawkRequest("/call-url/", "POST", {callerId: nickname},
+                               (error, responseText) => {
+        if (error) {
+          this._failureHandler(cb, error);
+          return;
+        }
+
+        try {
+          var urlData = JSON.parse(responseText);
 
-      var req = $.ajax({
-        type: "POST",
-        url: endpoint,
-        data: reqData,
-        xhrFields: {
-          withCredentials: false
-        },
-        crossDomain: true,
-        beforeSend: function (xhr, settings) {
-          try {
-            this._attachAnyServerCreds(xhr, settings);
-          } catch (ex) {
-            cb(ex);
-            return false;
-          }
-          return true;
-        }.bind(this),
-        success: function(callUrlData) {
-          // XXX split this out into two functions for better readability
-          try {
-            cb(null, this._validate(callUrlData, ["call_url", "expiresAt"]));
+          // XXX Support an alternate call_url property for
+          // backwards compatibility whilst we switch over servers.
+          // Bug 1033988 will want to remove these two lines.
+          if (urlData.call_url)
+            urlData.callUrl = urlData.call_url;
+
+          cb(null, this._validate(urlData, expectedCallUrlProperties));
 
-            var expiresHours = this._hoursToSeconds(callUrlData.expiresAt);
-            navigator.mozLoop.noteCallUrlExpiry(expiresHours);
-          } catch (err) {
-            console.log("Error requesting call info", err);
-            cb(err);
-          }
-        }.bind(this),
-        dataType: "json"
+          var expiresHours = this._hoursToSeconds(urlData.expiresAt);
+          this.mozLoop.noteCallUrlExpiry(expiresHours);
+        } catch (err) {
+          console.log("Error requesting call info", err);
+          cb(err);
+        }
       });
-
-      req.fail(this._failureHandler.bind(this, cb));
     },
 
     /**
      * Requests a call URL from the Loop server. It will note the
      * expiry time for the url with the mozLoop api.
      *
      * Callback parameters:
      * - err null on successful registration, non-null otherwise.
@@ -220,143 +148,49 @@ loop.shared.Client = (function($) {
      */
     requestCallUrl: function(nickname, cb) {
       this._ensureRegistered(function(err) {
         if (err) {
           console.log("Error registering with Loop server, code: " + err);
           cb(err);
           return;
         }
-        this._ensureCredentials(function (err) {
-          if (err) {
-            console.log("Error setting up credentials: " + err);
-            cb(err);
-            return;
-          }
-          this._requestCallUrlInternal(nickname, cb);
-        }.bind(this));
+
+        this._requestCallUrlInternal(nickname, cb);
       }.bind(this));
     },
 
     /**
      * Requests call information from the server for all calls since the
      * given version.
      *
      * @param  {String} version the version identifier from the push
      *                          notification
      * @param  {Function} cb Callback(err, calls)
      */
     requestCallsInfo: function(version, cb) {
-      this._ensureCredentials(function (err) {
-        if (err) {
-          console.log("Error setting up credentials: " + err);
-          cb(err);
-          return;
-        }
-        this._requestCallsInfoInternal(version, cb);
-      }.bind(this));
-    },
-
-    _requestCallsInfoInternal: function(version, cb) {
+      // XXX It is likely that we'll want to move some of this to whatever
+      // opens the chat window, but we'll need to decide on this in bug 1002418
       if (!version) {
         throw new Error("missing required parameter version");
       }
 
-      var endpoint = this.settings.baseServerUrl + "/calls";
+      this.mozLoop.hawkRequest("/calls?version=" + version, "GET", null,
+                               (error, responseText) => {
+        if (error) {
+          this._failureHandler(cb, error);
+          return;
+        }
 
-      // XXX It is likely that we'll want to move some of this to whatever
-      // opens the chat window, but we'll need to decide that once we make a
-      // decision on chrome versus content, and know if we're going with
-      // LoopService or a frameworker.
-      var req = $.ajax({
-        type: "GET",
-        url: endpoint + "?version=" + version,
-        xhrFields: {
-          withCredentials: false
-        },
-        crossDomain: true,
-        beforeSend: function (xhr, settings) {
-          try {
-            this._attachAnyServerCreds(xhr, settings);
-          } catch (ex) {
-            cb(ex);
-            return false;
-          }
-          return true;
-        }.bind(this),
-        success: function(callsData) {
-          try {
-            cb(null, this._validate(callsData, ["calls"]));
-          } catch (err) {
-            console.log("Error requesting calls info", err);
-            cb(err);
-          }
-        }.bind(this),
-        dataType: "json"
-      });
+        try {
+          var callsData = JSON.parse(responseText);
 
-      req.fail(this._failureHandler.bind(this, cb));
-    },
-
-    /**
-     * Posts a call request to the server for a call represented by the
-     * loopToken. Will return the session data for the call.
-     *
-     * @param  {String} loopToken The loopToken representing the call
-     * @param  {Function} cb Callback(err, sessionData)
-     */
-    requestCallInfo: function(loopToken, cb) {
-      if (!loopToken) {
-        throw new Error("missing required parameter loopToken");
-      }
-
-      var req = $.ajax({
-        url:         this.settings.baseServerUrl + "/calls/" + loopToken,
-        method:      "POST",
-        contentType: "application/json",
-        data:        JSON.stringify({}),
-        dataType:    "json"
-      });
-
-      req.done(function(sessionData) {
-        try {
-          cb(null, this._validate(sessionData, [
-            "sessionId", "sessionToken", "apiKey"
-          ]));
+          cb(null, this._validate(callsData, expectedCallProperties));
         } catch (err) {
-          console.log("Error requesting call info", err);
+          console.log("Error requesting calls info", err);
           cb(err);
         }
-      }.bind(this));
-
-      req.fail(this._failureHandler.bind(this, cb));
+      });
     },
-
-    /**
-     * If this._credentials is set, adds a hawk Authorization header based
-     * based on those credentials to the passed-in XHR.
-     *
-     * @param xhr        request to add any header to
-     * @param settings   settings object passed to jQuery.ajax()
-     * @private
-     */
-    _attachAnyServerCreds: function(xhr, settings) {
-      // if the server needs credentials and didn't get them, it will
-      // return failure for us, so if we don't have any creds, don't try to
-      // attach them.
-      if (!this._credentials) {
-        return;
-      }
-
-      var header = hawk.client.header(settings.url, settings.type,
-        { credentials: this._credentials });
-      if (header.err) {
-        throw new Error(header.err);
-      }
-
-      xhr.setRequestHeader("Authorization", header.field);
-
-      return;
-    }
   };
 
   return Client;
 })(jQuery);
--- a/browser/components/loop/content/js/conversation.js
+++ b/browser/components/loop/content/js/conversation.js
@@ -132,17 +132,17 @@ loop.conversation = (function(OT, mozL10
     },
 
     /**
      * Accepts an incoming call.
      */
     accept: function() {
       window.navigator.mozLoop.stopAlerting();
       this._conversation.initiate({
-        baseServerUrl: window.navigator.mozLoop.serverUrl,
+        client: new loop.Client(),
         outgoing: false
       });
     },
 
     /**
      * Declines an incoming call.
      */
     decline: function() {
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -44,30 +44,49 @@ loop.panel = (function(_, mozL10n) {
     render: function() {
       this.$el.html(this.template({
         checked: navigator.mozLoop.doNotDisturb ? "checked" : ""
       }));
       return this;
     }
   });
 
+  var ToSView = sharedViews.BaseView.extend({
+    template: _.template([
+      '<p data-l10n-id="legal_text_and_links"',
+      '  data-l10n-args=\'',
+      '    {"terms_of_use_url": "https://accounts.firefox.com/legal/terms",',
+      '     "privacy_notice_url": "www.mozilla.org/privacy/"',
+      '    }\'></p>'
+    ].join('')),
+
+    render: function() {
+      if (navigator.mozLoop.getLoopCharPref('seenToS') === null) {
+        this.$el.html(this.template());
+        navigator.mozLoop.setLoopCharPref('seenToS', 'seen');
+      }
+      return this;
+    }
+  });
+
   /**
    * Panel view.
    */
   var PanelView = sharedViews.BaseView.extend({
     template: _.template([
       '<div class="description">',
       '  <p data-l10n-id="get_link_to_share"></p>',
       '</div>',
       '<div class="action">',
       '  <form class="invite">',
       '    <input type="text" name="caller" data-l10n-id="caller" required>',
       '    <button type="submit" class="get-url btn btn-success"',
       '       data-l10n-id="get_a_call_url"></button>',
       '  </form>',
+      '  <p class="tos"></p>',
       '  <p class="result hide">',
       '    <input id="call-url" type="url" readonly>',
       '    <a class="go-back btn btn-info" href="" data-l10n-id="new_url"></a>',
       '  </p>',
       '  <p class="dnd"></p>',
       '</div>',
     ].join("")),
 
@@ -86,19 +105,17 @@ loop.panel = (function(_, mozL10n) {
     },
 
     initialize: function(options) {
       options = options || {};
       if (!options.notifier) {
         throw new Error("missing required notifier");
       }
       this.notifier = options.notifier;
-      this.client = new loop.shared.Client({
-        baseServerUrl: navigator.mozLoop.serverUrl
-      });
+      this.client = new loop.Client();
     },
 
     getNickname: function() {
       return this.$("input[name=caller]").val();
     },
 
     getCallUrl: function(event) {
       this.notifier.clear();
@@ -124,17 +141,17 @@ loop.panel = (function(_, mozL10n) {
       this.$(".description p").text(__("get_link_to_share"));
       this.changeButtonState();
     },
 
     onCallUrlReceived: function(callUrlData) {
       this.notifier.clear();
       this.$(".action .invite").hide();
       this.$(".action .invite input").val("");
-      this.$(".action .result input").val(callUrlData.call_url);
+      this.$(".action .result input").val(callUrlData.callUrl);
       this.$(".action .result").show();
       this.$(".description p").text(__("share_link_url"));
     },
 
     setPending: function() {
       this.$("[name=caller]").addClass("pending");
       this.$(".get-url").addClass("disabled").attr("disabled", "disabled");
     },
@@ -153,16 +170,17 @@ loop.panel = (function(_, mozL10n) {
         this.$(".get-url").addClass("disabled").attr("disabled", "disabled");
       }
     },
 
     render: function() {
       this.$el.html(this.template());
       // Do not Disturb sub view
       this.dndView = new DoNotDisturbView({el: this.$(".dnd")}).render();
+      this.tosView = new ToSView({el: this.$(".tos")}).render();
       return this;
     }
   });
 
   var PanelRouter = loop.desktopRouter.DesktopRouter.extend({
     /**
      * DOM document object.
      * @type {HTMLDocument}
@@ -238,11 +256,12 @@ loop.panel = (function(_, mozL10n) {
     evtObject.initEvent('loopPanelInitialized', true, false);
     window.dispatchEvent(evtObject);
   }
 
   return {
     init: init,
     PanelView: PanelView,
     DoNotDisturbView: DoNotDisturbView,
-    PanelRouter: PanelRouter
+    PanelRouter: PanelRouter,
+    ToSView: ToSView
   };
 })(_, document.mozL10n);
--- a/browser/components/loop/content/panel.html
+++ b/browser/components/loop/content/panel.html
@@ -14,20 +14,17 @@
     <div id="messages"></div>
 
     <div id="main"></div>
 
     <script type="text/javascript" src="loop/libs/l10n.js"></script>
     <script type="text/javascript" src="loop/shared/libs/jquery-2.1.0.js"></script>
     <script type="text/javascript" src="loop/shared/libs/lodash-2.4.1.js"></script>
     <script type="text/javascript" src="loop/shared/libs/backbone-1.1.2.js"></script>
-    <script type="text/javascript" src="loop/shared/libs/sjcl-dev20140604.js"></script>
-    <script type="text/javascript" src="loop/shared/libs/token.js"></script>
-    <script type="text/javascript" src="loop/shared/libs/hawk-browser-2.2.1.js"></script>
 
-    <script type="text/javascript" src="loop/shared/js/client.js"></script>
     <script type="text/javascript" src="loop/shared/js/models.js"></script>
     <script type="text/javascript" src="loop/shared/js/router.js"></script>
     <script type="text/javascript" src="loop/shared/js/views.js"></script>
+    <script type="text/javascript" src="loop/js/client.js"></script>
     <script type="text/javascript" src="loop/js/desktopRouter.js"></script>
     <script type="text/javascript" src="loop/js/panel.js"></script>
  </body>
 </html>
--- a/browser/components/loop/content/shared/css/panel.css
+++ b/browser/components/loop/content/shared/css/panel.css
@@ -1,14 +1,18 @@
 /* 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/. */
 
 /* Panel styles */
 
+a {
+  color: #0095DD;
+}
+
 .panel {
   /* XXX the Social API panel behaves weirdly on inner element size changes,
      adding unwanted scrollbars; quickfix is to hide these for now. */
   overflow: hidden;
 }
 
 .spacer {
   margin-bottom: 1em;
@@ -71,13 +75,20 @@
 
 /* For some reason, buttons have a bigger default font size in FF; we're
    reducing a bit for graphical consistency here. */
 .share .action button {
   font-size: .9em;
   padding-top: 6px;
 }
 
+.tos {
+  font-size: .6rem;
+  color: #a8a8a8;
+  text-align: center;
+  padding: 1rem;
+}
+
 /* Specific cases */
 
 .panel #messages .alert {
   margin-bottom: 0;
 }
--- a/browser/components/loop/content/shared/js/models.js
+++ b/browser/components/loop/content/shared/js/models.js
@@ -57,33 +57,33 @@ loop.shared.models = (function() {
 
     /**
      * Initiates a conversation, requesting call session information to the Loop
      * server and updates appropriately the current model attributes with the
      * data.
      *
      * Available options:
      *
-     * - {String} baseServerUrl The server URL
      * - {Boolean} outgoing Set to true if this model represents the
      *                            outgoing call.
+     * - {Boolean} callType Only valid for outgoing calls. The type of media in
+     *                      the call, e.g. "audio" or "audio-video"
+     * - {loop.shared.Client} client  A client object to request call information
+     *                                from. Expects requestCallInfo for outgoing
+     *                                calls, requestCallsInfo for incoming calls.
      *
      * Triggered events:
      *
      * - `session:ready` when the session information have been successfully
      *   retrieved from the server;
      * - `session:error` when the request failed.
      *
      * @param {Object} options Options object
      */
     initiate: function(options) {
-      var client = new loop.shared.Client({
-        baseServerUrl: options.baseServerUrl
-      });
-
       function handleResult(err, sessionData) {
         /*jshint validthis:true */
         if (err) {
           this.trigger("session:error", new Error(
             "Retrieval of session information failed: HTTP " + err));
           return;
         }
 
@@ -94,20 +94,21 @@ loop.shared.models = (function() {
         // Bug 990714 should fix this.
         if (!options.outgoing)
           sessionData = sessionData[0];
 
         this.setReady(sessionData);
       }
 
       if (options.outgoing) {
-        client.requestCallInfo(this.get("loopToken"), handleResult.bind(this));
+        options.client.requestCallInfo(this.get("loopToken"), options.callType,
+          handleResult.bind(this));
       }
       else {
-        client.requestCallsInfo(this.get("loopVersion"),
+        options.client.requestCallsInfo(this.get("loopVersion"),
           handleResult.bind(this));
       }
     },
 
     /**
      * Checks that the session is ready.
      *
      * @return {Boolean}
deleted file mode 100644
--- a/browser/components/loop/content/shared/libs/hawk-browser-2.2.1.js
+++ /dev/null
@@ -1,556 +0,0 @@
-/*
-    HTTP Hawk Authentication Scheme
-    Copyright (c) 2012-2014, Eran Hammer <eran@hammer.io>
-    BSD Licensed
-*/
-
-
-// Declare namespace
-
-var hawk = {
-    internals: {}
-};
-
-
-hawk.client = {
-
-    // Generate an Authorization header for a given request
-
-    /*
-        uri: 'http://example.com/resource?a=b' or object generated by hawk.utils.parseUri()
-        method: HTTP verb (e.g. 'GET', 'POST')
-        options: {
-
-            // Required
-
-            credentials: {
-                id: 'dh37fgj492je',
-                key: 'aoijedoaijsdlaksjdl',
-                algorithm: 'sha256'                                 // 'sha1', 'sha256'
-            },
-
-            // Optional
-
-            ext: 'application-specific',                        // Application specific data sent via the ext attribute
-            timestamp: Date.now() / 1000,                       // A pre-calculated timestamp in seconds
-            nonce: '2334f34f',                                  // A pre-generated nonce
-            localtimeOffsetMsec: 400,                           // Time offset to sync with server time (ignored if timestamp provided)
-            payload: '{"some":"payload"}',                      // UTF-8 encoded string for body hash generation (ignored if hash provided)
-            contentType: 'application/json',                    // Payload content-type (ignored if hash provided)
-            hash: 'U4MKKSmiVxk37JCCrAVIjV=',                    // Pre-calculated payload hash
-            app: '24s23423f34dx',                               // Oz application id
-            dlg: '234sz34tww3sd'                                // Oz delegated-by application id
-        }
-    */
-
-    header: function (uri, method, options) {
-
-        var result = {
-            field: '',
-            artifacts: {}
-        };
-
-        // Validate inputs
-
-        if (!uri || (typeof uri !== 'string' && typeof uri !== 'object') ||
-            !method || typeof method !== 'string' ||
-            !options || typeof options !== 'object') {
-
-            result.err = 'Invalid argument type';
-            return result;
-        }
-
-        // Application time
-
-        var timestamp = options.timestamp || hawk.utils.now(options.localtimeOffsetMsec);
-
-        // Validate credentials
-
-        var credentials = options.credentials;
-        if (!credentials ||
-            !credentials.id ||
-            !credentials.key ||
-            !credentials.algorithm) {
-
-            result.err = 'Invalid credentials object';
-            return result;
-        }
-
-        if (hawk.crypto.algorithms.indexOf(credentials.algorithm) === -1) {
-            result.err = 'Unknown algorithm';
-            return result;
-        }
-
-        // Parse URI
-
-        if (typeof uri === 'string') {
-            uri = hawk.utils.parseUri(uri);
-        }
-
-        // Calculate signature
-
-        var artifacts = {
-            ts: timestamp,
-            nonce: options.nonce || hawk.utils.randomString(6),
-            method: method,
-            resource: uri.relative,
-            host: uri.hostname,
-            port: uri.port,
-            hash: options.hash,
-            ext: options.ext,
-            app: options.app,
-            dlg: options.dlg
-        };
-
-        result.artifacts = artifacts;
-
-        // Calculate payload hash
-
-        if (!artifacts.hash &&
-            (options.payload || options.payload === '')) {
-
-            artifacts.hash = hawk.crypto.calculatePayloadHash(options.payload, credentials.algorithm, options.contentType);
-        }
-
-        var mac = hawk.crypto.calculateMac('header', credentials, artifacts);
-
-        // Construct header
-
-        var hasExt = artifacts.ext !== null && artifacts.ext !== undefined && artifacts.ext !== '';       // Other falsey values allowed
-        var header = 'Hawk id="' + credentials.id +
-                     '", ts="' + artifacts.ts +
-                     '", nonce="' + artifacts.nonce +
-                     (artifacts.hash ? '", hash="' + artifacts.hash : '') +
-                     (hasExt ? '", ext="' + hawk.utils.escapeHeaderAttribute(artifacts.ext) : '') +
-                     '", mac="' + mac + '"';
-
-        if (artifacts.app) {
-            header += ', app="' + artifacts.app +
-                      (artifacts.dlg ? '", dlg="' + artifacts.dlg : '') + '"';
-        }
-
-        result.field = header;
-
-        return result;
-    },
-
-
-    // Validate server response
-
-    /*
-        request:    object created via 'new XMLHttpRequest()' after response received
-        artifacts:  object received from header().artifacts
-        options: {
-            payload:    optional payload received
-            required:   specifies if a Server-Authorization header is required. Defaults to 'false'
-        }
-    */
-
-    authenticate: function (request, credentials, artifacts, options) {
-
-        options = options || {};
-
-        var getHeader = function (name) {
-
-            return request.getResponseHeader ? request.getResponseHeader(name) : request.getHeader(name);
-        };
-
-        var wwwAuthenticate = getHeader('www-authenticate');
-        if (wwwAuthenticate) {
-
-            // Parse HTTP WWW-Authenticate header
-
-            var attributes = hawk.utils.parseAuthorizationHeader(wwwAuthenticate, ['ts', 'tsm', 'error']);
-            if (!attributes) {
-                return false;
-            }
-
-            if (attributes.ts) {
-                var tsm = hawk.crypto.calculateTsMac(attributes.ts, credentials);
-                if (tsm !== attributes.tsm) {
-                    return false;
-                }
-
-                hawk.utils.setNtpOffset(attributes.ts - Math.floor((new Date()).getTime() / 1000));     // Keep offset at 1 second precision
-            }
-        }
-
-        // Parse HTTP Server-Authorization header
-
-        var serverAuthorization = getHeader('server-authorization');
-        if (!serverAuthorization &&
-            !options.required) {
-
-            return true;
-        }
-
-        var attributes = hawk.utils.parseAuthorizationHeader(serverAuthorization, ['mac', 'ext', 'hash']);
-        if (!attributes) {
-            return false;
-        }
-
-        var modArtifacts = {
-            ts: artifacts.ts,
-            nonce: artifacts.nonce,
-            method: artifacts.method,
-            resource: artifacts.resource,
-            host: artifacts.host,
-            port: artifacts.port,
-            hash: attributes.hash,
-            ext: attributes.ext,
-            app: artifacts.app,
-            dlg: artifacts.dlg
-        };
-
-        var mac = hawk.crypto.calculateMac('response', credentials, modArtifacts);
-        if (mac !== attributes.mac) {
-            return false;
-        }
-
-        if (!options.payload &&
-            options.payload !== '') {
-
-            return true;
-        }
-
-        if (!attributes.hash) {
-            return false;
-        }
-
-        var calculatedHash = hawk.crypto.calculatePayloadHash(options.payload, credentials.algorithm, getHeader('content-type'));
-        return (calculatedHash === attributes.hash);
-    },
-
-    message: function (host, port, message, options) {
-
-        // Validate inputs
-
-        if (!host || typeof host !== 'string' ||
-            !port || typeof port !== 'number' ||
-            message === null || message === undefined || typeof message !== 'string' ||
-            !options || typeof options !== 'object') {
-
-            return null;
-        }
-
-        // Application time
-
-        var timestamp = options.timestamp || hawk.utils.now(options.localtimeOffsetMsec);
-
-        // Validate credentials
-
-        var credentials = options.credentials;
-        if (!credentials ||
-            !credentials.id ||
-            !credentials.key ||
-            !credentials.algorithm) {
-
-            // Invalid credential object
-            return null;
-        }
-
-        if (hawk.crypto.algorithms.indexOf(credentials.algorithm) === -1) {
-            return null;
-        }
-
-        // Calculate signature
-
-        var artifacts = {
-            ts: timestamp,
-            nonce: options.nonce || hawk.utils.randomString(6),
-            host: host,
-            port: port,
-            hash: hawk.crypto.calculatePayloadHash(message, credentials.algorithm)
-        };
-
-        // Construct authorization
-
-        var result = {
-            id: credentials.id,
-            ts: artifacts.ts,
-            nonce: artifacts.nonce,
-            hash: artifacts.hash,
-            mac: hawk.crypto.calculateMac('message', credentials, artifacts)
-        };
-
-        return result;
-    },
-
-    authenticateTimestamp: function (message, credentials, updateClock) {           // updateClock defaults to true
-
-        var tsm = hawk.crypto.calculateTsMac(message.ts, credentials);
-        if (tsm !== message.tsm) {
-            return false;
-        }
-
-        if (updateClock !== false) {
-            hawk.utils.setNtpOffset(message.ts - Math.floor((new Date()).getTime() / 1000));    // Keep offset at 1 second precision
-        }
-
-        return true;
-    }
-};
-
-
-hawk.crypto = {
-
-    headerVersion: '1',
-
-    algorithms: ['sha1', 'sha256'],
-
-    calculateMac: function (type, credentials, options) {
-
-        var normalized = hawk.crypto.generateNormalizedString(type, options);
-
-        var hmac = CryptoJS['Hmac' + credentials.algorithm.toUpperCase()](normalized, credentials.key);
-        return hmac.toString(CryptoJS.enc.Base64);
-    },
-
-    generateNormalizedString: function (type, options) {
-
-        var normalized = 'hawk.' + hawk.crypto.headerVersion + '.' + type + '\n' +
-                         options.ts + '\n' +
-                         options.nonce + '\n' +
-                         (options.method || '').toUpperCase() + '\n' +
-                         (options.resource || '') + '\n' +
-                         options.host.toLowerCase() + '\n' +
-                         options.port + '\n' +
-                         (options.hash || '') + '\n';
-
-        if (options.ext) {
-            normalized += options.ext.replace('\\', '\\\\').replace('\n', '\\n');
-        }
-
-        normalized += '\n';
-
-        if (options.app) {
-            normalized += options.app + '\n' +
-                          (options.dlg || '') + '\n';
-        }
-
-        return normalized;
-    },
-
-    calculatePayloadHash: function (payload, algorithm, contentType) {
-
-        var hash = CryptoJS.algo[algorithm.toUpperCase()].create();
-        hash.update('hawk.' + hawk.crypto.headerVersion + '.payload\n');
-        hash.update(hawk.utils.parseContentType(contentType) + '\n');
-        hash.update(payload);
-        hash.update('\n');
-        return hash.finalize().toString(CryptoJS.enc.Base64);
-    },
-
-    calculateTsMac: function (ts, credentials) {
-
-        var hash = CryptoJS['Hmac' + credentials.algorithm.toUpperCase()]('hawk.' + hawk.crypto.headerVersion + '.ts\n' + ts + '\n', credentials.key);
-        return hash.toString(CryptoJS.enc.Base64);
-    }
-};
-
-
-// localStorage compatible interface
-
-hawk.internals.LocalStorage = function () {
-
-    this._cache = {};
-    this.length = 0;
-
-    this.getItem = function (key) {
-
-        return this._cache.hasOwnProperty(key) ? String(this._cache[key]) : null;
-    };
-
-    this.setItem = function (key, value) {
-
-        this._cache[key] = String(value);
-        this.length = Object.keys(this._cache).length;
-    };
-
-    this.removeItem = function (key) {
-
-        delete this._cache[key];
-        this.length = Object.keys(this._cache).length;
-    };
-
-    this.clear = function () {
-
-        this._cache = {};
-        this.length = 0;
-    };
-
-    this.key = function (i) {
-
-        return Object.keys(this._cache)[i || 0];
-    };
-};
-
-
-hawk.utils = {
-
-    storage: new hawk.internals.LocalStorage(),
-
-    setStorage: function (storage) {
-
-        var ntpOffset = hawk.utils.storage.getItem('hawk_ntp_offset');
-        hawk.utils.storage = storage;
-        if (ntpOffset) {
-            hawk.utils.setNtpOffset(ntpOffset);
-        }
-    },
-
-    setNtpOffset: function (offset) {
-
-        try {
-            hawk.utils.storage.setItem('hawk_ntp_offset', offset);
-        }
-        catch (err) {
-            console.error('[hawk] could not write to storage.');
-            console.error(err);
-        }
-    },
-
-    getNtpOffset: function () {
-
-        var offset = hawk.utils.storage.getItem('hawk_ntp_offset');
-        if (!offset) {
-            return 0;
-        }
-
-        return parseInt(offset, 10);
-    },
-
-    now: function (localtimeOffsetMsec) {
-
-        return Math.floor(((new Date()).getTime() + (localtimeOffsetMsec || 0)) / 1000) + hawk.utils.getNtpOffset();
-    },
-
-    escapeHeaderAttribute: function (attribute) {
-
-        return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"');
-    },
-
-    parseContentType: function (header) {
-
-        if (!header) {
-            return '';
-        }
-
-        return header.split(';')[0].replace(/^\s+|\s+$/g, '').toLowerCase();
-    },
-
-    parseAuthorizationHeader: function (header, keys) {
-
-        if (!header) {
-            return null;
-        }
-
-        var headerParts = header.match(/^(\w+)(?:\s+(.*))?$/);       // Header: scheme[ something]
-        if (!headerParts) {
-            return null;
-        }
-
-        var scheme = headerParts[1];
-        if (scheme.toLowerCase() !== 'hawk') {
-            return null;
-        }
-
-        var attributesString = headerParts[2];
-        if (!attributesString) {
-            return null;
-        }
-
-        var attributes = {};
-        var verify = attributesString.replace(/(\w+)="([^"\\]*)"\s*(?:,\s*|$)/g, function ($0, $1, $2) {
-
-            // Check valid attribute names
-
-            if (keys.indexOf($1) === -1) {
-                return;
-            }
-
-            // Allowed attribute value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9
-
-            if ($2.match(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~]+$/) === null) {
-                return;
-            }
-
-            // Check for duplicates
-
-            if (attributes.hasOwnProperty($1)) {
-                return;
-            }
-
-            attributes[$1] = $2;
-            return '';
-        });
-
-        if (verify !== '') {
-            return null;
-        }
-
-        return attributes;
-    },
-
-    randomString: function (size) {
-
-        var randomSource = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
-        var len = randomSource.length;
-
-        var result = [];
-        for (var i = 0; i < size; ++i) {
-            result[i] = randomSource[Math.floor(Math.random() * len)];
-        }
-
-        return result.join('');
-    },
-
-    parseUri: function (input) {
-
-        // Based on: parseURI 1.2.2
-        // http://blog.stevenlevithan.com/archives/parseuri
-        // (c) Steven Levithan <stevenlevithan.com>
-        // MIT License
-
-        var keys = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'hostname', 'port', 'resource', 'relative', 'pathname', 'directory', 'file', 'query', 'fragment'];
-
-        var uriRegex = /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?(((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?)(?:#(.*))?)/;
-        var uriByNumber = input.match(uriRegex);
-        var uri = {};
-
-        for (var i = 0, il = keys.length; i < il; ++i) {
-            uri[keys[i]] = uriByNumber[i] || '';
-        }
-
-        if (uri.port === '') {
-            uri.port = (uri.protocol.toLowerCase() === 'http' ? '80' : (uri.protocol.toLowerCase() === 'https' ? '443' : ''));
-        }
-
-        return uri;
-    }
-};
-
-
-// $lab:coverage:off$
-
-// Based on: Crypto-JS v3.1.2
-// Copyright (c) 2009-2013, Jeff Mott. All rights reserved.
-// http://code.google.com/p/crypto-js/
-// http://code.google.com/p/crypto-js/wiki/License
-
-var CryptoJS = CryptoJS || function (h, r) { var k = {}, l = k.lib = {}, n = function () { }, f = l.Base = { extend: function (a) { n.prototype = this; var b = new n; a && b.mixIn(a); b.hasOwnProperty("init") || (b.init = function () { b.$super.init.apply(this, arguments) }); b.init.prototype = b; b.$super = this; return b }, create: function () { var a = this.extend(); a.init.apply(a, arguments); return a }, init: function () { }, mixIn: function (a) { for (var b in a) a.hasOwnProperty(b) && (this[b] = a[b]); a.hasOwnProperty("toString") && (this.toString = a.toString) }, clone: function () { return this.init.prototype.extend(this) } }, j = l.WordArray = f.extend({ init: function (a, b) { a = this.words = a || []; this.sigBytes = b != r ? b : 4 * a.length }, toString: function (a) { return (a || s).stringify(this) }, concat: function (a) { var b = this.words, d = a.words, c = this.sigBytes; a = a.sigBytes; this.clamp(); if (c % 4) for (var e = 0; e < a; e++) b[c + e >>> 2] |= (d[e >>> 2] >>> 24 - 8 * (e % 4) & 255) << 24 - 8 * ((c + e) % 4); else if (65535 < d.length) for (e = 0; e < a; e += 4) b[c + e >>> 2] = d[e >>> 2]; else b.push.apply(b, d); this.sigBytes += a; return this }, clamp: function () { var a = this.words, b = this.sigBytes; a[b >>> 2] &= 4294967295 << 32 - 8 * (b % 4); a.length = h.ceil(b / 4) }, clone: function () { var a = f.clone.call(this); a.words = this.words.slice(0); return a }, random: function (a) { for (var b = [], d = 0; d < a; d += 4) b.push(4294967296 * h.random() | 0); return new j.init(b, a) } }), m = k.enc = {}, s = m.Hex = { stringify: function (a) { var b = a.words; a = a.sigBytes; for (var d = [], c = 0; c < a; c++) { var e = b[c >>> 2] >>> 24 - 8 * (c % 4) & 255; d.push((e >>> 4).toString(16)); d.push((e & 15).toString(16)) } return d.join("") }, parse: function (a) { for (var b = a.length, d = [], c = 0; c < b; c += 2) d[c >>> 3] |= parseInt(a.substr(c, 2), 16) << 24 - 4 * (c % 8); return new j.init(d, b / 2) } }, p = m.Latin1 = { stringify: function (a) { var b = a.words; a = a.sigBytes; for (var d = [], c = 0; c < a; c++) d.push(String.fromCharCode(b[c >>> 2] >>> 24 - 8 * (c % 4) & 255)); return d.join("") }, parse: function (a) { for (var b = a.length, d = [], c = 0; c < b; c++) d[c >>> 2] |= (a.charCodeAt(c) & 255) << 24 - 8 * (c % 4); return new j.init(d, b) } }, t = m.Utf8 = { stringify: function (a) { try { return decodeURIComponent(escape(p.stringify(a))) } catch (b) { throw Error("Malformed UTF-8 data"); } }, parse: function (a) { return p.parse(unescape(encodeURIComponent(a))) } }, q = l.BufferedBlockAlgorithm = f.extend({ reset: function () { this._data = new j.init; this._nDataBytes = 0 }, _append: function (a) { "string" == typeof a && (a = t.parse(a)); this._data.concat(a); this._nDataBytes += a.sigBytes }, _process: function (a) { var b = this._data, d = b.words, c = b.sigBytes, e = this.blockSize, f = c / (4 * e), f = a ? h.ceil(f) : h.max((f | 0) - this._minBufferSize, 0); a = f * e; c = h.min(4 * a, c); if (a) { for (var g = 0; g < a; g += e) this._doProcessBlock(d, g); g = d.splice(0, a); b.sigBytes -= c } return new j.init(g, c) }, clone: function () { var a = f.clone.call(this); a._data = this._data.clone(); return a }, _minBufferSize: 0 }); l.Hasher = q.extend({ cfg: f.extend(), init: function (a) { this.cfg = this.cfg.extend(a); this.reset() }, reset: function () { q.reset.call(this); this._doReset() }, update: function (a) { this._append(a); this._process(); return this }, finalize: function (a) { a && this._append(a); return this._doFinalize() }, blockSize: 16, _createHelper: function (a) { return function (b, d) { return (new a.init(d)).finalize(b) } }, _createHmacHelper: function (a) { return function (b, d) { return (new u.HMAC.init(a, d)).finalize(b) } } }); var u = k.algo = {}; return k }(Math);
-(function () { var k = CryptoJS, b = k.lib, m = b.WordArray, l = b.Hasher, d = [], b = k.algo.SHA1 = l.extend({ _doReset: function () { this._hash = new m.init([1732584193, 4023233417, 2562383102, 271733878, 3285377520]) }, _doProcessBlock: function (n, p) { for (var a = this._hash.words, e = a[0], f = a[1], h = a[2], j = a[3], b = a[4], c = 0; 80 > c; c++) { if (16 > c) d[c] = n[p + c] | 0; else { var g = d[c - 3] ^ d[c - 8] ^ d[c - 14] ^ d[c - 16]; d[c] = g << 1 | g >>> 31 } g = (e << 5 | e >>> 27) + b + d[c]; g = 20 > c ? g + ((f & h | ~f & j) + 1518500249) : 40 > c ? g + ((f ^ h ^ j) + 1859775393) : 60 > c ? g + ((f & h | f & j | h & j) - 1894007588) : g + ((f ^ h ^ j) - 899497514); b = j; j = h; h = f << 30 | f >>> 2; f = e; e = g } a[0] = a[0] + e | 0; a[1] = a[1] + f | 0; a[2] = a[2] + h | 0; a[3] = a[3] + j | 0; a[4] = a[4] + b | 0 }, _doFinalize: function () { var b = this._data, d = b.words, a = 8 * this._nDataBytes, e = 8 * b.sigBytes; d[e >>> 5] |= 128 << 24 - e % 32; d[(e + 64 >>> 9 << 4) + 14] = Math.floor(a / 4294967296); d[(e + 64 >>> 9 << 4) + 15] = a; b.sigBytes = 4 * d.length; this._process(); return this._hash }, clone: function () { var b = l.clone.call(this); b._hash = this._hash.clone(); return b } }); k.SHA1 = l._createHelper(b); k.HmacSHA1 = l._createHmacHelper(b) })();
-(function (k) { for (var g = CryptoJS, h = g.lib, v = h.WordArray, j = h.Hasher, h = g.algo, s = [], t = [], u = function (q) { return 4294967296 * (q - (q | 0)) | 0 }, l = 2, b = 0; 64 > b;) { var d; a: { d = l; for (var w = k.sqrt(d), r = 2; r <= w; r++) if (!(d % r)) { d = !1; break a } d = !0 } d && (8 > b && (s[b] = u(k.pow(l, 0.5))), t[b] = u(k.pow(l, 1 / 3)), b++); l++ } var n = [], h = h.SHA256 = j.extend({ _doReset: function () { this._hash = new v.init(s.slice(0)) }, _doProcessBlock: function (q, h) { for (var a = this._hash.words, c = a[0], d = a[1], b = a[2], k = a[3], f = a[4], g = a[5], j = a[6], l = a[7], e = 0; 64 > e; e++) { if (16 > e) n[e] = q[h + e] | 0; else { var m = n[e - 15], p = n[e - 2]; n[e] = ((m << 25 | m >>> 7) ^ (m << 14 | m >>> 18) ^ m >>> 3) + n[e - 7] + ((p << 15 | p >>> 17) ^ (p << 13 | p >>> 19) ^ p >>> 10) + n[e - 16] } m = l + ((f << 26 | f >>> 6) ^ (f << 21 | f >>> 11) ^ (f << 7 | f >>> 25)) + (f & g ^ ~f & j) + t[e] + n[e]; p = ((c << 30 | c >>> 2) ^ (c << 19 | c >>> 13) ^ (c << 10 | c >>> 22)) + (c & d ^ c & b ^ d & b); l = j; j = g; g = f; f = k + m | 0; k = b; b = d; d = c; c = m + p | 0 } a[0] = a[0] + c | 0; a[1] = a[1] + d | 0; a[2] = a[2] + b | 0; a[3] = a[3] + k | 0; a[4] = a[4] + f | 0; a[5] = a[5] + g | 0; a[6] = a[6] + j | 0; a[7] = a[7] + l | 0 }, _doFinalize: function () { var d = this._data, b = d.words, a = 8 * this._nDataBytes, c = 8 * d.sigBytes; b[c >>> 5] |= 128 << 24 - c % 32; b[(c + 64 >>> 9 << 4) + 14] = k.floor(a / 4294967296); b[(c + 64 >>> 9 << 4) + 15] = a; d.sigBytes = 4 * b.length; this._process(); return this._hash }, clone: function () { var b = j.clone.call(this); b._hash = this._hash.clone(); return b } }); g.SHA256 = j._createHelper(h); g.HmacSHA256 = j._createHmacHelper(h) })(Math);
-(function () { var c = CryptoJS, k = c.enc.Utf8; c.algo.HMAC = c.lib.Base.extend({ init: function (a, b) { a = this._hasher = new a.init; "string" == typeof b && (b = k.parse(b)); var c = a.blockSize, e = 4 * c; b.sigBytes > e && (b = a.finalize(b)); b.clamp(); for (var f = this._oKey = b.clone(), g = this._iKey = b.clone(), h = f.words, j = g.words, d = 0; d < c; d++) h[d] ^= 1549556828, j[d] ^= 909522486; f.sigBytes = g.sigBytes = e; this.reset() }, reset: function () { var a = this._hasher; a.reset(); a.update(this._iKey) }, update: function (a) { this._hasher.update(a); return this }, finalize: function (a) { var b = this._hasher; a = b.finalize(a); b.reset(); return b.finalize(this._oKey.clone().concat(a)) } }) })();
-(function () { var h = CryptoJS, j = h.lib.WordArray; h.enc.Base64 = { stringify: function (b) { var e = b.words, f = b.sigBytes, c = this._map; b.clamp(); b = []; for (var a = 0; a < f; a += 3) for (var d = (e[a >>> 2] >>> 24 - 8 * (a % 4) & 255) << 16 | (e[a + 1 >>> 2] >>> 24 - 8 * ((a + 1) % 4) & 255) << 8 | e[a + 2 >>> 2] >>> 24 - 8 * ((a + 2) % 4) & 255, g = 0; 4 > g && a + 0.75 * g < f; g++) b.push(c.charAt(d >>> 6 * (3 - g) & 63)); if (e = c.charAt(64)) for (; b.length % 4;) b.push(e); return b.join("") }, parse: function (b) { var e = b.length, f = this._map, c = f.charAt(64); c && (c = b.indexOf(c), -1 != c && (e = c)); for (var c = [], a = 0, d = 0; d < e; d++) if (d % 4) { var g = f.indexOf(b.charAt(d - 1)) << 2 * (d % 4), h = f.indexOf(b.charAt(d)) >>> 6 - 2 * (d % 4); c[a >>> 2] |= (g | h) << 24 - 8 * (a % 4); a++ } return j.create(c, a) }, _map: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" } })();
-
-hawk.crypto.internals = CryptoJS;
-
-
-// Export if used as a module
-
-if (typeof module !== 'undefined' && module.exports) {
-    module.exports = hawk;
-}
-
-// $lab:coverage:on$
deleted file mode 100644
--- a/browser/components/loop/content/shared/libs/sjcl-dev20140604.js
+++ /dev/null
@@ -1,606 +0,0 @@
-/** @fileOverview Javascript cryptography implementation.
- *
- * Crush to remove comments, shorten variable names and
- * generally reduce transmission size.
- *
- * @author Emily Stark
- * @author Mike Hamburg
- * @author Dan Boneh
- */
-
-"use strict";
-/*jslint indent: 2, bitwise: false, nomen: false, plusplus: false, white: false, regexp: false */
-/*global document, window, escape, unescape, module, require, Uint32Array */
-
-/** @namespace The Stanford Javascript Crypto Library, top-level namespace. */
-var sjcl = {
-  /** @namespace Symmetric ciphers. */
-  cipher: {},
-
-  /** @namespace Hash functions.  Right now only SHA256 is implemented. */
-  hash: {},
-
-  /** @namespace Key exchange functions.  Right now only SRP is implemented. */
-  keyexchange: {},
-  
-  /** @namespace Block cipher modes of operation. */
-  mode: {},
-
-  /** @namespace Miscellaneous.  HMAC and PBKDF2. */
-  misc: {},
-  
-  /**
-   * @namespace Bit array encoders and decoders.
-   *
-   * @description
-   * The members of this namespace are functions which translate between
-   * SJCL's bitArrays and other objects (usually strings).  Because it
-   * isn't always clear which direction is encoding and which is decoding,
-   * the method names are "fromBits" and "toBits".
-   */
-  codec: {},
-  
-  /** @namespace Exceptions. */
-  exception: {
-    /** @constructor Ciphertext is corrupt. */
-    corrupt: function(message) {
-      this.toString = function() { return "CORRUPT: "+this.message; };
-      this.message = message;
-    },
-    
-    /** @constructor Invalid parameter. */
-    invalid: function(message) {
-      this.toString = function() { return "INVALID: "+this.message; };
-      this.message = message;
-    },
-    
-    /** @constructor Bug or missing feature in SJCL. @constructor */
-    bug: function(message) {
-      this.toString = function() { return "BUG: "+this.message; };
-      this.message = message;
-    },
-
-    /** @constructor Something isn't ready. */
-    notReady: function(message) {
-      this.toString = function() { return "NOT READY: "+this.message; };
-      this.message = message;
-    }
-  }
-};
-
-if(typeof module !== 'undefined' && module.exports){
-  module.exports = sjcl;
-}
-/** @fileOverview Arrays of bits, encoded as arrays of Numbers.
- *
- * @author Emily Stark
- * @author Mike Hamburg
- * @author Dan Boneh
- */
-
-/** @namespace Arrays of bits, encoded as arrays of Numbers.
- *
- * @description
- * <p>
- * These objects are the currency accepted by SJCL's crypto functions.
- * </p>
- *
- * <p>
- * Most of our crypto primitives operate on arrays of 4-byte words internally,
- * but many of them can take arguments that are not a multiple of 4 bytes.
- * This library encodes arrays of bits (whose size need not be a multiple of 8
- * bits) as arrays of 32-bit words.  The bits are packed, big-endian, into an
- * array of words, 32 bits at a time.  Since the words are double-precision
- * floating point numbers, they fit some extra data.  We use this (in a private,
- * possibly-changing manner) to encode the number of bits actually  present
- * in the last word of the array.
- * </p>
- *
- * <p>
- * Because bitwise ops clear this out-of-band data, these arrays can be passed
- * to ciphers like AES which want arrays of words.
- * </p>
- */
-sjcl.bitArray = {
-  /**
-   * Array slices in units of bits.
-   * @param {bitArray} a The array to slice.
-   * @param {Number} bstart The offset to the start of the slice, in bits.
-   * @param {Number} bend The offset to the end of the slice, in bits.  If this is undefined,
-   * slice until the end of the array.
-   * @return {bitArray} The requested slice.
-   */
-  bitSlice: function (a, bstart, bend) {
-    a = sjcl.bitArray._shiftRight(a.slice(bstart/32), 32 - (bstart & 31)).slice(1);
-    return (bend === undefined) ? a : sjcl.bitArray.clamp(a, bend-bstart);
-  },
-
-  /**
-   * Extract a number packed into a bit array.
-   * @param {bitArray} a The array to slice.
-   * @param {Number} bstart The offset to the start of the slice, in bits.
-   * @param {Number} length The length of the number to extract.
-   * @return {Number} The requested slice.
-   */
-  extract: function(a, bstart, blength) {
-    // FIXME: this Math.floor is not necessary at all, but for some reason
-    // seems to suppress a bug in the Chromium JIT.
-    var x, sh = Math.floor((-bstart-blength) & 31);
-    if ((bstart + blength - 1 ^ bstart) & -32) {
-      // it crosses a boundary
-      x = (a[bstart/32|0] << (32 - sh)) ^ (a[bstart/32+1|0] >>> sh);
-    } else {
-      // within a single word
-      x = a[bstart/32|0] >>> sh;
-    }
-    return x & ((1<<blength) - 1);
-  },
-
-  /**
-   * Concatenate two bit arrays.
-   * @param {bitArray} a1 The first array.
-   * @param {bitArray} a2 The second array.
-   * @return {bitArray} The concatenation of a1 and a2.
-   */
-  concat: function (a1, a2) {
-    if (a1.length === 0 || a2.length === 0) {
-      return a1.concat(a2);
-    }
-    
-    var last = a1[a1.length-1], shift = sjcl.bitArray.getPartial(last);
-    if (shift === 32) {
-      return a1.concat(a2);
-    } else {
-      return sjcl.bitArray._shiftRight(a2, shift, last|0, a1.slice(0,a1.length-1));
-    }
-  },
-
-  /**
-   * Find the length of an array of bits.
-   * @param {bitArray} a The array.
-   * @return {Number} The length of a, in bits.
-   */
-  bitLength: function (a) {
-    var l = a.length, x;
-    if (l === 0) { return 0; }
-    x = a[l - 1];
-    return (l-1) * 32 + sjcl.bitArray.getPartial(x);
-  },
-
-  /**
-   * Truncate an array.
-   * @param {bitArray} a The array.
-   * @param {Number} len The length to truncate to, in bits.
-   * @return {bitArray} A new array, truncated to len bits.
-   */
-  clamp: function (a, len) {
-    if (a.length * 32 < len) { return a; }
-    a = a.slice(0, Math.ceil(len / 32));
-    var l = a.length;
-    len = len & 31;
-    if (l > 0 && len) {
-      a[l-1] = sjcl.bitArray.partial(len, a[l-1] & 0x80000000 >> (len-1), 1);
-    }
-    return a;
-  },
-
-  /**
-   * Make a partial word for a bit array.
-   * @param {Number} len The number of bits in the word.
-   * @param {Number} x The bits.
-   * @param {Number} [0] _end Pass 1 if x has already been shifted to the high side.
-   * @return {Number} The partial word.
-   */
-  partial: function (len, x, _end) {
-    if (len === 32) { return x; }
-    return (_end ? x|0 : x << (32-len)) + len * 0x10000000000;
-  },
-
-  /**
-   * Get the number of bits used by a partial word.
-   * @param {Number} x The partial word.
-   * @return {Number} The number of bits used by the partial word.
-   */
-  getPartial: function (x) {
-    return Math.round(x/0x10000000000) || 32;
-  },
-
-  /**
-   * Compare two arrays for equality in a predictable amount of time.
-   * @param {bitArray} a The first array.
-   * @param {bitArray} b The second array.
-   * @return {boolean} true if a == b; false otherwise.
-   */
-  equal: function (a, b) {
-    if (sjcl.bitArray.bitLength(a) !== sjcl.bitArray.bitLength(b)) {
-      return false;
-    }
-    var x = 0, i;
-    for (i=0; i<a.length; i++) {
-      x |= a[i]^b[i];
-    }
-    return (x === 0);
-  },
-
-  /** Shift an array right.
-   * @param {bitArray} a The array to shift.
-   * @param {Number} shift The number of bits to shift.
-   * @param {Number} [carry=0] A byte to carry in
-   * @param {bitArray} [out=[]] An array to prepend to the output.
-   * @private
-   */
-  _shiftRight: function (a, shift, carry, out) {
-    var i, last2=0, shift2;
-    if (out === undefined) { out = []; }
-    
-    for (; shift >= 32; shift -= 32) {
-      out.push(carry);
-      carry = 0;
-    }
-    if (shift === 0) {
-      return out.concat(a);
-    }
-    
-    for (i=0; i<a.length; i++) {
-      out.push(carry | a[i]>>>shift);
-      carry = a[i] << (32-shift);
-    }
-    last2 = a.length ? a[a.length-1] : 0;
-    shift2 = sjcl.bitArray.getPartial(last2);
-    out.push(sjcl.bitArray.partial(shift+shift2 & 31, (shift + shift2 > 32) ? carry : out.pop(),1));
-    return out;
-  },
-  
-  /** xor a block of 4 words together.
-   * @private
-   */
-  _xor4: function(x,y) {
-    return [x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]];
-  }
-};
-/** @fileOverview Bit array codec implementations.
- *
- * @author Emily Stark
- * @author Mike Hamburg
- * @author Dan Boneh
- */
- 
-/** @namespace UTF-8 strings */
-sjcl.codec.utf8String = {
-  /** Convert from a bitArray to a UTF-8 string. */
-  fromBits: function (arr) {
-    var out = "", bl = sjcl.bitArray.bitLength(arr), i, tmp;
-    for (i=0; i<bl/8; i++) {
-      if ((i&3) === 0) {
-        tmp = arr[i/4];
-      }
-      out += String.fromCharCode(tmp >>> 24);
-      tmp <<= 8;
-    }
-    return decodeURIComponent(escape(out));
-  },
-  
-  /** Convert from a UTF-8 string to a bitArray. */
-  toBits: function (str) {
-    str = unescape(encodeURIComponent(str));
-    var out = [], i, tmp=0;
-    for (i=0; i<str.length; i++) {
-      tmp = tmp << 8 | str.charCodeAt(i);
-      if ((i&3) === 3) {
-        out.push(tmp);
-        tmp = 0;
-      }
-    }
-    if (i&3) {
-      out.push(sjcl.bitArray.partial(8*(i&3), tmp));
-    }
-    return out;
-  }
-};
-/** @fileOverview Bit array codec implementations.
- *
- * @author Emily Stark
- * @author Mike Hamburg
- * @author Dan Boneh
- */
-
-/** @namespace Hexadecimal */
-sjcl.codec.hex = {
-  /** Convert from a bitArray to a hex string. */
-  fromBits: function (arr) {
-    var out = "", i;
-    for (i=0; i<arr.length; i++) {
-      out += ((arr[i]|0)+0xF00000000000).toString(16).substr(4);
-    }
-    return out.substr(0, sjcl.bitArray.bitLength(arr)/4);//.replace(/(.{8})/g, "$1 ");
-  },
-  /** Convert from a hex string to a bitArray. */
-  toBits: function (str) {
-    var i, out=[], len;
-    str = str.replace(/\s|0x/g, "");
-    len = str.length;
-    str = str + "00000000";
-    for (i=0; i<str.length; i+=8) {
-      out.push(parseInt(str.substr(i,8),16)^0);
-    }
-    return sjcl.bitArray.clamp(out, len*4);
-  }
-};
-
-/** @fileOverview Javascript SHA-256 implementation.
- *
- * An older version of this implementation is available in the public
- * domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh,
- * Stanford University 2008-2010 and BSD-licensed for liability
- * reasons.
- *
- * Special thanks to Aldo Cortesi for pointing out several bugs in
- * this code.
- *
- * @author Emily Stark
- * @author Mike Hamburg
- * @author Dan Boneh
- */
-
-/**
- * Context for a SHA-256 operation in progress.
- * @constructor
- * @class Secure Hash Algorithm, 256 bits.
- */
-sjcl.hash.sha256 = function (hash) {
-  if (!this._key[0]) { this._precompute(); }
-  if (hash) {
-    this._h = hash._h.slice(0);
-    this._buffer = hash._buffer.slice(0);
-    this._length = hash._length;
-  } else {
-    this.reset();
-  }
-};
-
-/**
- * Hash a string or an array of words.
- * @static
- * @param {bitArray|String} data the data to hash.
- * @return {bitArray} The hash value, an array of 16 big-endian words.
- */
-sjcl.hash.sha256.hash = function (data) {
-  return (new sjcl.hash.sha256()).update(data).finalize();
-};
-
-sjcl.hash.sha256.prototype = {
-  /**
-   * The hash's block size, in bits.
-   * @constant
-   */
-  blockSize: 512,
-   
-  /**
-   * Reset the hash state.
-   * @return this
-   */
-  reset:function () {
-    this._h = this._init.slice(0);
-    this._buffer = [];
-    this._length = 0;
-    return this;
-  },
-  
-  /**
-   * Input several words to the hash.
-   * @param {bitArray|String} data the data to hash.
-   * @return this
-   */
-  update: function (data) {
-    if (typeof data === "string") {
-      data = sjcl.codec.utf8String.toBits(data);
-    }
-    var i, b = this._buffer = sjcl.bitArray.concat(this._buffer, data),
-        ol = this._length,
-        nl = this._length = ol + sjcl.bitArray.bitLength(data);
-    for (i = 512+ol & -512; i <= nl; i+= 512) {
-      this._block(b.splice(0,16));
-    }
-    return this;
-  },
-  
-  /**
-   * Complete hashing and output the hash value.
-   * @return {bitArray} The hash value, an array of 8 big-endian words.
-   */
-  finalize:function () {
-    var i, b = this._buffer, h = this._h;
-
-    // Round out and push the buffer
-    b = sjcl.bitArray.concat(b, [sjcl.bitArray.partial(1,1)]);
-    
-    // Round out the buffer to a multiple of 16 words, less the 2 length words.
-    for (i = b.length + 2; i & 15; i++) {
-      b.push(0);
-    }
-    
-    // append the length
-    b.push(Math.floor(this._length / 0x100000000));
-    b.push(this._length | 0);
-
-    while (b.length) {
-      this._block(b.splice(0,16));
-    }
-
-    this.reset();
-    return h;
-  },
-
-  /**
-   * The SHA-256 initialization vector, to be precomputed.
-   * @private
-   */
-  _init:[],
-  /*
-  _init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19],
-  */
-  
-  /**
-   * The SHA-256 hash key, to be precomputed.
-   * @private
-   */
-  _key:[],
-  /*
-  _key:
-    [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
-     0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
-     0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
-     0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
-     0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
-     0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
-     0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
-     0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2],
-  */
-
-
-  /**
-   * Function to precompute _init and _key.
-   * @private
-   */
-  _precompute: function () {
-    var i = 0, prime = 2, factor;
-
-    function frac(x) { return (x-Math.floor(x)) * 0x100000000 | 0; }
-
-    outer: for (; i<64; prime++) {
-      for (factor=2; factor*factor <= prime; factor++) {
-        if (prime % factor === 0) {
-          // not a prime
-          continue outer;
-        }
-      }
-      
-      if (i<8) {
-        this._init[i] = frac(Math.pow(prime, 1/2));
-      }
-      this._key[i] = frac(Math.pow(prime, 1/3));
-      i++;
-    }
-  },
-  
-  /**
-   * Perform one cycle of SHA-256.
-   * @param {bitArray} words one block of words.
-   * @private
-   */
-  _block:function (words) {  
-    var i, tmp, a, b,
-      w = words.slice(0),
-      h = this._h,
-      k = this._key,
-      h0 = h[0], h1 = h[1], h2 = h[2], h3 = h[3],
-      h4 = h[4], h5 = h[5], h6 = h[6], h7 = h[7];
-
-    /* Rationale for placement of |0 :
-     * If a value can overflow is original 32 bits by a factor of more than a few
-     * million (2^23 ish), there is a possibility that it might overflow the
-     * 53-bit mantissa and lose precision.
-     *
-     * To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
-     * propagates around the loop, and on the hash state h[].  I don't believe
-     * that the clamps on h4 and on h0 are strictly necessary, but it's close
-     * (for h4 anyway), and better safe than sorry.
-     *
-     * The clamps on h[] are necessary for the output to be correct even in the
-     * common case and for short inputs.
-     */
-    for (i=0; i<64; i++) {
-      // load up the input word for this round
-      if (i<16) {
-        tmp = w[i];
-      } else {
-        a   = w[(i+1 ) & 15];
-        b   = w[(i+14) & 15];
-        tmp = w[i&15] = ((a>>>7  ^ a>>>18 ^ a>>>3  ^ a<<25 ^ a<<14) + 
-                         (b>>>17 ^ b>>>19 ^ b>>>10 ^ b<<15 ^ b<<13) +
-                         w[i&15] + w[(i+9) & 15]) | 0;
-      }
-      
-      tmp = (tmp + h7 + (h4>>>6 ^ h4>>>11 ^ h4>>>25 ^ h4<<26 ^ h4<<21 ^ h4<<7) +  (h6 ^ h4&(h5^h6)) + k[i]); // | 0;
-      
-      // shift register
-      h7 = h6; h6 = h5; h5 = h4;
-      h4 = h3 + tmp | 0;
-      h3 = h2; h2 = h1; h1 = h0;
-
-      h0 = (tmp +  ((h1&h2) ^ (h3&(h1^h2))) + (h1>>>2 ^ h1>>>13 ^ h1>>>22 ^ h1<<30 ^ h1<<19 ^ h1<<10)) | 0;
-    }
-
-    h[0] = h[0]+h0 | 0;
-    h[1] = h[1]+h1 | 0;
-    h[2] = h[2]+h2 | 0;
-    h[3] = h[3]+h3 | 0;
-    h[4] = h[4]+h4 | 0;
-    h[5] = h[5]+h5 | 0;
-    h[6] = h[6]+h6 | 0;
-    h[7] = h[7]+h7 | 0;
-  }
-};
-
-
-/** @fileOverview HMAC implementation.
- *
- * @author Emily Stark
- * @author Mike Hamburg
- * @author Dan Boneh
- */
-
-/** HMAC with the specified hash function.
- * @constructor
- * @param {bitArray} key the key for HMAC.
- * @param {Object} [hash=sjcl.hash.sha256] The hash function to use.
- */
-sjcl.misc.hmac = function (key, Hash) {
-  this._hash = Hash = Hash || sjcl.hash.sha256;
-  var exKey = [[],[]], i,
-      bs = Hash.prototype.blockSize / 32;
-  this._baseHash = [new Hash(), new Hash()];
-
-  if (key.length > bs) {
-    key = Hash.hash(key);
-  }
-  
-  for (i=0; i<bs; i++) {
-    exKey[0][i] = key[i]^0x36363636;
-    exKey[1][i] = key[i]^0x5C5C5C5C;
-  }
-  
-  this._baseHash[0].update(exKey[0]);
-  this._baseHash[1].update(exKey[1]);
-  this._resultHash = new Hash(this._baseHash[0]);
-};
-
-/** HMAC with the specified hash function.  Also called encrypt since it's a prf.
- * @param {bitArray|String} data The data to mac.
- */
-sjcl.misc.hmac.prototype.encrypt = sjcl.misc.hmac.prototype.mac = function (data) {
-  if (!this._updated) {
-    this.update(data);
-    return this.digest(data);
-  } else {
-    throw new sjcl.exception.invalid("encrypt on already updated hmac called!");
-  }
-};
-
-sjcl.misc.hmac.prototype.reset = function () {
-  this._resultHash = new this._hash(this._baseHash[0]);
-  this._updated = false;
-};
-
-sjcl.misc.hmac.prototype.update = function (data) {
-  this._updated = true;
-  this._resultHash.update(data);
-};
-
-sjcl.misc.hmac.prototype.digest = function () {
-  var w = this._resultHash.finalize(), result = new (this._hash)(this._baseHash[1]).update(w).finalize();
-
-  this.reset();
-
-  return result;
-};
\ No newline at end of file
deleted file mode 100644
--- a/browser/components/loop/content/shared/libs/token.js
+++ /dev/null
@@ -1,78 +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/. */
-
-'use strict';
- 
-var PREFIX_NAME = 'identity.mozilla.com/picl/v1/';
-var bitSlice = sjcl.bitArray.bitSlice;
-var salt = sjcl.codec.hex.toBits('');
-
-/**
- * hkdf - The HMAC-based Key Derivation Function
- * based on https://github.com/mozilla/node-hkdf
- *
- * @class hkdf
- * @param {bitArray} ikm Initial keying material
- * @param {bitArray} info Key derivation data
- * @param {bitArray} salt Salt
- * @param {integer} length Length of the derived key in bytes
- * @return promise object- It will resolve with `output` data
- */
-function hkdf(ikm, info, salt, length, callback) {
-  var mac = new sjcl.misc.hmac(salt, sjcl.hash.sha256);
-  mac.update(ikm);
-
-  // compute the PRK
-  var prk = mac.digest();
-
-  // hash length is 32 because only sjcl.hash.sha256 is used at this moment
-  var hashLength = 32;
-  var num_blocks = Math.ceil(length / hashLength);
-  var prev = sjcl.codec.hex.toBits('');
-  var output = '';
-
-  for (var i = 0; i < num_blocks; i++) {
-    var hmac = new sjcl.misc.hmac(prk, sjcl.hash.sha256);
-
-    var input = sjcl.bitArray.concat(
-      sjcl.bitArray.concat(prev, info),
-      sjcl.codec.utf8String.toBits((String.fromCharCode(i + 1)))
-    );
-
-    hmac.update(input);
-
-    prev = hmac.digest();
-    output += sjcl.codec.hex.fromBits(prev);
-  }
-
-  var truncated = sjcl.bitArray.clamp(sjcl.codec.hex.toBits(output), length * 8);
-
-  callback(truncated);
-}
-
- 
-/**
-* @class hawkCredentials
-* @method deriveHawkCredentials
-* @param {String} tokenHex
-* @param {String} context
-* @param {int} size
-* @returns {Promise}
-*/
-function deriveHawkCredentials(tokenHex, context, size, callback) {
-  var token = sjcl.codec.hex.toBits(tokenHex);
-  var info = sjcl.codec.utf8String.toBits(PREFIX_NAME + context);
-
-  hkdf(token, info, salt, size || 3 * 32, function(out) {
-    var authKey = bitSlice(out, 8 * 32, 8 * 64);
-    var bundleKey = bitSlice(out, 8 * 64);
-    callback({
-      algorithm: 'sha256',
-      id: sjcl.codec.hex.fromBits(bitSlice(out, 0, 8 * 32)),
-      key: sjcl.codec.hex.fromBits(authKey),
-      bundleKey: bundleKey
-    });
-  });
-}
- 
--- a/browser/components/loop/jar.mn
+++ b/browser/components/loop/jar.mn
@@ -6,30 +6,27 @@ browser.jar:
   content/browser/loop/conversation.html             (content/conversation.html)
   content/browser/loop/panel.html                    (content/panel.html)
   content/browser/loop/shared/css/common.css         (content/shared/css/common.css)
   content/browser/loop/shared/css/panel.css          (content/shared/css/panel.css)
   content/browser/loop/shared/css/conversation.css   (content/shared/css/conversation.css)
   content/browser/loop/shared/img/icon_32.png        (content/shared/img/icon_32.png)
   content/browser/loop/shared/img/icon_64.png        (content/shared/img/icon_64.png)
   content/browser/loop/shared/img/loading-icon.gif   (content/shared/img/loading-icon.gif)
-  content/browser/loop/shared/js/client.js           (content/shared/js/client.js)
   content/browser/loop/shared/js/models.js           (content/shared/js/models.js)
   content/browser/loop/shared/js/router.js           (content/shared/js/router.js)
   content/browser/loop/shared/js/views.js            (content/shared/js/views.js)
   content/browser/loop/shared/libs/lodash-2.4.1.js   (content/shared/libs/lodash-2.4.1.js)
   content/browser/loop/shared/libs/jquery-2.1.0.js   (content/shared/libs/jquery-2.1.0.js)
   content/browser/loop/shared/libs/backbone-1.1.2.js (content/shared/libs/backbone-1.1.2.js)
-  content/browser/loop/shared/libs/sjcl-dev20140604.js        (content/shared/libs/sjcl-dev20140604.js)
-  content/browser/loop/shared/libs/token.js          (content/shared/libs/token.js)
-  content/browser/loop/shared/libs/hawk-browser-2.2.1.js      (content/shared/libs/hawk-browser-2.2.1.js)
   content/browser/loop/shared/sounds/Firefox-Long.ogg   (content/shared/sounds/Firefox-Long.ogg)
   content/browser/loop/libs/l10n.js                  (content/libs/l10n.js)
+  content/browser/loop/js/client.js                  (content/js/client.js)
+  content/browser/loop/js/conversation.js            (content/js/conversation.js)
   content/browser/loop/js/desktopRouter.js           (content/js/desktopRouter.js)
-  content/browser/loop/js/conversation.js            (content/js/conversation.js)
   content/browser/loop/js/panel.js                   (content/js/panel.js)
   # Partner SDK assets
   content/browser/loop/libs/sdk.js                                             (content/libs/sdk.js)
   content/browser/loop/otcdn/webrtc/v2.2.5/css/ot.min.css                             (content/libs/otcdn/webrtc/v2.2.5/css/ot.min.css)
   content/browser/loop/otcdn/webrtc/v2.2.5/js/dynamic_config.min.js                   (content/libs/otcdn/webrtc/v2.2.5/js/dynamic_config.min.js)
   content/browser/loop/otcdn/webrtc/v2.2.5/images/rtc/access-denied-chrome.png        (content/libs/otcdn/webrtc/v2.2.5/images/rtc/access-denied-chrome.png)
   content/browser/loop/otcdn/webrtc/v2.2.5/images/rtc/access-denied-copy-firefox.png  (content/libs/otcdn/webrtc/v2.2.5/images/rtc/access-denied-copy-firefox.png)
   content/browser/loop/otcdn/webrtc/v2.2.5/images/rtc/access-denied-firefox.png       (content/libs/otcdn/webrtc/v2.2.5/images/rtc/access-denied-firefox.png)
--- a/browser/components/loop/standalone/content/index.html
+++ b/browser/components/loop/standalone/content/index.html
@@ -25,20 +25,20 @@
     <script src="https://static.opentok.com/webrtc/v2.2/js/opentok.min.js"></script>
     <script type="text/javascript" src="libs/webl10n-20130617.js"></script>
     <script type="text/javascript" src="shared/libs/jquery-2.1.0.js"></script>
     <script type="text/javascript" src="shared/libs/lodash-2.4.1.js"></script>
     <script type="text/javascript" src="shared/libs/backbone-1.1.2.js"></script>
 
     <!-- app scripts -->
     <script type="text/javascript" src="config.js"></script>
-    <script type="text/javascript" src="shared/js/client.js"></script>
     <script type="text/javascript" src="shared/js/models.js"></script>
     <script type="text/javascript" src="shared/js/views.js"></script>
     <script type="text/javascript" src="shared/js/router.js"></script>
+    <script type="text/javascript" src="js/standaloneClient.js"></script>
     <script type="text/javascript" src="js/webapp.js"></script>
 
     <script>
     // Set the 'lang' and 'dir' attributes to <html> when the page is translated
     window.addEventListener('localized', function() {
       document.documentElement.lang = document.webL10n.getLanguage();
       document.documentElement.dir = document.webL10n.getDirection();
     }, false);
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/standalone/content/js/standaloneClient.js
@@ -0,0 +1,123 @@
+/* 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/. */
+
+/* global loop:true, hawk, deriveHawkCredentials */
+
+var loop = loop || {};
+loop.StandaloneClient = (function($) {
+  "use strict";
+
+  // The expected properties to be returned from the POST /calls request.
+  var expectedCallsProperties = [ "sessionId", "sessionToken", "apiKey" ];
+
+  /**
+   * Loop server standalone client.
+   *
+   * @param {Object} settings Settings object.
+   */
+  function StandaloneClient(settings) {
+    settings = settings || {};
+    if (!settings.baseServerUrl) {
+      throw new Error("missing required baseServerUrl");
+    }
+
+    this.settings = settings;
+  }
+
+  StandaloneClient.prototype = {
+    /**
+     * Validates a data object to confirm it has the specified properties.
+     *
+     * @param  {Object} The data object to verify
+     * @param  {Array} The list of properties to verify within the object
+     * @return This returns either the specific property if only one
+     *         property is specified, or it returns all properties
+     */
+    _validate: function(data, properties) {
+      if (typeof data !== "object") {
+        throw new Error("Invalid data received from server");
+      }
+
+      properties.forEach(function (property) {
+        if (!data.hasOwnProperty(property)) {
+          throw new Error("Invalid data received from server - missing " +
+            property);
+        }
+      });
+
+      if (properties.length == 1) {
+        return data[properties[0]];
+      }
+
+      return data;
+    },
+
+    /**
+     * Generic handler for XHR failures.
+     *
+     * @param {Function} cb Callback(err)
+     * @param jqXHR See jQuery docs
+     * @param textStatus See jQuery docs
+     * @param errorThrown See jQuery docs
+     */
+    _failureHandler: function(cb, jqXHR, textStatus, errorThrown) {
+      var error = "Unknown error.",
+          jsonRes = jqXHR && jqXHR.responseJSON || {};
+      // Received error response format:
+      // { "status": "errors",
+      //   "errors": [{
+      //      "location": "url",
+      //      "name": "token",
+      //      "description": "invalid token"
+      // }]}
+      if (jsonRes.status === "errors" && Array.isArray(jsonRes.errors)) {
+        error = "Details: " + jsonRes.errors.map(function(err) {
+          return Object.keys(err).map(function(field) {
+            return field + ": " + err[field];
+          }).join(", ");
+        }).join("; ");
+      }
+      var message = "HTTP " + jqXHR.status + " " + errorThrown +
+          "; " + error;
+      console.error(message);
+      cb(new Error(message));
+    },
+
+    /**
+     * Posts a call request to the server for a call represented by the
+     * loopToken. Will return the session data for the call.
+     *
+     * @param  {String} loopToken The loopToken representing the call
+     * @param  {String} callType The type of media in the call, e.g.
+     *                           "audio" or "audio-video"
+     * @param  {Function} cb Callback(err, sessionData)
+     */
+    requestCallInfo: function(loopToken, callType, cb) {
+      if (!loopToken) {
+        throw new Error("missing required parameter loopToken");
+      }
+
+      var req = $.ajax({
+        url:         this.settings.baseServerUrl + "/calls/" + loopToken,
+        method:      "POST",
+        contentType: "application/json",
+        dataType:    "json",
+        data: JSON.stringify({callType: callType})
+      });
+
+      req.done(function(sessionData) {
+        try {
+          cb(null, this._validate(sessionData, expectedCallsProperties));
+        } catch (err) {
+          console.log("Error requesting call info", err);
+          cb(err);
+        }
+      }.bind(this));
+
+      req.fail(this._failureHandler.bind(this, cb));
+    },
+  };
+
+  return StandaloneClient;
+})(jQuery);
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -87,18 +87,23 @@ loop.webapp = (function($, _, OT) {
     /**
      * Initiates the call.
      *
      * @param {SubmitEvent} event
      */
     initiate: function(event) {
       event.preventDefault();
       this.model.initiate({
-        baseServerUrl: baseServerUrl,
-        outgoing: true
+        client: new loop.StandaloneClient({
+          baseServerUrl: baseServerUrl,
+        }),
+        outgoing: true,
+        // For now, we assume both audio and video as there is no
+        // other option to select.
+        callType: "audio-video"
       });
       this.disableForm();
     }
   });
 
   /**
    * Webapp Router.
    */
rename from browser/components/loop/test/shared/client_test.js
rename to browser/components/loop/test/desktop-local/client_test.js
--- a/browser/components/loop/test/shared/client_test.js
+++ b/browser/components/loop/test/desktop-local/client_test.js
@@ -1,291 +1,184 @@
 /* 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/. */
 
 /*global loop, sinon, it, beforeEach, afterEach, describe, hawk */
 
 var expect = chai.expect;
 
-describe("loop.shared.Client", function() {
+describe("loop.Client", function() {
   "use strict";
 
   var sandbox,
-      fakeXHR,
-      requests = [],
       callback,
+      client,
       mozLoop,
-      fakeToken;
+      fakeToken,
+      hawkRequestStub;
 
-  var fakeErrorRes = JSON.stringify({
-      status: "errors",
-      errors: [{
-        location: "url",
-        name: "token",
-        description: "invalid token"
-      }]
-    });
+  var fakeErrorRes = {
+      code: 400,
+      errno: 400,
+      error: "Request Failed",
+      message: "invalid token"
+    };
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
-    fakeXHR = sandbox.useFakeXMLHttpRequest();
-    requests = [];
-    // https://github.com/cjohansen/Sinon.JS/issues/393
-    fakeXHR.xhr.onCreate = function (xhr) {
-      requests.push(xhr);
-    };
     callback = sinon.spy();
     fakeToken = "fakeTokenText";
+    mozLoop = {
+      getLoopCharPref: sandbox.stub()
+        .returns(null)
+        .withArgs("hawk-session-token")
+        .returns(fakeToken),
+      ensureRegistered: sinon.stub().callsArgWith(0, null),
+      noteCallUrlExpiry: sinon.spy(),
+      hawkRequest: sinon.stub()
+    };
+    // Alias for clearer tests.
+    hawkRequestStub = mozLoop.hawkRequest;
+    client = new loop.Client({
+      mozLoop: mozLoop
+    });
   });
 
   afterEach(function() {
     sandbox.restore();
   });
 
-  describe("loop.shared.Client", function() {
-    describe("#constructor", function() {
-      it("should require a baseServerUrl setting", function() {
-        expect(function() {
-          new loop.shared.Client();
-        }).to.Throw(Error, /required/);
-      });
-    });
-
+  describe("loop.Client", function() {
     describe("#requestCallUrl", function() {
-      var client;
-
-      beforeEach(function() {
-        window.navigator.mozLoop = {
-          ensureRegistered: sinon.stub().callsArgWith(0, null),
-          noteCallUrlExpiry: sinon.spy(),
-          getLoopCharPref: sandbox.stub()
-            .returns(null)
-            .withArgs("hawk-session-token")
-            .returns(fakeToken)
-        };
-        client = new loop.shared.Client(
-          {baseServerUrl: "http://fake.api", mozLoop: window.navigator.mozLoop}
-        );
-      });
-
       it("should ensure loop is registered", function() {
         client.requestCallUrl("foo", callback);
 
-        sinon.assert.calledOnce(navigator.mozLoop.ensureRegistered);
+        sinon.assert.calledOnce(mozLoop.ensureRegistered);
       });
 
       it("should send an error when registration fails", function() {
-        navigator.mozLoop.ensureRegistered.callsArgWith(0, "offline");
+        mozLoop.ensureRegistered.callsArgWith(0, "offline");
 
         client.requestCallUrl("foo", callback);
 
         sinon.assert.calledOnce(callback);
         sinon.assert.calledWithExactly(callback, "offline");
       });
 
       it("should post to /call-url/", function() {
         client.requestCallUrl("foo", callback);
 
-        expect(requests).to.have.length.of(1);
-        expect(requests[0].method).to.be.equal("POST");
-        expect(requests[0].url).to.be.equal("http://fake.api/call-url/");
-        expect(requests[0].requestBody).to.be.equal('callerId=foo');
+        sinon.assert.calledOnce(hawkRequestStub);
+        sinon.assert.calledWith(hawkRequestStub,
+                                "/call-url/", "POST", {callerId: "foo"});
       });
 
-      it("should set the XHR Authorization header", function() {
-        sandbox.stub(hawk.client, "header").returns( {field: fakeToken} );
-        client._credentials = {
-          // XXX we probably really want to stub out external module calls
-          // eg deriveHawkCredentials, rather supplying them with valid arguments
-          // like we're doing here:
-          key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
-          algorithm: 'sha256',
-          user: 'Steve'
-        };
-
-        client.requestCallUrl("foo", callback);
-
-        expect(requests[0].requestHeaders.Authorization).to.equal(fakeToken);
-      });
-
-      it("should request a call url", function() {
+      it("should call the callback with the url when the request succeeds", function() {
         var callUrlData = {
-          "call_url": "fakeCallUrl",
+          "callUrl": "fakeCallUrl",
           "expiresAt": 60
         };
 
+        // Sets up the hawkRequest stub to trigger the callback with no error
+        // and the url.
+        hawkRequestStub.callsArgWith(3, null,
+                                     JSON.stringify(callUrlData));
+
         client.requestCallUrl("foo", callback);
-        requests[0].respond(200, {"Content-Type": "application/json"},
-                            JSON.stringify(callUrlData));
 
         sinon.assert.calledWithExactly(callback, null, callUrlData);
       });
 
-      it("should note the call url expiry", function() {
+      it("should note the call url expiry when the request succeeds", function() {
         var callUrlData = {
-          "call_url": "fakeCallUrl",
+          "callUrl": "fakeCallUrl",
           "expiresAt": 60
         };
 
+        // Sets up the hawkRequest stub to trigger the callback with no error
+        // and the url.
+        hawkRequestStub.callsArgWith(3, null,
+                                     JSON.stringify(callUrlData));
+
         client.requestCallUrl("foo", callback);
-        requests[0].respond(200, {"Content-Type": "application/json"},
-                            JSON.stringify(callUrlData));
 
         // expiresAt is in hours, and noteCallUrlExpiry wants seconds.
-        sinon.assert.calledWithExactly(navigator.mozLoop.noteCallUrlExpiry,
+        sinon.assert.calledOnce(mozLoop.noteCallUrlExpiry);
+        sinon.assert.calledWithExactly(mozLoop.noteCallUrlExpiry,
           60 * 60 * 60);
       });
 
       it("should send an error when the request fails", function() {
+        // Sets up the hawkRequest stub to trigger the callback with
+        // an error
+        hawkRequestStub.callsArgWith(3, fakeErrorRes);
+
         client.requestCallUrl("foo", callback);
 
-        expect(requests).to.have.length.of(1);
-        requests[0].respond(400, {"Content-Type": "application/json"},
-                            fakeErrorRes);
-
+        sinon.assert.calledOnce(callback);
         sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
           return /400.*invalid token/.test(err.message);
         }));
       });
 
       it("should send an error if the data is not valid", function() {
+        // Sets up the hawkRequest stub to trigger the callback with
+        // an error
+        hawkRequestStub.callsArgWith(3, null, "{}");
+
         client.requestCallUrl("foo", callback);
-        requests[0].respond(200, {"Content-Type": "application/json"},
-                            '{"bad": {}}');
 
+        sinon.assert.calledOnce(callback);
         sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
           return /Invalid data received/.test(err.message);
         }));
       });
     });
 
     describe("#requestCallsInfo", function() {
-      var client;
-
-      beforeEach(function() {
-        mozLoop = {
-          getLoopCharPref: sandbox.stub()
-            .returns(null)
-            .withArgs("hawk-session-token")
-            .returns(fakeToken)
-        };
-        client = new loop.shared.Client(
-          {baseServerUrl: "http://fake.api", mozLoop: mozLoop}
-        );
-      });
-
       it("should prevent launching a conversation when version is missing",
         function() {
           expect(function() {
             client.requestCallsInfo();
           }).to.Throw(Error, /missing required parameter version/);
         });
 
-      it("should request data for all calls", function() {
+      it("should perform a get on /calls", function() {
         client.requestCallsInfo(42, callback);
 
-        expect(requests).to.have.length.of(1);
-        expect(requests[0].url).to.be.equal("http://fake.api/calls?version=42");
-        expect(requests[0].method).to.be.equal("GET");
+        sinon.assert.calledOnce(hawkRequestStub);
+        sinon.assert.calledWith(hawkRequestStub,
+                                "/calls?version=42", "GET", null);
+
+      });
 
-        requests[0].respond(200, {"Content-Type": "application/json"},
-                                 '{"calls": [{"apiKey": "fake"}]}');
+      it("should request data for all calls", function() {
+        hawkRequestStub.callsArgWith(3, null,
+                                     '{"calls": [{"apiKey": "fake"}]}');
+
+        client.requestCallsInfo(42, callback);
+
         sinon.assert.calledWithExactly(callback, null, [{apiKey: "fake"}]);
       });
 
-      it("should set the XHR Authorization header", function() {
-        sandbox.stub(hawk.client, "header").returns( {field: fakeToken} );
-        // XXX we probably really want to stub out external module calls
-        // eg deriveHawkCredentials, rather supplying them with valid arguments
-        // like we're doing here:
-        client._credentials = {
-          key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
-          algorithm: 'sha256',
-          user: 'Steve'
-        };
+      it("should send an error when the request fails", function() {
+        hawkRequestStub.callsArgWith(3, fakeErrorRes);
 
-        client.requestCallsInfo("foo", callback);
-
-        expect(requests[0].requestHeaders.Authorization).to.equal(fakeToken);
-      });
-
-      it("should send an error when the request fails", function() {
         client.requestCallsInfo(42, callback);
 
-        requests[0].respond(400, {"Content-Type": "application/json"},
-                                 fakeErrorRes);
         sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
           return /400.*invalid token/.test(err.message);
         }));
       });
 
       it("should send an error if the data is not valid", function() {
+        hawkRequestStub.callsArgWith(3, null, "{}");
+
         client.requestCallsInfo(42, callback);
 
-        requests[0].respond(200, {"Content-Type": "application/json"},
-                                 '{"bad": {}}');
-        sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
-          return /Invalid data received/.test(err.message);
-        }));
-      });
-    });
-
-    describe("requestCallInfo", function() {
-      var client;
-
-      beforeEach(function() {
-        client = new loop.shared.Client(
-          {baseServerUrl: "http://fake.api", mozLoop: undefined}
-        );
-      });
-
-      it("should prevent launching a conversation when token is missing",
-        function() {
-          expect(function() {
-            client.requestCallInfo();
-          }).to.Throw(Error, /missing.*[Tt]oken/);
-        });
-
-      it("should post data for the given call", function() {
-        client.requestCallInfo("fake", callback);
-
-        expect(requests).to.have.length.of(1);
-        expect(requests[0].url).to.be.equal("http://fake.api/calls/fake");
-        expect(requests[0].method).to.be.equal("POST");
-      });
-
-      it("should receive call data for the given call", function() {
-        client.requestCallInfo("fake", callback);
-
-        var sessionData = {
-          sessionId: "one",
-          sessionToken: "two",
-          apiKey: "three"
-        };
-
-        requests[0].respond(200, {"Content-Type": "application/json"},
-                            JSON.stringify(sessionData));
-        sinon.assert.calledWithExactly(callback, null, sessionData);
-      });
-
-      it("should send an error when the request fails", function() {
-        client.requestCallInfo("fake", callback);
-
-        requests[0].respond(400, {"Content-Type": "application/json"},
-                            fakeErrorRes);
-        sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
-          return /400.*invalid token/.test(err.message);
-        }));
-      });
-
-      it("should send an error if the data is not valid", function() {
-        client.requestCallInfo("fake", callback);
-
-        requests[0].respond(200, {"Content-Type": "application/json"},
-                            '{"bad": "one"}');
         sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
           return /Invalid data received/.test(err.message);
         }));
       });
     });
   });
 });
--- a/browser/components/loop/test/desktop-local/index.html
+++ b/browser/components/loop/test/desktop-local/index.html
@@ -26,25 +26,26 @@
   <script src="../shared/vendor/sinon-1.9.0.js"></script>
   <script>
     /*global chai,mocha */
     chai.Assertion.includeStack = true;
     mocha.setup('bdd');
   </script>
 
   <!-- App scripts -->
-  <script src="../../content/shared/js/client.js"></script>
   <script src="../../content/shared/js/models.js"></script>
   <script src="../../content/shared/js/router.js"></script>
   <script src="../../content/shared/js/views.js"></script>
+  <script src="../../content/js/client.js"></script>
+  <script src="../../content/js/conversation.js"></script>
   <script src="../../content/js/desktopRouter.js"></script>
-  <script src="../../content/js/conversation.js"></script>
   <script src="../../content/js/panel.js"></script>
 
   <!-- Test scripts -->
+  <script src="client_test.js"></script>
   <script src="conversation_test.js"></script>
   <script src="panel_test.js"></script>
   <script>
     mocha.run(function () {
       $("#mocha").append("<p id='complete'>Complete.</p>");
     });
   </script>
 </body>
--- a/browser/components/loop/test/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -4,17 +4,18 @@
 
 /*global loop, sinon */
 
 var expect = chai.expect;
 
 describe("loop.panel", function() {
   "use strict";
 
-  var sandbox, notifier, fakeXHR, requests = [], savedMozLoop;
+  var sandbox, notifier, fakeXHR, requests = [], savedMozLoop,
+      fakeSeenToSPref = 0;
 
   function createTestRouter(fakeDocument) {
     return new loop.panel.PanelRouter({
       notifier: notifier,
       document: fakeDocument
     });
   }
 
@@ -40,18 +41,26 @@ describe("loop.panel", function() {
       get serverUrl() {
         return "http://example.com";
       },
       getStrings: function() {
         return "{}";
       },
       get locale() {
         return "en-US";
+      },
+      setLoopCharPref: sandbox.stub(),
+      getLoopCharPref: function () {
+        if (fakeSeenToSPref === 0) {
+          return null;
+        }
+        return 'seen';
       }
     };
+
     document.mozL10n.initialize(navigator.mozLoop);
   });
 
   afterEach(function() {
     delete navigator.mozLoop;
     $("#fixtures").empty();
     sandbox.restore();
   });
@@ -205,67 +214,67 @@ describe("loop.panel", function() {
 
   describe("loop.panel.PanelView", function() {
     beforeEach(function() {
       $("#fixtures").append('<div id="messages"></div><div id="main"></div>');
     });
 
     describe("#getCallUrl", function() {
       it("should reset all pending notifications", function() {
-        var requestCallUrl = sandbox.stub(loop.shared.Client.prototype,
+        var requestCallUrl = sandbox.stub(loop.Client.prototype,
                                           "requestCallUrl");
         var view = new loop.panel.PanelView({notifier: notifier}).render();
 
         view.getCallUrl({preventDefault: sandbox.spy()});
 
         sinon.assert.calledOnce(view.notifier.clear, "clear");
       });
 
       it("should request a call url to the server", function() {
-        var requestCallUrl = sandbox.stub(loop.shared.Client.prototype,
+        var requestCallUrl = sandbox.stub(loop.Client.prototype,
                                           "requestCallUrl");
         var view = new loop.panel.PanelView({notifier: notifier});
         sandbox.stub(view, "getNickname").returns("foo");
 
         view.getCallUrl({preventDefault: sandbox.spy()});
 
         sinon.assert.calledOnce(requestCallUrl);
         sinon.assert.calledWith(requestCallUrl, "foo");
       });
 
       it("should set the call url form in a pending state", function() {
-        var requestCallUrl = sandbox.stub(loop.shared.Client.prototype,
+        var requestCallUrl = sandbox.stub(loop.Client.prototype,
                                           "requestCallUrl");
         sandbox.stub(loop.panel.PanelView.prototype, "setPending");
 
         var view = new loop.panel.PanelView({notifier: notifier});
 
         view.getCallUrl({preventDefault: sandbox.spy()});
 
         sinon.assert.calledOnce(view.setPending);
       });
 
       it("should clear the pending state when a response is received",
         function() {
           sandbox.stub(loop.panel.PanelView.prototype,
                        "clearPending");
           var requestCallUrl = sandbox.stub(
-            loop.shared.Client.prototype, "requestCallUrl", function(_, cb) {
+            loop.Client.prototype, "requestCallUrl", function(_, cb) {
               cb("fake error");
             });
           var view = new loop.panel.PanelView({notifier: notifier});
 
           view.getCallUrl({preventDefault: sandbox.spy()});
 
           sinon.assert.calledOnce(view.clearPending);
         });
 
       it("should notify the user when the operation failed", function() {
         var requestCallUrl = sandbox.stub(
-          loop.shared.Client.prototype, "requestCallUrl", function(_, cb) {
+          loop.Client.prototype, "requestCallUrl", function(_, cb) {
             cb("fake error");
           });
         var view = new loop.panel.PanelView({notifier: notifier});
 
         view.getCallUrl({preventDefault: sandbox.spy()});
 
         sinon.assert.calledOnce(view.notifier.errorL10n);
         sinon.assert.calledWithExactly(view.notifier.errorL10n,
@@ -273,17 +282,17 @@ describe("loop.panel", function() {
       });
     });
 
     describe("#onCallUrlReceived", function() {
       var callUrlData;
 
       beforeEach(function() {
         callUrlData = {
-          call_url: "http://call.me/",
+          callUrl: "http://call.me/",
           expiresAt: 1000
         };
       });
 
       it("should update the text field with the call url", function() {
         var view = new loop.panel.PanelView({notifier: notifier});
         view.render();
 
@@ -317,11 +326,63 @@ describe("loop.panel", function() {
         var renderDnD = sandbox.stub(loop.panel.DoNotDisturbView.prototype,
                                      "render");
         var view = new loop.panel.PanelView({notifier: notifier});
 
         view.render();
 
         sinon.assert.calledOnce(renderDnD);
       });
+
+      it("should render a ToSView", function() {
+        var renderToS = sandbox.stub(loop.panel.ToSView.prototype, "render");
+        var view = new loop.panel.PanelView({notifier: notifier});
+
+        view.render();
+
+        sinon.assert.calledOnce(renderToS);
+      });
+    });
+
+    describe('loop.panel.ToSView', function() {
+
+      beforeEach(function() {
+
+        $('#fixtures').append('<div id="#tos-view"></div>');
+
+      });
+
+      // XXX Until it's possible to easily test creation of text,
+      // not doing so. As it stands, the magic in the L10nView
+      // class makes stubbing BaseView.render impractical.
+
+      it("should set the value of the loop.seenToS preference to 'seen'",
+        function() {
+          var ToSView = new loop.panel.ToSView({el: $("#tos-view")});
+
+          ToSView.render();
+
+          sinon.assert.calledOnce(navigator.mozLoop.setLoopCharPref);
+          sinon.assert.calledWithExactly(navigator.mozLoop.setLoopCharPref,
+            'seenToS', 'seen');
+        });
+
+      it("should render when the value of loop.seenToS is not set", function() {
+        var renderToS = sandbox.spy(loop.panel.ToSView.prototype, "render");
+        var ToSView = new loop.panel.ToSView({el: $('#tos-view')});
+
+        ToSView.render();
+
+        sinon.assert.calledOnce(renderToS);
+      });
+
+      it("should not render when the value of loop.seenToS is set to 'seen'",
+        function() {
+        var ToSView = new loop.panel.ToSView({el: $('#tos-view')});
+        fakeSeenToSPref = 1;
+
+        ToSView.render();
+
+        sinon.assert.notCalled(navigator.mozLoop.setLoopCharPref);
+      });
     });
   });
 });
--- a/browser/components/loop/test/shared/index.html
+++ b/browser/components/loop/test/shared/index.html
@@ -14,39 +14,34 @@
   </div>
   <div id="messages"></div>
   <div id="fixtures"></div>
 
   <!-- libs -->
   <script src="../../content/shared/libs/jquery-2.1.0.js"></script>
   <script src="../../content/shared/libs/lodash-2.4.1.js"></script>
   <script src="../../content/shared/libs/backbone-1.1.2.js"></script>
-  <script src="../../content/shared/libs/sjcl-dev20140604.js"></script>
-  <script src="../../content/shared/libs/token.js"></script>
-  <script src="../../content/shared/libs/hawk-browser-2.2.1.js"></script>
-  <script src="../../standalone/content/libs/webl10n-20130617.js"></script>
+ <script src="../../standalone/content/libs/webl10n-20130617.js"></script>
 
   <!-- test dependencies -->
   <script src="vendor/mocha-1.17.1.js"></script>
   <script src="vendor/chai-1.9.0.js"></script>
   <script src="vendor/sinon-1.9.0.js"></script>
   <script>
     /*global chai, mocha */
     chai.Assertion.includeStack = true;
     mocha.setup('bdd');
   </script>
 
   <!-- App scripts -->
-  <script src="../../content/shared/js/client.js"></script>
   <script src="../../content/shared/js/models.js"></script>
   <script src="../../content/shared/js/views.js"></script>
   <script src="../../content/shared/js/router.js"></script>
 
   <!-- Test scripts -->
-  <script src="client_test.js"></script>
   <script src="models_test.js"></script>
   <script src="views_test.js"></script>
   <script src="router_test.js"></script>
   <script>
     mocha.run(function () {
       $("#mocha").append("<p id='complete'>Complete.</p>");
     });
   </script>
--- a/browser/components/loop/test/shared/models_test.js
+++ b/browser/components/loop/test/shared/models_test.js
@@ -47,94 +47,100 @@ describe("loop.shared.models", function(
       it("should require a sdk option", function() {
         expect(function() {
           new sharedModels.ConversationModel();
         }).to.Throw(Error, /missing required sdk/);
       });
     });
 
     describe("constructed", function() {
-      var conversation, reqCallInfoStub, reqCallsInfoStub, fakeBaseServerUrl;
+      var conversation, fakeClient, fakeBaseServerUrl,
+          requestCallInfoStub, requestCallsInfoStub;
 
       beforeEach(function() {
         conversation = new sharedModels.ConversationModel({}, {sdk: fakeSDK});
         conversation.set("loopToken", "fakeToken");
         fakeBaseServerUrl = "http://fakeBaseServerUrl";
-        reqCallInfoStub = sandbox.stub(loop.shared.Client.prototype,
-          "requestCallInfo");
-        reqCallsInfoStub = sandbox.stub(loop.shared.Client.prototype,
-          "requestCallsInfo");
+        fakeClient = {
+          requestCallInfo: sandbox.stub(),
+          requestCallsInfo: sandbox.stub()
+        };
+        requestCallInfoStub = fakeClient.requestCallInfo;
+        requestCallsInfoStub = fakeClient.requestCallsInfo;
       });
 
       describe("#initiate", function() {
         it("call requestCallInfo on the client for outgoing calls",
           function() {
             conversation.initiate({
-              baseServerUrl: fakeBaseServerUrl,
-              outgoing: true
+              client: fakeClient,
+              outgoing: true,
+              callType: "audio"
             });
 
-            sinon.assert.calledOnce(reqCallInfoStub);
-            sinon.assert.calledWith(reqCallInfoStub, "fakeToken");
+            sinon.assert.calledOnce(requestCallInfoStub);
+            sinon.assert.calledWith(requestCallInfoStub, "fakeToken", "audio");
           });
 
         it("should not call requestCallsInfo on the client for outgoing calls",
           function() {
             conversation.initiate({
-              baseServerUrl: fakeBaseServerUrl,
-              outgoing: true
+              client: fakeClient,
+              outgoing: true,
+              callType: "audio"
             });
 
-            sinon.assert.notCalled(reqCallsInfoStub);
+            sinon.assert.notCalled(requestCallsInfoStub);
           });
 
         it("call requestCallsInfo on the client for incoming calls",
           function() {
+            conversation.set("loopVersion", 42);
             conversation.initiate({
-              baseServerUrl: fakeBaseServerUrl,
+              client: fakeClient,
               outgoing: false
             });
 
-            sinon.assert.calledOnce(reqCallsInfoStub);
-            sinon.assert.calledWith(reqCallsInfoStub);
+            sinon.assert.calledOnce(requestCallsInfoStub);
+            sinon.assert.calledWith(requestCallsInfoStub, 42);
           });
 
         it("should not call requestCallInfo on the client for incoming calls",
           function() {
             conversation.initiate({
-              baseServerUrl: fakeBaseServerUrl,
+              client: fakeClient,
               outgoing: false
             });
 
-            sinon.assert.notCalled(reqCallInfoStub);
+            sinon.assert.notCalled(requestCallInfoStub);
           });
 
         it("should update conversation session information from server data",
           function() {
             sandbox.stub(conversation, "setReady");
-            reqCallInfoStub.callsArgWith(1, null, fakeSessionData);
+            requestCallInfoStub.callsArgWith(2, null, fakeSessionData);
 
             conversation.initiate({
-              baseServerUrl: fakeBaseServerUrl,
+              client: fakeClient,
               outgoing: true
             });
 
             sinon.assert.calledOnce(conversation.setReady);
             sinon.assert.calledWith(conversation.setReady, fakeSessionData);
           });
 
         it("should trigger a `session:error` on failure", function(done) {
-          reqCallInfoStub.callsArgWith(1,
+          requestCallInfoStub.callsArgWith(2,
             new Error("failed: HTTP 400 Bad Request; fake"));
 
           conversation.on("session:error", function(err) {
             expect(err.message).to.match(/failed: HTTP 400 Bad Request; fake/);
             done();
           }).initiate({
-            baseServerUrl: fakeBaseServerUrl,
+            client: fakeClient,
             outgoing: true
           });
         });
       });
 
       describe("#setReady", function() {
         it("should update conversation session information", function() {
           conversation.setReady(fakeSessionData);
--- a/browser/components/loop/test/standalone/index.html
+++ b/browser/components/loop/test/standalone/index.html
@@ -28,18 +28,20 @@
   <script>
     chai.Assertion.includeStack = true;
     mocha.setup('bdd');
   </script>
   <!-- App scripts -->
   <script src="../../content/shared/js/models.js"></script>
   <script src="../../content/shared/js/views.js"></script>
   <script src="../../content/shared/js/router.js"></script>
+  <script src="../../standalone/content/js/standaloneClient.js"></script>
   <script src="../../standalone/content/js/webapp.js"></script>
-  <!-- Test scripts -->
+ <!-- Test scripts -->
+  <script src="standalone_client_test.js"></script>
   <script src="webapp_test.js"></script>
   <script>
     mocha.run(function () {
       $("#mocha").append("<p id='complete'>Complete.</p>");
     });
 </script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/standalone/standalone_client_test.js
@@ -0,0 +1,112 @@
+/* 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/. */
+
+/*global loop, sinon, it, beforeEach, afterEach, describe, hawk */
+
+var expect = chai.expect;
+
+describe("loop.StandaloneClient", function() {
+  "use strict";
+
+  var sandbox,
+      fakeXHR,
+      requests = [],
+      callback,
+      fakeToken;
+
+  var fakeErrorRes = JSON.stringify({
+      status: "errors",
+      errors: [{
+        location: "url",
+        name: "token",
+        description: "invalid token"
+      }]
+    });
+
+  beforeEach(function() {
+    sandbox = sinon.sandbox.create();
+    fakeXHR = sandbox.useFakeXMLHttpRequest();
+    requests = [];
+    // https://github.com/cjohansen/Sinon.JS/issues/393
+    fakeXHR.xhr.onCreate = function (xhr) {
+      requests.push(xhr);
+    };
+    callback = sinon.spy();
+    fakeToken = "fakeTokenText";
+  });
+
+  afterEach(function() {
+    sandbox.restore();
+  });
+
+  describe("loop.StandaloneClient", function() {
+    describe("#constructor", function() {
+      it("should require a baseServerUrl setting", function() {
+        expect(function() {
+          new loop.StandaloneClient();
+        }).to.Throw(Error, /required/);
+      });
+    });
+
+    describe("requestCallInfo", function() {
+      var client;
+
+      beforeEach(function() {
+        client = new loop.StandaloneClient(
+          {baseServerUrl: "http://fake.api"}
+        );
+      });
+
+      it("should prevent launching a conversation when token is missing",
+        function() {
+          expect(function() {
+            client.requestCallInfo();
+          }).to.Throw(Error, /missing.*[Tt]oken/);
+        });
+
+      it("should post data for the given call", function() {
+        client.requestCallInfo("fake", "audio", callback);
+
+        expect(requests).to.have.length.of(1);
+        expect(requests[0].url).to.be.equal("http://fake.api/calls/fake");
+        expect(requests[0].method).to.be.equal("POST");
+        expect(requests[0].requestBody).to.be.equal('{"callType":"audio"}');
+      });
+
+      it("should receive call data for the given call", function() {
+        client.requestCallInfo("fake", "audio-video", callback);
+
+        var sessionData = {
+          sessionId: "one",
+          sessionToken: "two",
+          apiKey: "three"
+        };
+
+        requests[0].respond(200, {"Content-Type": "application/json"},
+                            JSON.stringify(sessionData));
+        sinon.assert.calledWithExactly(callback, null, sessionData);
+      });
+
+      it("should send an error when the request fails", function() {
+        client.requestCallInfo("fake", "audio", callback);
+
+        requests[0].respond(400, {"Content-Type": "application/json"},
+                            fakeErrorRes);
+        sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
+          return /400.*invalid token/.test(err.message);
+        }));
+      });
+
+      it("should send an error if the data is not valid", function() {
+        client.requestCallInfo("fake", "audio", callback);
+
+        requests[0].respond(200, {"Content-Type": "application/json"},
+                            '{"bad": "one"}');
+        sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
+          return /Invalid data received/.test(err.message);
+        }));
+      });
+    });
+  });
+});
--- a/browser/components/loop/test/standalone/webapp_test.js
+++ b/browser/components/loop/test/standalone/webapp_test.js
@@ -276,20 +276,21 @@ describe("loop.webapp", function() {
 
       it("should start the conversation establishment process", function() {
         conversation.set("loopToken", "fake");
 
         view.initiate(fakeSubmitEvent);
 
         sinon.assert.calledOnce(fakeSubmitEvent.preventDefault);
         sinon.assert.calledOnce(initiate);
-        sinon.assert.calledWith(initiate, {
-          baseServerUrl: loop.webapp.baseServerUrl,
-          outgoing: true
-        });
+        sinon.assert.calledWith(initiate, sinon.match(function (value) {
+          return !!value.outgoing &&
+            (value.client instanceof loop.StandaloneClient) &&
+            value.client.settings.baseServerUrl === loop.webapp.baseServerUrl;
+        }, "{client: <properly constructed client>, outgoing: true}"));
       });
 
       it("should disable current form once session is initiated", function() {
         sandbox.stub(view, "disableForm");
         conversation.set("loopToken", "fake");
 
         view.initiate(fakeSubmitEvent);
 
--- a/browser/components/preferences/in-content/advanced.xul
+++ b/browser/components/preferences/in-content/advanced.xul
@@ -354,17 +354,17 @@
       </groupbox>
     </tabpanel>
 
     <!-- Update -->
     <tabpanel id="updatePanel" orient="vertical">
 #ifdef MOZ_UPDATER
       <groupbox id="updateApp" align="start">
         <caption><label>&updateApp.label;</label></caption>
-        <radiogroup id="updateRadioGroup"
+        <radiogroup id="updateRadioGroup" align="start"
                     oncommand="gAdvancedPane.updateWritePrefs();">
 #ifdef XP_WIN
 #ifdef MOZ_METRO
               <radio id="autoMetro"
                      value="autoMetro"
                      label="&updateAutoMetro.label;"
                      accesskey="&updateAutoMetro.accesskey;"/>
               <hbox id="autoMetroIndent"
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -146,38 +146,47 @@
           >&dontrememberActions.clearHistory.label;</html:a>&dontrememberActions.post.label;</description>
         </vbox>
         <spacer flex="1" class="indent"/>
       </hbox>
     </vbox>
     <vbox id="historyCustomPane">
       <separator class="thin"/>
       <vbox class="indent">
-        <checkbox id="privateBrowsingAutoStart"
-                  label="&privateBrowsingPermanent2.label;"
-                  accesskey="&privateBrowsingPermanent2.accesskey;"
-                  preference="browser.privatebrowsing.autostart"
-                  oncommand="gPrivacyPane.updateAutostart()"/>
-
+        <hbox>
+          <checkbox id="privateBrowsingAutoStart"
+                    label="&privateBrowsingPermanent2.label;"
+                    accesskey="&privateBrowsingPermanent2.accesskey;"
+                    preference="browser.privatebrowsing.autostart"
+                    oncommand="gPrivacyPane.updateAutostart()"/>
+          <spacer flex="1"/>
+        </hbox>
         <vbox class="indent">
-          <checkbox id="rememberHistory"
-                    label="&rememberHistory2.label;"
-                    accesskey="&rememberHistory2.accesskey;"
-                    preference="places.history.enabled"/>
-          <checkbox id="rememberForms"
-                    label="&rememberSearchForm.label;"
-                    accesskey="&rememberSearchForm.accesskey;"
-                    preference="browser.formfill.enable"/>
+          <hbox>
+            <checkbox id="rememberHistory"
+                      label="&rememberHistory2.label;"
+                      accesskey="&rememberHistory2.accesskey;"
+                      preference="places.history.enabled"/>
+            <spacer flex="1"/>
+          </hbox>
+          <hbox>
+            <checkbox id="rememberForms"
+                      label="&rememberSearchForm.label;"
+                      accesskey="&rememberSearchForm.accesskey;"
+                      preference="browser.formfill.enable"/>
+            <spacer flex="1"/>
+          </hbox>
 
           <hbox id="cookiesBox">
-            <checkbox id="acceptCookies" label="&acceptCookies.label;" flex="1"
+            <checkbox id="acceptCookies" label="&acceptCookies.label;"
                       preference="network.cookie.cookieBehavior"
                       accesskey="&acceptCookies.accesskey;"
                       onsyncfrompreference="return gPrivacyPane.readAcceptCookies();"
                       onsynctopreference="return gPrivacyPane.writeAcceptCookies();"/>
+            <spacer flex="1"/>
             <button id="cookieExceptions" oncommand="gPrivacyPane.showCookieExceptions();"
                     label="&cookieExceptions.label;" accesskey="&cookieExceptions.accesskey;"
                     preference="pref.privacy.disable_button.cookie_exceptions"/>
           </hbox>
           <hbox id="acceptThirdPartyRow"
                 class="indent"
                 align="center">
             <label id="acceptThirdPartyLabel" control="acceptThirdPartyMenu"
@@ -209,20 +218,21 @@
             <spacer flex="1"/>
             <button id="showCookiesButton"
                     label="&showCookies.label;" accesskey="&showCookies.accesskey;"
                     oncommand="gPrivacyPane.showCookies();"
                     preference="pref.privacy.disable_button.view_cookies"/>
           </hbox>
           <hbox id="clearDataBox"
                 align="center">
-            <checkbox id="alwaysClear" flex="1"
+            <checkbox id="alwaysClear"
                       preference="privacy.sanitize.sanitizeOnShutdown"
                       label="&clearOnClose.label;"
                       accesskey="&clearOnClose.accesskey;"/>
+            <spacer flex="1"/>
             <button id="clearDataSettings" label="&clearOnCloseSettings.label;"
                     accesskey="&clearOnCloseSettings.accesskey;"
                     oncommand="gPrivacyPane.showClearPrivateDataSettings();"/>
           </hbox>
         </vbox>
       </vbox>
     </vbox>
   </deck>
--- a/browser/components/preferences/in-content/sync.xul
+++ b/browser/components/preferences/in-content/sync.xul
@@ -271,17 +271,17 @@
           </vbox>
         </hbox>
       </deck>
     </groupbox>
 
     <groupbox id="syncOptions">
       <caption><label>&syncBrand.shortName.label;</label></caption>
       <hbox id="fxaSyncEngines">
-        <vbox>
+        <vbox align="start">
           <checkbox label="&engine.tabs.label;"
                     accesskey="&engine.tabs.accesskey;"
                     preference="engine.tabs"/>
           <checkbox label="&engine.bookmarks.label;"
                     accesskey="&engine.bookmarks.accesskey;"
                     preference="engine.bookmarks"/>
           <hbox>
             <checkbox id="fxa-pweng-chk"
--- a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-02.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-02.js
@@ -9,17 +9,18 @@
 function ifTestingSupported() {
   let [target, debuggee, front] = yield initCallWatcherBackend(SIMPLE_CANVAS_URL);
 
   let navigated = once(target, "navigate");
 
   yield front.setup({
     tracedGlobals: ["CanvasRenderingContext2D", "WebGLRenderingContext"],
     startRecording: true,
-    performReload: true
+    performReload: true,
+    storeCalls: true
   });
   ok(true, "The front was setup up successfully.");
 
   yield navigated;
   ok(true, "Target automatically navigated when the front was set up.");
 
   // Allow the content to execute some functions.
   yield waitForTick();
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -119,11 +119,13 @@ browser.jar:
     content/browser/devtools/app-manager/projects.xhtml                (app-manager/content/projects.xhtml)
     content/browser/devtools/app-manager/index.xul                     (app-manager/content/index.xul)
     content/browser/devtools/app-manager/index.js                      (app-manager/content/index.js)
     content/browser/devtools/app-manager/help.xhtml                    (app-manager/content/help.xhtml)
     content/browser/devtools/app-manager/manifest-editor.js            (app-manager/content/manifest-editor.js)
     content/browser/devtools/graphs-frame.xhtml                        (shared/widgets/graphs-frame.xhtml)
     content/browser/devtools/spectrum-frame.xhtml                      (shared/widgets/spectrum-frame.xhtml)
     content/browser/devtools/spectrum.css                              (shared/widgets/spectrum.css)
+    content/browser/devtools/cubic-bezier-frame.xhtml                  (shared/widgets/cubic-bezier-frame.xhtml)
+    content/browser/devtools/cubic-bezier.css                          (shared/widgets/cubic-bezier.css)
     content/browser/devtools/eyedropper.xul                            (eyedropper/eyedropper.xul)
     content/browser/devtools/eyedropper/crosshairs.css                 (eyedropper/crosshairs.css)
     content/browser/devtools/eyedropper/nocursor.css                   (eyedropper/nocursor.css)
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -1940,16 +1940,29 @@ CustomRequestView.prototype = {
 function NetworkDetailsView() {
   dumpn("NetworkDetailsView was instantiated");
 
   this._onTabSelect = this._onTabSelect.bind(this);
 };
 
 NetworkDetailsView.prototype = {
   /**
+   * An object containing the state of tabs.
+   */
+  _viewState: {
+    // if updating[tab] is true a task is currently updating the given tab.
+    updating: [],
+    // if dirty[tab] is true, the tab needs to be repopulated once current
+    // update task finishes
+    dirty: [],
+    // the most recently received attachment data for the request
+    latestData: null,
+  },
+
+  /**
    * Initialization function, called when the network monitor is started.
    */
   initialize: function() {
     dumpn("Initializing the NetworkDetailsView");
 
     this.widget = $("#event-details-pane");
 
     this._headers = new VariablesView($("#all-headers"),
@@ -2044,17 +2057,29 @@ NetworkDetailsView.prototype = {
     let tab = this.widget.selectedIndex;
     let view = this;
 
     // Make sure the data source is valid and don't populate the same tab twice.
     if (!src || populated[tab]) {
       return;
     }
 
+    let viewState = this._viewState;
+    if (viewState.updating[tab]) {
+      // A task is currently updating this tab. If we started another update
+      // task now it would result in a duplicated content as described in bugs
+      // 997065 and 984687. As there's no way to stop the current task mark the
+      // tab dirty and refresh the panel once the current task finishes.
+      viewState.dirty[tab] = true;
+      viewState.latestData = src;
+      return;
+    }
+
     Task.spawn(function*() {
+      viewState.updating[tab] = true;
       switch (tab) {
         case 0: // "Headers"
           yield view._setSummary(src);
           yield view._setResponseHeaders(src.responseHeaders);
           yield view._setRequestHeaders(
             src.requestHeaders,
             src.requestHeadersFromUploadStream);
           break;
@@ -2074,23 +2099,42 @@ NetworkDetailsView.prototype = {
           break;
         case 4: // "Timings"
           yield view._setTimingsInformation(src.eventTimings);
           break;
         case 5: // "Preview"
           yield view._setHtmlPreview(src.responseContent);
           break;
       }
-      populated[tab] = true;
-      window.emit(EVENTS.TAB_UPDATED);
-
-      if (NetMonitorController.isConnected()) {
-        NetMonitorView.RequestsMenu.ensureSelectedItemIsVisible();
+      viewState.updating[tab] = false;
+    }).then(() => {
+      if (tab == this.widget.selectedIndex) {
+        if (viewState.dirty[tab]) {
+          // The request information was updated while the task was running.
+          viewState.dirty[tab] = false;
+          view.populate(viewState.latestData);
+        }
+        else {
+          // Tab is selected but not dirty. We're done here.
+          populated[tab] = true;
+          window.emit(EVENTS.TAB_UPDATED);
+
+          if (NetMonitorController.isConnected()) {
+            NetMonitorView.RequestsMenu.ensureSelectedItemIsVisible();
+          }
+        }
       }
-    });
+      else {
+        if (viewState.dirty[tab]) {
+          // Tab is dirty but no longer selected. Don't refresh it now, it'll be
+          // done if the tab is shown again.
+          viewState.dirty[tab] = false;
+        }
+      }
+    }, Cu.reportError);
   },
 
   /**
    * Sets the network request summary shown in this view.
    *
    * @param object aData
    *        The data source (this should be the attachment of a request item).
    */
--- a/browser/devtools/netmonitor/test/browser.ini
+++ b/browser/devtools/netmonitor/test/browser.ini
@@ -47,16 +47,17 @@ support-files =
 [browser_net_complex-params.js]
 [browser_net_content-type.js]
 [browser_net_curl-utils.js]
 [browser_net_copy_image_as_data_uri.js]
 [browser_net_copy_url.js]
 [browser_net_copy_as_curl.js]
 [browser_net_cyrillic-01.js]
 [browser_net_cyrillic-02.js]
+[browser_net_details-no-duplicated-content.js]
 [browser_net_filter-01.js]
 [browser_net_filter-02.js]
 [browser_net_filter-03.js]
 [browser_net_filter-04.js]
 [browser_net_footer-summary.js]
 [browser_net_html-preview.js]
 [browser_net_icon-preview.js]
 [browser_net_image-tooltip.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/netmonitor/test/browser_net_details-no-duplicated-content.js
@@ -0,0 +1,112 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// A test to ensure that the content in details pane is not duplicated.
+
+let test = Task.async(function* () {
+  info("Initializing test");
+  let [tab, debuggee, monitor] = yield initNetMonitor(CUSTOM_GET_URL);
+  let panel = monitor.panelWin;
+  let { NetMonitorView, EVENTS } = panel;
+  let { RequestsMenu, NetworkDetails } = NetMonitorView;
+
+  let TEST_CASES = [
+    {
+      desc: "Test headers tab",
+      pageURI: CUSTOM_GET_URL,
+      isPost: false,
+      tabIndex: 0,
+      variablesView: NetworkDetails._headers,
+      expectedScopeLength: 2,
+    },
+    {
+      desc: "Test cookies tab",
+      pageURI: CUSTOM_GET_URL,
+      isPost: false,
+      tabIndex: 1,
+      variablesView: NetworkDetails._cookies,
+      expectedScopeLength: 1,
+    },
+    {
+      desc: "Test params tab",
+      pageURI: POST_RAW_URL,
+      isPost: true,
+      tabIndex: 2,
+      variablesView: NetworkDetails._params,
+      expectedScopeLength: 1,
+    },
+  ];
+
+  info("Adding a cookie for the \"Cookie\" tab test");
+  debuggee.document.cookie = "a=b; Max-Age=10; path=" +  CUSTOM_GET_URL;
+  is(debuggee.document.cookie, "a=b", "Cookie was added.")
+
+  info("Running tests");
+  for (let spec of TEST_CASES) {
+    yield runTestCase(spec);
+  }
+
+  // Remove the cookie. If an error occurs Max-Age ensures it doesn't stay to
+  // mess with the tests.
+  info("Removing the added cookie.");
+  debuggee.document.cookie = "a=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
+  is(debuggee.document.cookie, "", "Cookie was removed.");
+
+  yield teardown(monitor);
+  finish();
+
+  /**
+   * A helper that handles the execution of each case.
+   */
+  function* runTestCase(spec) {
+    info("Running case: " + spec.desc);
+    debuggee.content.location = spec.pageURI;
+
+    yield waitForNetworkEvents(monitor, 1);
+    RequestsMenu.clear();
+    yield waitForFinalDetailTabUpdate(spec.tabIndex, spec.isPost);
+
+    is(spec.variablesView._store.length, spec.expectedScopeLength,
+       "View contains " + spec.expectedScopeLength + " scope headers");
+  }
+
+  /**
+   * A helper that prepares the variables view for the actual testing. It
+   * - selects the correct tab
+   * - performs the specified request
+   * - opens the details view
+   * - waits for the final update to happen
+   */
+  function* waitForFinalDetailTabUpdate(tabIndex, isPost) {
+    let onNetworkEvent = waitFor(panel, EVENTS.NETWORK_EVENT);
+    let onDetailsPopulated = waitFor(panel, EVENTS.NETWORKDETAILSVIEW_POPULATED);
+    let onRequestFinished = isPost ?
+      waitForNetworkEvents(monitor, 0, 1) : waitForNetworkEvents(monitor, 1);
+
+    info("Performing a request");
+    debuggee.performRequests(1, null);
+
+    info("Waiting for NETWORK_EVENT");
+    yield onNetworkEvent;
+
+    ok(true, "Received NETWORK_EVENT. Selecting the item.");
+    let item = RequestsMenu.getItemAtIndex(0);
+    RequestsMenu.selectedItem = item;
+
+    info("Item selected. Waiting for NETWORKDETAILSVIEW_POPULATED");
+    yield onDetailsPopulated;
+
+    info("Selecting tab at index " + tabIndex);
+    NetworkDetails.widget.selectedIndex = tabIndex;
+
+    ok(true, "Received NETWORKDETAILSVIEW_POPULATED. Waiting for request to finish");
+    yield onRequestFinished;
+
+    ok(true, "Request finished. Waiting for tab update to complete");
+    let onDetailsUpdateFinished = waitFor(panel, EVENTS.TAB_UPDATED);
+    yield onDetailsUpdateFinished;
+    ok(true, "Details were updated");
+  }
+});
--- a/browser/devtools/netmonitor/test/browser_net_status-codes.js
+++ b/browser/devtools/netmonitor/test/browser_net_status-codes.js
@@ -1,155 +1,203 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
 
 /**
  * Tests if requests display the correct status code and text in the UI.
  */
 
-function test() {
-  initNetMonitor(STATUS_CODES_URL).then(([aTab, aDebuggee, aMonitor]) => {
-    info("Starting test... ");
+let test = Task.async(function*() {
+  let [tab, debuggee, monitor] = yield initNetMonitor(STATUS_CODES_URL);
+
+  info("Starting test... ");
 
-    let { document, L10N, NetMonitorView } = aMonitor.panelWin;
-    let { RequestsMenu, NetworkDetails } = NetMonitorView;
+  let { document, L10N, NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu, NetworkDetails } = NetMonitorView;
+  let requestItems = [];
 
-    RequestsMenu.lazyUpdate = false;
-    NetworkDetails._params.lazyEmpty = false;
-
-    waitForNetworkEvents(aMonitor, 5).then(() => {
-      let requestItems = [];
+  RequestsMenu.lazyUpdate = false;
+  NetworkDetails._params.lazyEmpty = false;
 
-      verifyRequestItemTarget(requestItems[0] = RequestsMenu.getItemAtIndex(0),
-        "GET", STATUS_CODES_SJS + "?sts=100", {
-          status: 101,
-          statusText: "Switching Protocols",
-          type: "plain",
-          fullMimeType: "text/plain; charset=utf-8",
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0),
-          time: true
-        });
-      verifyRequestItemTarget(requestItems[1] = RequestsMenu.getItemAtIndex(1),
-        "GET", STATUS_CODES_SJS + "?sts=200", {
-          status: 202,
-          statusText: "Created",
-          type: "plain",
-          fullMimeType: "text/plain; charset=utf-8",
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
-          time: true
-        });
-      verifyRequestItemTarget(requestItems[2] = RequestsMenu.getItemAtIndex(2),
-        "GET", STATUS_CODES_SJS + "?sts=300", {
-          status: 303,
-          statusText: "See Other",
-          type: "plain",
-          fullMimeType: "text/plain; charset=utf-8",
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0),
-          time: true
-        });
-      verifyRequestItemTarget(requestItems[3] = RequestsMenu.getItemAtIndex(3),
-        "GET", STATUS_CODES_SJS + "?sts=400", {
-          status: 404,
-          statusText: "Not Found",
-          type: "plain",
-          fullMimeType: "text/plain; charset=utf-8",
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
-          time: true
-        });
-      verifyRequestItemTarget(requestItems[4] = RequestsMenu.getItemAtIndex(4),
-        "GET", STATUS_CODES_SJS + "?sts=500", {
-          status: 501,
-          statusText: "Not Implemented",
-          type: "plain",
-          fullMimeType: "text/plain; charset=utf-8",
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
-          time: true
-        });
+  const REQUEST_DATA = [
+    { // request #0
+      method: "GET",
+      uri: STATUS_CODES_SJS + "?sts=100",
+      details: {
+        status: 101,
+        statusText: "Switching Protocols",
+        type: "plain",
+        fullMimeType: "text/plain; charset=utf-8",
+        size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0),
+        time: true
+      }
+    },
+    { // request #1
+      method: "GET",
+      uri: STATUS_CODES_SJS + "?sts=200",
+      details: {
+        status: 202,
+        statusText: "Created",
+        type: "plain",
+        fullMimeType: "text/plain; charset=utf-8",
+        size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
+        time: true
+      }
+    },
+    { // request #2
+      method: "GET",
+      uri: STATUS_CODES_SJS + "?sts=300",
+      details: {
+        status: 303,
+        statusText: "See Other",
+        type: "plain",
+        fullMimeType: "text/plain; charset=utf-8",
+        size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0),
+        time: true
+      }
+    },
+    { // request #3
+      method: "GET",
+      uri: STATUS_CODES_SJS + "?sts=400",
+      details: {
+        status: 404,
+        statusText: "Not Found",
+        type: "plain",
+        fullMimeType: "text/plain; charset=utf-8",
+        size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
+        time: true
+      }
+    },
+    { // request #4
+      method: "GET",
+      uri: STATUS_CODES_SJS + "?sts=500",
+      details: {
+        status: 501,
+        statusText: "Not Implemented",
+        type: "plain",
+        fullMimeType: "text/plain; charset=utf-8",
+        size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
+        time: true
+      }
+    }
+  ];
 
-      // Test summaries...
-      EventUtils.sendMouseEvent({ type: "mousedown" },
-        document.querySelectorAll("#details-pane tab")[0]);
+  debuggee.performRequests();
+  yield waitForNetworkEvents(monitor, 5);
+
+  info("Performing tests");
+  yield verifyRequests();
+  yield testTab(0, testSummary);
+  yield testTab(2, testParams);
+
+  yield teardown(monitor);
+  finish();
 
-      EventUtils.sendMouseEvent({ type: "mousedown" }, requestItems[0].target);
-      testSummary("GET", STATUS_CODES_SJS + "?sts=100", "101", "Switching Protocols");
-
-      EventUtils.sendMouseEvent({ type: "mousedown" }, requestItems[1].target);
-      testSummary("GET", STATUS_CODES_SJS + "?sts=200", "202", "Created");
-
-      EventUtils.sendMouseEvent({ type: "mousedown" }, requestItems[2].target);
-      testSummary("GET", STATUS_CODES_SJS + "?sts=300", "303", "See Other");
+  /**
+   * A helper that verifies all requests show the correct information and caches
+   * RequestsMenu items to requestItems array.
+   */
+  function* verifyRequests() {
+    info("Verifying requests contain correct information.");
+    let index = 0;
+    for (let request of REQUEST_DATA) {
+      let item = RequestsMenu.getItemAtIndex(index);
+      requestItems[index] = item;
 
-      EventUtils.sendMouseEvent({ type: "mousedown" }, requestItems[3].target);
-      testSummary("GET", STATUS_CODES_SJS + "?sts=400", "404", "Not Found");
-
-      EventUtils.sendMouseEvent({ type: "mousedown" }, requestItems[4].target);
-      testSummary("GET", STATUS_CODES_SJS + "?sts=500", "501", "Not Implemented");
+      info("Verifying request #" + index);
+      yield verifyRequestItemTarget(item, request.method, request.uri, request.details);
 
-      // Test params...
-      EventUtils.sendMouseEvent({ type: "mousedown" },
-        document.querySelectorAll("#details-pane tab")[2]);
-
-      EventUtils.sendMouseEvent({ type: "mousedown" }, requestItems[0].target);
-      testParamsTab("\"100\"");
-
-      EventUtils.sendMouseEvent({ type: "mousedown" }, requestItems[1].target);
-      testParamsTab("\"200\"");
+      index++;
+    }
+  }
 
-      EventUtils.sendMouseEvent({ type: "mousedown" }, requestItems[2].target);
-      testParamsTab("\"300\"");
-
-      EventUtils.sendMouseEvent({ type: "mousedown" }, requestItems[3].target);
-      testParamsTab("\"400\"");
+  /**
+   * A helper that opens a given tab of request details pane, selects and passes
+   * all requests to the given test function.
+   *
+   * @param Number tab
+   *               The index of NetworkDetails tab to activate.
+   * @param Function testFn(requestItem)
+   *        A function that should perform all necessary tests. It's called once
+   *        for every item of REQUEST_DATA with that item being selected in the
+   *        NetworkMonitor.
+   */
+  function* testTab(tab, testFn) {
+    info("Testing tab #" + tab);
+    EventUtils.sendMouseEvent({ type: "mousedown" },
+          document.querySelectorAll("#details-pane tab")[tab]);
 
-      EventUtils.sendMouseEvent({ type: "mousedown" }, requestItems[4].target);
-      testParamsTab("\"500\"");
+    let counter = 0;
+    for (let item of REQUEST_DATA) {
+      info("Waiting tab #" + tab + " to update with request #" + counter);
+      yield chooseRequest(counter);
+
+      info("Tab updated. Performing checks");
+      yield testFn(item);
 
-      // We're done here.
-      teardown(aMonitor).then(finish);
+      counter++;
+    }
+  }
 
-      function testSummary(aMethod, aUrl, aStatus, aStatusText) {
-        let tab = document.querySelectorAll("#details-pane tab")[0];
-        let tabpanel = document.querySelectorAll("#details-pane tabpanel")[0];
+  /**
+   * A function that tests "Summary" contains correct information.
+   */
+  function* testSummary(data) {
+    let tab = document.querySelectorAll("#details-pane tab")[0];
+    let tabpanel = document.querySelectorAll("#details-pane tabpanel")[0];
 
-        is(tabpanel.querySelector("#headers-summary-url-value").getAttribute("value"),
-          aUrl, "The url summary value is incorrect.");
-        is(tabpanel.querySelector("#headers-summary-method-value").getAttribute("value"),
-          aMethod, "The method summary value is incorrect.");
-        is(tabpanel.querySelector("#headers-summary-status-circle").getAttribute("code"),
-          aStatus, "The status summary code is incorrect.");
-        is(tabpanel.querySelector("#headers-summary-status-value").getAttribute("value"),
-          aStatus + " " + aStatusText, "The status summary value is incorrect.");
-      }
+    let { method, uri, details: { status, statusText } } = data;
+    is(tabpanel.querySelector("#headers-summary-url-value").getAttribute("value"),
+      uri, "The url summary value is incorrect.");
+    is(tabpanel.querySelector("#headers-summary-method-value").getAttribute("value"),
+      method, "The method summary value is incorrect.");
+    is(tabpanel.querySelector("#headers-summary-status-circle").getAttribute("code"),
+      status, "The status summary code is incorrect.");
+    is(tabpanel.querySelector("#headers-summary-status-value").getAttribute("value"),
+      status + " " + statusText, "The status summary value is incorrect.");
+  }
 
-      function testParamsTab(aStatusParamValue) {
-        let tab = document.querySelectorAll("#details-pane tab")[2];
-        let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
+  /**
+   * A function that tests "Params" tab contains correct information.
+   */
+  function* testParams(data) {
+    let tab = document.querySelectorAll("#details-pane tab")[2];
+    let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
+    let statusParamValue = data.uri.split("=").pop();
+    let statusParamShownValue = "\"" + statusParamValue + "\"";
 
-        is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
-          "There should be 1 param scope displayed in this tabpanel.");
-        is(tabpanel.querySelectorAll(".variable-or-property").length, 1,
-          "There should be 1 param value displayed in this tabpanel.");
-        is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
-          "The empty notice should not be displayed in this tabpanel.");
+    is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
+      "There should be 1 param scope displayed in this tabpanel.");
+    is(tabpanel.querySelectorAll(".variable-or-property").length, 1,
+      "There should be 1 param value displayed in this tabpanel.");
+    is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+      "The empty notice should not be displayed in this tabpanel.");
 
-        let paramsScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+    let paramsScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
 
-        is(paramsScope.querySelector(".name").getAttribute("value"),
-          L10N.getStr("paramsQueryString"),
-          "The params scope doesn't have the correct title.");
+    is(paramsScope.querySelector(".name").getAttribute("value"),
+      L10N.getStr("paramsQueryString"),
+      "The params scope doesn't have the correct title.");
 
-        is(paramsScope.querySelectorAll(".variables-view-variable .name")[0].getAttribute("value"),
-          "sts", "The param name was incorrect.");
-        is(paramsScope.querySelectorAll(".variables-view-variable .value")[0].getAttribute("value"),
-          aStatusParamValue, "The param value was incorrect.");
+    is(paramsScope.querySelectorAll(".variables-view-variable .name")[0].getAttribute("value"),
+      "sts", "The param name was incorrect.");
+    is(paramsScope.querySelectorAll(".variables-view-variable .value")[0].getAttribute("value"),
+      statusParamShownValue, "The param value was incorrect.");
 
-        is(tabpanel.querySelector("#request-params-box")
-          .hasAttribute("hidden"), false,
-          "The request params box should not be hidden.");
-        is(tabpanel.querySelector("#request-post-data-textarea-box")
-          .hasAttribute("hidden"), true,
-          "The request post data textarea box should be hidden.");
-      }
-    });
+    is(tabpanel.querySelector("#request-params-box")
+      .hasAttribute("hidden"), false,
+      "The request params box should not be hidden.");
+    is(tabpanel.querySelector("#request-post-data-textarea-box")
+      .hasAttribute("hidden"), true,
+      "The request post data textarea box should be hidden.");
+  }
 
-    aDebuggee.performRequests();
-  });
-}
+  /**
+   * A helper that clicks on a specified request and returns a promise resolved
+   * when NetworkDetails has been populated with the data of the given request.
+   */
+  function chooseRequest(index) {
+    EventUtils.sendMouseEvent({ type: "mousedown" }, requestItems[index].target);
+    return waitFor(monitor.panelWin, monitor.panelWin.EVENTS.TAB_UPDATED);
+  }
+});
--- a/browser/devtools/shared/test/browser.ini
+++ b/browser/devtools/shared/test/browser.ini
@@ -7,16 +7,19 @@ support-files =
   browser_layoutHelpers_iframe.html
   browser_templater_basic.html
   browser_toolbar_basic.html
   browser_toolbar_webconsole_errors_count.html
   head.js
   leakhunt.js
 
 [browser_css_color.js]
+[browser_cubic-bezier-01.js]
+[browser_cubic-bezier-02.js]
+[browser_cubic-bezier-03.js]
 [browser_graphs-01.js]
 [browser_graphs-02.js]
 [browser_graphs-03.js]
 [browser_graphs-04.js]
 [browser_graphs-05.js]
 [browser_graphs-06.js]
 [browser_graphs-07.js]
 [browser_graphs-08.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_cubic-bezier-01.js
@@ -0,0 +1,36 @@
+/* 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";
+
+// Tests that the CubicBezierWidget generates content in a given parent node
+
+const TEST_URI = "chrome://browser/content/devtools/cubic-bezier-frame.xhtml";
+const {CubicBezierWidget} = devtools.require("devtools/shared/widgets/CubicBezierWidget");
+
+let test = Task.async(function*() {
+  yield promiseTab(TEST_URI);
+
+  info("Checking that the markup is created in the parent");
+  let container = content.document.querySelector("#container");
+  let w = new CubicBezierWidget(container);
+
+  ok(container.querySelector(".coordinate-plane"),
+    "The coordinate plane has been added");
+  let buttons = container.querySelectorAll("button");
+  is(buttons.length, 2,
+    "The 2 control points have been added");
+  is(buttons[0].className, "control-point");
+  is(buttons[0].id, "P1");
+  is(buttons[1].className, "control-point");
+  is(buttons[1].id, "P2");
+  ok(container.querySelector("canvas"), "The curve canvas has been added");
+
+  info("Destroying the widget");
+  w.destroy();
+  is(container.children.length, 0, "All nodes have been removed");
+
+  gBrowser.removeCurrentTab();
+  finish();
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_cubic-bezier-02.js
@@ -0,0 +1,152 @@
+/* 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";
+
+// Tests the CubicBezierWidget events
+
+const TEST_URI = "chrome://browser/content/devtools/cubic-bezier-frame.xhtml";
+const {CubicBezierWidget, PREDEFINED} =
+  devtools.require("devtools/shared/widgets/CubicBezierWidget");
+
+let test = Task.async(function*() {
+  yield promiseTab(TEST_URI);
+
+  let container = content.document.querySelector("#container");
+  let w = new CubicBezierWidget(container, PREDEFINED.linear);
+
+  yield pointsCanBeDragged(w);
+  yield curveCanBeClicked(w);
+  yield pointsCanBeMovedWithKeyboard(w);
+
+  w.destroy();
+  gBrowser.removeCurrentTab();
+  finish();
+});
+
+function* pointsCanBeDragged(widget) {
+  info("Checking that the control points can be dragged with the mouse");
+
+  info("Listening for the update event");
+  let onUpdated = widget.once("updated");
+
+  info("Generating a mousedown/move/up on P1");
+  widget._onPointMouseDown({target: widget.p1});
+  EventUtils.synthesizeMouse(content.document.documentElement, 0, 100,
+    {type: "mousemove"}, content.window);
+  EventUtils.synthesizeMouse(content.document.documentElement, 0, 100,
+    {type: "mouseup"}, content.window);
+
+  let bezier = yield onUpdated;
+  ok(true, "The widget fired the updated event");
+  ok(bezier, "The updated event contains a bezier argument");
+  is(bezier.P1[0], 0, "The new P1 time coordinate is correct");
+  is(bezier.P1[1], 1, "The new P1 progress coordinate is correct");
+
+  info("Listening for the update event");
+  let onUpdated = widget.once("updated");
+
+  info("Generating a mousedown/move/up on P2");
+  widget._onPointMouseDown({target: widget.p2});
+  EventUtils.synthesizeMouse(content.document.documentElement, 200, 300,
+    {type: "mousemove"}, content.window);
+  EventUtils.synthesizeMouse(content.document.documentElement, 200, 300,
+    {type: "mouseup"}, content.window);
+
+  let bezier = yield onUpdated;
+  is(bezier.P2[0], 1, "The new P2 time coordinate is correct");
+  is(bezier.P2[1], 0, "The new P2 progress coordinate is correct");
+}
+
+function* curveCanBeClicked(widget) {
+  info("Checking that clicking on the curve moves the closest control point");
+
+  info("Listening for the update event");
+  let onUpdated = widget.once("updated");
+
+  info("Click close to P1");
+  widget._onCurveClick({pageX: 50, pageY: 150});
+
+  let bezier = yield onUpdated;
+  ok(true, "The widget fired the updated event");
+  is(bezier.P1[0], 0.25, "The new P1 time coordinate is correct");
+  is(bezier.P1[1], 0.75, "The new P1 progress coordinate is correct");
+  is(bezier.P2[0], 1, "P2 time coordinate remained unchanged");
+  is(bezier.P2[1], 0, "P2 progress coordinate remained unchanged");
+
+  info("Listening for the update event");
+  let onUpdated = widget.once("updated");
+
+  info("Click close to P2");
+  widget._onCurveClick({pageX: 150, pageY: 250});
+
+  let bezier = yield onUpdated;
+  is(bezier.P2[0], 0.75, "The new P2 time coordinate is correct");
+  is(bezier.P2[1], 0.25, "The new P2 progress coordinate is correct");
+  is(bezier.P1[0], 0.25, "P1 time coordinate remained unchanged");
+  is(bezier.P1[1], 0.75, "P1 progress coordinate remained unchanged");
+}
+
+function* pointsCanBeMovedWithKeyboard(widget) {
+  info("Checking that points respond to keyboard events");
+
+  info("Moving P1 to the left");
+  let onUpdated = widget.once("updated");
+  widget._onPointKeyDown(getKeyEvent(widget.p1, 37));
+  let bezier = yield onUpdated;
+  is(bezier.P1[0], 0.235, "The new P1 time coordinate is correct");
+  is(bezier.P1[1], 0.75, "The new P1 progress coordinate is correct");
+
+  info("Moving P1 to the left, fast");
+  let onUpdated = widget.once("updated");
+  widget._onPointKeyDown(getKeyEvent(widget.p1, 37, true));
+  let bezier = yield onUpdated;
+  is(bezier.P1[0], 0.085, "The new P1 time coordinate is correct");
+  is(bezier.P1[1], 0.75, "The new P1 progress coordinate is correct");
+
+  info("Moving P1 to the right, fast");
+  let onUpdated = widget.once("updated");
+  widget._onPointKeyDown(getKeyEvent(widget.p1, 39, true));
+  let bezier = yield onUpdated;
+  is(bezier.P1[0], 0.235, "The new P1 time coordinate is correct");
+  is(bezier.P1[1], 0.75, "The new P1 progress coordinate is correct");
+
+  info("Moving P1 to the bottom");
+  let onUpdated = widget.once("updated");
+  widget._onPointKeyDown(getKeyEvent(widget.p1, 40));
+  let bezier = yield onUpdated;
+  is(bezier.P1[0], 0.235, "The new P1 time coordinate is correct");
+  is(bezier.P1[1], 0.735, "The new P1 progress coordinate is correct");
+
+  info("Moving P1 to the bottom, fast");
+  let onUpdated = widget.once("updated");
+  widget._onPointKeyDown(getKeyEvent(widget.p1, 40, true));
+  let bezier = yield onUpdated;
+  is(bezier.P1[0], 0.235, "The new P1 time coordinate is correct");
+  is(bezier.P1[1], 0.585, "The new P1 progress coordinate is correct");
+
+  info("Moving P1 to the top, fast");
+  let onUpdated = widget.once("updated");
+  widget._onPointKeyDown(getKeyEvent(widget.p1, 38, true));
+  let bezier = yield onUpdated;
+  is(bezier.P1[0], 0.235, "The new P1 time coordinate is correct");
+  is(bezier.P1[1], 0.735, "The new P1 progress coordinate is correct");
+
+  info("Checking that keyboard events also work with P2");
+  info("Moving P2 to the left");
+  let onUpdated = widget.once("updated");
+  widget._onPointKeyDown(getKeyEvent(widget.p2, 37));
+  let bezier = yield onUpdated;
+  is(bezier.P2[0], 0.735, "The new P2 time coordinate is correct");
+  is(bezier.P2[1], 0.25, "The new P2 progress coordinate is correct");
+}
+
+function getKeyEvent(target, keyCode, shift=false) {
+  return {
+    target: target,
+    keyCode: keyCode,
+    shiftKey: shift,
+    preventDefault: () => {}
+  };
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_cubic-bezier-03.js
@@ -0,0 +1,67 @@
+/* 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";
+
+// Tests that coordinates can be changed programatically in the CubicBezierWidget
+
+const TEST_URI = "chrome://browser/content/devtools/cubic-bezier-frame.xhtml";
+const {CubicBezierWidget, PREDEFINED} =
+  devtools.require("devtools/shared/widgets/CubicBezierWidget");
+
+let test = Task.async(function*() {
+  yield promiseTab(TEST_URI);
+
+  let container = content.document.querySelector("#container");
+  let w = new CubicBezierWidget(container, PREDEFINED.linear);
+
+  yield coordinatesCanBeChangedByProvidingAnArray(w);
+  yield coordinatesCanBeChangedByProvidingAValue(w);
+
+  w.destroy();
+  gBrowser.removeCurrentTab();
+  finish();
+});
+
+function* coordinatesCanBeChangedByProvidingAnArray(widget) {
+  info("Listening for the update event");
+  let onUpdated = widget.once("updated");
+
+  info("Setting new coordinates");
+  widget.coordinates = [0,1,1,0];
+
+  let bezier = yield onUpdated;
+  ok(true, "The updated event was fired as a result of setting coordinates");
+
+  is(bezier.P1[0], 0, "The new P1 time coordinate is correct");
+  is(bezier.P1[1], 1, "The new P1 progress coordinate is correct");
+  is(bezier.P2[0], 1, "The new P2 time coordinate is correct");
+  is(bezier.P2[1], 0, "The new P2 progress coordinate is correct");
+}
+
+function* coordinatesCanBeChangedByProvidingAValue(widget) {
+  info("Listening for the update event");
+  let onUpdated = widget.once("updated");
+
+  info("Setting linear css value");
+  widget.cssCubicBezierValue = "linear";
+  let bezier = yield onUpdated;
+  ok(true, "The updated event was fired as a result of setting cssValue");
+
+  is(bezier.P1[0], 0, "The new P1 time coordinate is correct");
+  is(bezier.P1[1], 0, "The new P1 progress coordinate is correct");
+  is(bezier.P2[0], 1, "The new P2 time coordinate is correct");
+  is(bezier.P2[1], 1, "The new P2 progress coordinate is correct");
+
+  info("Setting a custom cubic-bezier css value");
+  let onUpdated = widget.once("updated");
+  widget.cssCubicBezierValue = "cubic-bezier(.25,-0.5, 1, 1.45)";
+  let bezier = yield onUpdated;
+  ok(true, "The updated event was fired as a result of setting cssValue");
+
+  is(bezier.P1[0], .25, "The new P1 time coordinate is correct");
+  is(bezier.P1[1], -.5, "The new P1 progress coordinate is correct");
+  is(bezier.P2[0], 1, "The new P2 time coordinate is correct");
+  is(bezier.P2[1], 1.45, "The new P2 progress coordinate is correct");
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/unit/test_bezierCanvas.js
@@ -0,0 +1,113 @@
+/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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";
+
+// Tests the BezierCanvas API in the CubicBezierWidget module
+
+const Cu = Components.utils;
+let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+let require = devtools.require;
+let {CubicBezier, BezierCanvas} = require("devtools/shared/widgets/CubicBezierWidget");
+
+function run_test() {
+  offsetsGetterReturnsData();
+  convertsOffsetsToCoordinates();
+  plotsCanvas();
+}
+
+function offsetsGetterReturnsData() {
+  do_print("offsets getter returns an array of 2 offset objects");
+
+  let b = new BezierCanvas(getCanvasMock(), getCubicBezier(), [.25, 0]);
+  let offsets = b.offsets;
+
+  do_check_eq(offsets.length, 2);
+
+  do_check_true("top" in offsets[0]);
+  do_check_true("left" in offsets[0]);
+  do_check_true("top" in offsets[1]);
+  do_check_true("left" in offsets[1]);
+
+  do_check_eq(offsets[0].top, "300px");
+  do_check_eq(offsets[0].left, "0px");
+  do_check_eq(offsets[1].top, "100px");
+  do_check_eq(offsets[1].left, "200px");
+
+  do_print("offsets getter returns data according to current padding");
+
+  let b = new BezierCanvas(getCanvasMock(), getCubicBezier(), [0, 0]);
+  let offsets = b.offsets;
+
+  do_check_eq(offsets[0].top, "400px");
+  do_check_eq(offsets[0].left, "0px");
+  do_check_eq(offsets[1].top, "0px");
+  do_check_eq(offsets[1].left, "200px");
+}
+
+function convertsOffsetsToCoordinates() {
+  do_print("Converts offsets to coordinates");
+
+  let b = new BezierCanvas(getCanvasMock(), getCubicBezier(), [.25, 0]);
+
+  let coordinates = b.offsetsToCoordinates({style: {
+    left: "0px",
+    top: "0px"
+  }});
+  do_check_eq(coordinates.length, 2);
+  do_check_eq(coordinates[0], 0);
+  do_check_eq(coordinates[1], 1.5);
+
+  let coordinates = b.offsetsToCoordinates({style: {
+    left: "0px",
+    top: "300px"
+  }});
+  do_check_eq(coordinates[0], 0);
+  do_check_eq(coordinates[1], 0);
+
+  let coordinates = b.offsetsToCoordinates({style: {
+    left: "200px",
+    top: "100px"
+  }});
+  do_check_eq(coordinates[0], 1);
+  do_check_eq(coordinates[1], 1);
+}
+
+function plotsCanvas() {
+  do_print("Plots the curve to the canvas");
+
+  let hasDrawnCurve = false;
+  let b = new BezierCanvas(getCanvasMock(), getCubicBezier(), [.25, 0]);
+  b.ctx.bezierCurveTo = () => hasDrawnCurve = true;
+  b.plot();
+
+  do_check_true(hasDrawnCurve);
+}
+
+function getCubicBezier() {
+  return new CubicBezier([0,0,1,1]);
+}
+
+function getCanvasMock(w=200, h=400) {
+  return {
+    getContext: function() {
+      return {
+        scale: () => {},
+        translate: () => {},
+        clearRect: () => {},
+        beginPath: () => {},
+        closePath: () => {},
+        moveTo: () => {},
+        lineTo: () => {},
+        stroke: () => {},
+        arc: () => {},
+        fill: () => {},
+        bezierCurveTo: () => {}
+      };
+    },
+    width: w,
+    height: h
+  };
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/unit/test_cubicBezier.js
@@ -0,0 +1,102 @@
+/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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";
+
+// Tests the CubicBezier API in the CubicBezierWidget module
+
+const Cu = Components.utils;
+let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+let require = devtools.require;
+let {CubicBezier} = require("devtools/shared/widgets/CubicBezierWidget");
+
+function run_test() {
+  throwsWhenMissingCoordinates();
+  throwsWhenIncorrectCoordinates();
+  convertsStringCoordinates();
+  coordinatesToStringOutputsAString();
+  pointGettersReturnPointCoordinatesArrays();
+  toStringOutputsCubicBezierValue();
+}
+
+function throwsWhenMissingCoordinates() {
+  do_check_throws(() => {
+    new CubicBezier();
+  }, "Throws an exception when coordinates are missing");
+}
+
+function throwsWhenIncorrectCoordinates() {
+  do_check_throws(() => {
+    new CubicBezier([]);
+  }, "Throws an exception when coordinates are incorrect (empty array)");
+
+  do_check_throws(() => {
+    new CubicBezier([0,0]);
+  }, "Throws an exception when coordinates are incorrect (incomplete array)");
+
+  do_check_throws(() => {
+    new CubicBezier(["a", "b", "c", "d"]);
+  }, "Throws an exception when coordinates are incorrect (invalid type)");
+
+  do_check_throws(() => {
+    new CubicBezier([1.5, 0, 1.5, 0]);
+  }, "Throws an exception when coordinates are incorrect (time range invalid)");
+
+  do_check_throws(() => {
+    new CubicBezier([-0.5, 0, -0.5, 0]);
+  }, "Throws an exception when coordinates are incorrect (time range invalid)");
+}
+
+function convertsStringCoordinates() {
+  do_print("Converts string coordinates to numbers");
+  let c = new CubicBezier(["0", "1", ".5", "-2"]);
+
+  do_check_eq(c.coordinates[0], 0);
+  do_check_eq(c.coordinates[1], 1);
+  do_check_eq(c.coordinates[2], .5);
+  do_check_eq(c.coordinates[3], -2);
+}
+
+function coordinatesToStringOutputsAString() {
+  do_print("coordinates.toString() outputs a string representation");
+
+  let c = new CubicBezier(["0", "1", "0.5", "-2"]);
+  let string = c.coordinates.toString();
+  do_check_eq(string, "0,1,.5,-2");
+
+  let c = new CubicBezier([1, 1, 1, 1]);
+  let string = c.coordinates.toString();
+  do_check_eq(string, "1,1,1,1");
+}
+
+function pointGettersReturnPointCoordinatesArrays() {
+  do_print("Points getters return arrays of coordinates");
+
+  let c = new CubicBezier([0, .2, .5, 1]);
+  do_check_eq(c.P1[0], 0);
+  do_check_eq(c.P1[1], .2);
+  do_check_eq(c.P2[0], .5);
+  do_check_eq(c.P2[1], 1);
+}
+
+function toStringOutputsCubicBezierValue() {
+  do_print("toString() outputs the cubic-bezier() value");
+
+  let c = new CubicBezier([0, 0, 1, 1]);
+  do_check_eq(c.toString(), "cubic-bezier(0,0,1,1)");
+}
+
+function do_check_throws(cb, info) {
+  do_print(info);
+
+  let hasThrown = false;
+  try {
+    cb();
+  } catch (e) {
+    hasThrown = true;
+  }
+
+  do_check_true(hasThrown);
+}
--- a/browser/devtools/shared/test/unit/xpcshell.ini
+++ b/browser/devtools/shared/test/unit/xpcshell.ini
@@ -1,6 +1,8 @@
 [DEFAULT]
 head =
 tail =
 firefox-appdir = browser
 
+[test_bezierCanvas.js]
+[test_cubicBezier.js]
 [test_undoStack.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/widgets/CubicBezierWidget.js
@@ -0,0 +1,556 @@
+/**
+ * Copyright (c) 2013 Lea Verou. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+// Based on www.cubic-bezier.com by Lea Verou
+// See https://github.com/LeaVerou/cubic-bezier
+
+"use strict";
+
+const EventEmitter = require("devtools/toolkit/event-emitter");
+const {setTimeout, clearTimeout} = require("sdk/timers");
+
+const PREDEFINED = exports.PREDEFINED = {
+  "ease": [.25, .1, .25, 1],
+  "linear": [0, 0, 1, 1],
+  "ease-in": [.42, 0, 1, 1],
+  "ease-out": [0, 0, .58, 1],
+  "ease-in-out": [.42, 0, .58, 1]
+};
+
+/**
+ * CubicBezier data structure helper
+ * Accepts an array of coordinates and exposes a few useful getters
+ * @param {Array} coordinates i.e. [.42, 0, .58, 1]
+ */
+function CubicBezier(coordinates) {
+  if (!coordinates) {
+    throw "No offsets were defined";
+  }
+
+  this.coordinates = coordinates.map(n => +n);
+
+  for (let i = 4; i--;) {
+    let xy = this.coordinates[i];
+    if (isNaN(xy) || (!(i%2) && (xy < 0 || xy > 1))) {
+      throw "Wrong coordinate at " + i + "(" + xy + ")";
+    }
+  }
+
+  this.coordinates.toString = function() {
+    return this.map(n => {
+      return (Math.round(n * 100)/100 + '').replace(/^0\./, '.');
+    }) + "";
+  }
+}
+
+exports.CubicBezier = CubicBezier;
+
+CubicBezier.prototype = {
+  get P1() {
+    return this.coordinates.slice(0, 2);
+  },
+
+  get P2() {
+    return this.coordinates.slice(2);
+  },
+
+  toString: function() {
+    return 'cubic-bezier(' + this.coordinates + ')';
+  }
+};
+
+/**
+ * Bezier curve canvas plotting class
+ * @param {DOMNode} canvas
+ * @param {CubicBezier} bezier
+ * @param {Array} padding Amount of horizontal,vertical padding around the graph
+ */
+function BezierCanvas(canvas, bezier, padding) {
+  this.canvas = canvas;
+  this.bezier = bezier;
+  this.padding = getPadding(padding);
+
+  // Convert to a cartesian coordinate system with axes from 0 to 1
+  this.ctx = this.canvas.getContext('2d');
+  let p = this.padding;
+
+  this.ctx.scale(canvas.width * (1 - p[1] - p[3]),
+                 -canvas.height * (1 - p[0] - p[2]));
+  this.ctx.translate(p[3] / (1 - p[1] - p[3]),
+                     -1 - p[0] / (1 - p[0] - p[2]));
+};
+
+exports.BezierCanvas = BezierCanvas;
+
+BezierCanvas.prototype = {
+  /**
+   * Get P1 and P2 current top/left offsets so they can be positioned
+   * @return {Array} Returns an array of 2 {top:String,left:String} objects
+   */
+  get offsets() {
+    let p = this.padding, w = this.canvas.width, h = this.canvas.height;
+
+    return [{
+      left: w * (this.bezier.coordinates[0] * (1 - p[3] - p[1]) - p[3]) + 'px',
+      top: h * (1 - this.bezier.coordinates[1] * (1 - p[0] - p[2]) - p[0]) + 'px'
+    }, {
+      left: w * (this.bezier.coordinates[2] * (1 - p[3] - p[1]) - p[3]) + 'px',
+      top: h * (1 - this.bezier.coordinates[3] * (1 - p[0] - p[2]) - p[0]) + 'px'
+    }]
+  },
+
+  /**
+   * Convert an element's left/top offsets into coordinates
+   */
+  offsetsToCoordinates: function(element) {
+    let p = this.padding, w = this.canvas.width, h = this.canvas.height;
+
+    // Convert padding percentage to actual padding
+    p = p.map(function(a, i) { return a * (i % 2? w : h)});
+
+    return [
+      (parseInt(element.style.left) - p[3]) / (w + p[1] + p[3]),
+      (h - parseInt(element.style.top) - p[2]) / (h - p[0] - p[2])
+    ];
+  },
+
+  /**
+   * Draw the cubic bezier curve for the current coordinates
+   */
+  plot: function(settings={}) {
+    let xy = this.bezier.coordinates;
+
+    let defaultSettings = {
+      handleColor: '#666',
+      handleThickness: .008,
+      bezierColor: '#4C9ED9',
+      bezierThickness: .015
+    };
+
+    for (let setting in settings) {
+      defaultSettings[setting] = settings[setting];
+    }
+
+    this.ctx.clearRect(-.5,-.5, 2, 2);
+
+    // Draw control handles
+    this.ctx.beginPath();
+    this.ctx.fillStyle = defaultSettings.handleColor;
+    this.ctx.lineWidth = defaultSettings.handleThickness;
+    this.ctx.strokeStyle = defaultSettings.handleColor;
+
+    this.ctx.moveTo(0, 0);
+    this.ctx.lineTo(xy[0], xy[1]);
+    this.ctx.moveTo(1,1);
+    this.ctx.lineTo(xy[2], xy[3]);
+
+    this.ctx.stroke();
+    this.ctx.closePath();
+
+    function circle(ctx, cx, cy, r) {
+      return ctx.beginPath();
+      ctx.arc(cx, cy, r, 0, 2*Math.PI, !1);
+      ctx.closePath();
+    }
+
+    circle(this.ctx, xy[0], xy[1], 1.5 * defaultSettings.handleThickness);
+    this.ctx.fill();
+    circle(this.ctx, xy[2], xy[3], 1.5 * defaultSettings.handleThickness);
+    this.ctx.fill();
+
+    // Draw bezier curve
+    this.ctx.beginPath();
+    this.ctx.lineWidth = defaultSettings.bezierThickness;
+    this.ctx.strokeStyle = defaultSettings.bezierColor;
+    this.ctx.moveTo(0,0);
+    this.ctx.bezierCurveTo(xy[0], xy[1], xy[2], xy[3], 1,1);
+    this.ctx.stroke();
+    this.ctx.closePath();
+  }
+};
+
+/**
+ * Cubic-bezier widget. Uses the BezierCanvas class to draw the curve and
+ * adds the control points and user interaction
+ * @param {DOMNode} parent The container where the graph should be created
+ * @param {Array} coordinates Coordinates of the curve to be drawn
+ *
+ * Emits "updated" events whenever the curve is changed. Along with the event is
+ * sent a CubicBezier object
+ */
+function CubicBezierWidget(parent, coordinates=PREDEFINED["ease-in-out"]) {
+  this.parent = parent;
+  let {curve, p1, p2} = this._initMarkup();
+
+  this.curve = curve;
+  this.curveBoundingBox = curve.getBoundingClientRect();
+  this.p1 = p1;
+  this.p2 = p2;
+
+  // Create and plot the bezier curve
+  this.bezierCanvas = new BezierCanvas(this.curve,
+    new CubicBezier(coordinates), [.25, 0]);
+  this.bezierCanvas.plot();
+
+  // Place the control points
+  let offsets = this.bezierCanvas.offsets;
+  this.p1.style.left = offsets[0].left;
+  this.p1.style.top = offsets[0].top;
+  this.p2.style.left = offsets[1].left;
+  this.p2.style.top = offsets[1].top;
+
+  this._onPointMouseDown = this._onPointMouseDown.bind(this);
+  this._onPointKeyDown = this._onPointKeyDown.bind(this);
+  this._onCurveClick = this._onCurveClick.bind(this);
+  this._initEvents();
+
+  // Add the timing function previewer
+  this.timingPreview = new TimingFunctionPreviewWidget(parent);
+
+  EventEmitter.decorate(this);
+}
+
+exports.CubicBezierWidget = CubicBezierWidget;
+
+CubicBezierWidget.prototype = {
+  _initMarkup: function() {
+    let doc = this.parent.ownerDocument;
+
+    let plane = doc.createElement("div");
+    plane.className = "coordinate-plane";
+
+    let p1 = doc.createElement("button");
+    p1.className = "control-point";
+    p1.id = "P1";
+    plane.appendChild(p1);
+
+    let p2 = doc.createElement("button");
+    p2.className = "control-point";
+    p2.id = "P2";
+    plane.appendChild(p2);
+
+    let curve = doc.createElement("canvas");
+    curve.setAttribute("height", "400");
+    curve.setAttribute("width", "200");
+    curve.id = "curve";
+    plane.appendChild(curve);
+
+    this.parent.appendChild(plane);
+
+    return {
+      p1: p1,
+      p2: p2,
+      curve: curve
+    }
+  },
+
+  _removeMarkup: function() {
+    this.parent.ownerDocument.querySelector(".coordinate-plane").remove();
+  },
+
+  _initEvents: function() {
+    this.p1.addEventListener("mousedown", this._onPointMouseDown);
+    this.p2.addEventListener("mousedown", this._onPointMouseDown);
+
+    this.p1.addEventListener("keydown", this._onPointKeyDown);
+    this.p2.addEventListener("keydown", this._onPointKeyDown);
+
+    this.curve.addEventListener("click", this._onCurveClick);
+  },
+
+  _removeEvents: function() {
+    this.p1.removeEventListener("mousedown", this._onPointMouseDown);
+    this.p2.removeEventListener("mousedown", this._onPointMouseDown);
+
+    this.p1.removeEventListener("keydown", this._onPointKeyDown);
+    this.p2.removeEventListener("keydown", this._onPointKeyDown);
+
+    this.curve.removeEventListener("click", this._onCurveClick);
+  },
+
+  _onPointMouseDown: function(event) {
+    // Updating the boundingbox in case it has changed
+    this.curveBoundingBox = this.curve.getBoundingClientRect();
+
+    let point = event.target;
+    let doc = point.ownerDocument;
+    let self = this;
+
+    doc.onmousemove = function drag(e) {
+      let x = e.pageX;
+      let y = e.pageY;
+      let left = self.curveBoundingBox.left;
+      let top = self.curveBoundingBox.top;
+
+      if (x === 0 && y == 0) {
+        return;
+      }
+
+      // Constrain x
+      x = Math.min(Math.max(left, x), left + self.curveBoundingBox.width);
+
+      point.style.left = x - left + "px";
+      point.style.top = y - top + "px";
+
+      self._updateFromPoints();
+    };
+
+    doc.onmouseup = function () {
+      point.focus();
+      doc.onmousemove = doc.onmouseup = null;
+    }
+  },
+
+  _onPointKeyDown: function(event) {
+    let point = event.target;
+    let code = event.keyCode;
+
+    if (code >= 37 && code <= 40) {
+      event.preventDefault();
+
+      // Arrow keys pressed
+      let left = parseInt(point.style.left);
+      let top = parseInt(point.style.top);
+      let offset = 3 * (event.shiftKey ? 10 : 1);
+
+      switch (code) {
+        case 37: point.style.left = left - offset + 'px'; break;
+        case 38: point.style.top = top - offset + 'px'; break;
+        case 39: point.style.left = left + offset + 'px'; break;
+        case 40: point.style.top = top + offset + 'px'; break;
+      }
+
+      this._updateFromPoints();
+    }
+  },
+
+  _onCurveClick: function(event) {
+    let left = this.curveBoundingBox.left;
+    let top = this.curveBoundingBox.top;
+    let x = event.pageX - left;
+    let y = event.pageY - top;
+
+    // Find which point is closer
+    let distP1 = distance(x, y,
+      parseInt(this.p1.style.left), parseInt(this.p1.style.top));
+    let distP2 = distance(x, y,
+      parseInt(this.p2.style.left), parseInt(this.p2.style.top));
+
+    let point = distP1 < distP2 ? this.p1 : this.p2;
+    point.style.left = x + "px";
+    point.style.top = y + "px";
+
+    this._updateFromPoints();
+  },
+
+  /**
+   * Get the current point coordinates and redraw the curve to match
+   */
+  _updateFromPoints: function() {
+    // Get the new coordinates from the point's offsets
+    let coordinates = this.bezierCanvas.offsetsToCoordinates(this.p1)
+    coordinates = coordinates.concat(this.bezierCanvas.offsetsToCoordinates(this.p2));
+
+    this._redraw(coordinates);
+  },
+
+  /**
+   * Redraw the curve
+   * @param {Array} coordinates The array of control point coordinates
+   */
+  _redraw: function(coordinates) {
+    // Provide a new CubicBezier to the canvas and plot the curve
+    this.bezierCanvas.bezier = new CubicBezier(coordinates);
+    this.bezierCanvas.plot();
+    this.emit("updated", this.bezierCanvas.bezier);
+
+    this.timingPreview.preview(this.bezierCanvas.bezier + "");
+  },
+
+  /**
+   * Set new coordinates for the control points and redraw the curve
+   * @param {Array} coordinates
+   */
+  set coordinates(coordinates) {
+    this._redraw(coordinates)
+
+    // Move the points
+    let offsets = this.bezierCanvas.offsets;
+    this.p1.style.left = offsets[0].left;
+    this.p1.style.top = offsets[0].top;
+    this.p2.style.left = offsets[1].left;
+    this.p2.style.top = offsets[1].top;
+  },
+
+  /**
+   * Set new coordinates for the control point and redraw the curve
+   * @param {String} value A string value. E.g. "linear", "cubic-bezier(0,0,1,1)"
+   */
+  set cssCubicBezierValue(value) {
+    if (!value) {
+      return;
+    }
+
+    value = value.trim();
+
+    // Try with one of the predefined values
+    let coordinates = PREDEFINED[value];
+
+    // Otherwise parse the coordinates from the cubic-bezier function
+    if (!coordinates && value.startsWith("cubic-bezier")) {
+      coordinates = value.replace(/cubic-bezier|\(|\)/g, "").split(",").map(parseFloat);
+    }
+
+    this.coordinates = coordinates;
+  },
+
+  destroy: function() {
+    this._removeEvents();
+    this._removeMarkup();
+
+    this.timingPreview.destroy();
+
+    this.curve = this.p1 = this.p2 = null;
+  }
+};
+
+/**
+ * The TimingFunctionPreviewWidget animates a dot on a scale with a given
+ * timing-function
+ * @param {DOMNode} parent The container where this widget should go
+ */
+function TimingFunctionPreviewWidget(parent) {
+  this.previousValue = null;
+  this.autoRestartAnimation = null;
+
+  this.parent = parent;
+  this._initMarkup();
+}
+
+TimingFunctionPreviewWidget.prototype = {
+  PREVIEW_DURATION: 1000,
+
+  _initMarkup: function() {
+    let doc = this.parent.ownerDocument;
+
+    let container = doc.createElement("div");
+    container.className = "timing-function-preview";
+
+    this.dot = doc.createElement("div");
+    this.dot.className = "dot";
+    container.appendChild(this.dot);
+
+    let scale = doc.createElement("div");
+    scale.className = "scale";
+    container.appendChild(scale);
+
+    this.parent.appendChild(container);
+  },
+
+  destroy: function() {
+    clearTimeout(this.autoRestartAnimation);
+    this.parent.querySelector(".timing-function-preview").remove();
+    this.parent = this.dot = null;
+  },
+
+  /**
+   * Preview a new timing function. The current preview will only be stopped if
+   * the supplied function value is different from the previous one. If the
+   * supplied function is invalid, the preview will stop.
+   * @param {String} value
+   */
+  preview: function(value) {
+    // Don't restart the preview animation if the value is the same
+    if (value === this.previousValue) {
+      return false;
+    }
+
+    clearTimeout(this.autoRestartAnimation);
+
+    if (isValidTimingFunction(value)) {
+      this.dot.style.animationTimingFunction = value;
+      this.restartAnimation();
+    }
+
+    this.previousValue = value;
+  },
+
+  /**
+   * Re-start the preview animation from the beginning
+   */
+  restartAnimation: function() {
+    // Reset the animation duration in case it was changed
+    this.dot.style.animationDuration = (this.PREVIEW_DURATION * 2) + "ms";
+
+    // Just toggling the class won't do it unless there's a sync reflow
+    this.dot.classList.remove("animate");
+    let w = this.dot.offsetWidth;
+    this.dot.classList.add("animate");
+
+    // Restart it again after a while
+    this.autoRestartAnimation = setTimeout(this.restartAnimation.bind(this),
+      this.PREVIEW_DURATION * 2);
+  }
+};
+
+// Helpers
+
+function getPadding(padding) {
+  let p = typeof padding === 'number'? [padding] : padding;
+
+  if (p.length === 1) {
+    p[1] = p[0];
+  }
+
+  if (p.length === 2) {
+    p[2] = p[0];
+  }
+
+  if (p.length === 3) {
+    p[3] = p[1];
+  }
+
+  return p;
+}
+
+function distance(x1, y1, x2, y2) {
+  return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
+}
+
+/**
+ * Checks whether a string is a valid timing-function value
+ * @param {String} value
+ * @return {Boolean}
+ */
+function isValidTimingFunction(value) {
+  // Either it's a predefined value
+  if (value in PREDEFINED) {
+    return true;
+  }
+
+  // Or it has to match a cubic-bezier expression
+  if (value.match(/^cubic-bezier\(([0-9.\- ]+,){3}[0-9.\- ]+\)/)) {
+    return true;
+  }
+
+  return false;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/widgets/cubic-bezier-frame.xhtml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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/. -->
+<!DOCTYPE html>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+  <link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
+  <link rel="stylesheet" href="chrome://browser/content/devtools/cubic-bezier.css" ype="text/css"/>
+  <script type="application/javascript;version=1.8" src="theme-switching.js"/>
+  <style>
+    body {
+      margin: 0;
+      padding: 0;
+      width: 200px;
+      height: 415px;
+    }
+  </style>
+</head>
+<body role="application">
+  <div id="container"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/widgets/cubic-bezier.css
@@ -0,0 +1,142 @@
+/* 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/. */
+
+/* Based on Lea Verou www.cubic-bezier.com
+   See https://github.com/LeaVerou/cubic-bezier */
+
+.coordinate-plane {
+  position: absolute;
+  line-height: 0;
+  height: 400px;
+  width: 200px;
+}
+
+.coordinate-plane:before,
+.coordinate-plane:after {
+  position: absolute;
+  bottom: 25%;
+  left: 0;
+  width: 100%;
+}
+
+.coordinate-plane:before {
+  content: "";
+  border-bottom: 2px solid;
+  transform: rotate(-90deg) translateY(2px);
+  transform-origin: bottom left;
+}
+
+.coordinate-plane:after {
+  content: "";
+  border-top: 2px solid;
+  margin-bottom: -2px;
+}
+
+.theme-dark .coordinate-plane:before,
+.theme-dark .coordinate-plane:after {
+  border-color: #eee;
+}
+
+.control-point {
+  position: absolute;
+  z-index: 1;
+  height: 10px;
+  width: 10px;
+  border: 0;
+  background: #666;
+  display: block;
+  margin: -5px 0 0 -5px;
+  outline: none;
+  border-radius: 5px;
+  padding: 0;
+
+  cursor: pointer;
+}
+
+#P1x, #P1y {
+  color: #f08;
+}
+
+#P2x, #P2y {
+  color: #0ab;
+}
+
+canvas#curve {
+  background:
+    linear-gradient(-45deg, transparent 49.7%, rgba(0,0,0,.2) 49.7%, rgba(0,0,0,.2) 50.3%, transparent 50.3%) center no-repeat,
+    repeating-linear-gradient(transparent, #eee 0, #eee .5%, transparent .5%, transparent 10%) no-repeat,
+    repeating-linear-gradient(-90deg, transparent, #eee 0, #eee .5%, transparent .5%, transparent 10%) no-repeat;
+
+  background-size: 100% 50%, 100% 50%, 100% 50%;
+  background-position: 25%, 0, 0;
+
+  -moz-user-select: none;
+}
+
+.theme-dark canvas#curve {
+  background:
+    linear-gradient(-45deg, transparent 49.7%, #eee 49.7%, #eee 50.3%, transparent 50.3%) center no-repeat,
+    repeating-linear-gradient(transparent, rgba(0,0,0,.2) 0, rgba(0,0,0,.2) .5%, transparent .5%, transparent 10%) no-repeat,
+    repeating-linear-gradient(-90deg, transparent, rgba(0,0,0,.2) 0, rgba(0,0,0,.2) .5%, transparent .5%, transparent 10%) no-repeat;
+
+  background-size: 100% 50%, 100% 50%, 100% 50%;
+  background-position: 25%, 0, 0;
+}
+
+/* Timing function preview widget */
+
+.timing-function-preview {
+  position: absolute;
+  top: 400px;
+}
+
+.timing-function-preview .scale {
+  position: absolute;
+  top: 6px;
+  left: 0;
+  z-index: 1;
+
+  width: 200px;
+  height: 1px;
+
+  background: #ccc;
+}
+
+.timing-function-preview .dot {
+  position: absolute;
+  top: 0;
+  left: -7px;
+  z-index: 2;
+
+  width: 10px;
+  height: 10px;
+
+  border-radius: 50%;
+  border: 2px solid white;
+  background: #4C9ED9;
+}
+
+.timing-function-preview .dot.animate {
+  animation-duration: 2.5s;
+  animation-fill-mode: forwards;
+  animation-name: timing-function-preview;
+}
+
+@keyframes timing-function-preview {
+  0% {
+    left: -7px;
+  }
+  33% {
+    left: 193px;
+  }
+  50% {
+    left: 193px;
+  }
+  83% {
+    left: -7px;
+  }
+  100% {
+    left: -7px;
+  }
+}
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -25,8 +25,13 @@ stop=Stop
 peer_ended_conversation=Your peer ended the conversation.
 call_has_ended=Your call has ended.
 close_window=Close this window
 
 cannot_start_call_session_not_ready=Can't start call, session is not ready.
 network_disconnected=The network connection terminated abruptly.
 
 connection_error_see_console_notification=Call failed; see console for details.
+## LOCALIZATION NOTE (legal_text_and_links): In this item, don't translate the
+## part between {{..}}
+legal_text_and_links.innerHTML=By using this product you agree to the <a \
+  href="{{terms_of_use_url}}">Terms of Use</a> and <a \
+  href="{{privacy_notice_url}}">Privacy Notice</a>
--- a/content/media/test/test_fastSeek.html
+++ b/content/media/test/test_fastSeek.html
@@ -4,16 +4,25 @@
 https://bugzilla.mozilla.org/show_bug.cgi?id=778077
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 778077</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=778077">Mozilla Bug 778077</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
   <script type="application/javascript">
 
     /** Test for Bug 778077 - HTMLMediaElement.fastSeek() **/
     // Iterate through a list of keyframe timestamps, and seek to
     // halfway between the keyframe and the keyframe after it.
     var manager = new MediaTestManager;
 
     function doSeek(v) {
@@ -70,19 +79,10 @@ https://bugzilla.mozilla.org/show_bug.cg
       v.addEventListener("loadedmetadata", onloadedmetadata);
       v.addEventListener("seeked", onseeked);
       document.body.appendChild(v);
     }
 
     manager.runTests(gFastSeekTests, startTest);
 
   </script>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=778077">Mozilla Bug 778077</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-</pre>
 </body>
 </html>
--- a/dom/bluetooth/bluedroid/BluetoothInterface.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothInterface.cpp
@@ -1,23 +1,80 @@
 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
 /* 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/. */
 
 #include "BluetoothInterface.h"
+#include "nsAutoPtr.h"
+#include "nsThreadUtils.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 template<class T>
 struct interface_traits
 { };
 
 //
+// Result handling
+//
+
+template <typename Obj, typename Res>
+class BluetoothInterfaceRunnable0 : public nsRunnable
+{
+public:
+  BluetoothInterfaceRunnable0(Obj* aObj, Res (Obj::*aMethod)())
+  : mObj(aObj)
+  , mMethod(aMethod)
+  {
+    MOZ_ASSERT(mObj);
+    MOZ_ASSERT(mMethod);
+  }
+
+  NS_METHOD
+  Run() MOZ_OVERRIDE
+  {
+    ((*mObj).*mMethod)();
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<Obj> mObj;
+  void (Obj::*mMethod)();
+};
+
+template <typename Obj, typename Res, typename Arg1>
+class BluetoothInterfaceRunnable1 : public nsRunnable
+{
+public:
+  BluetoothInterfaceRunnable1(Obj* aObj, Res (Obj::*aMethod)(Arg1),
+                              const Arg1& aArg1)
+  : mObj(aObj)
+  , mMethod(aMethod)
+  , mArg1(aArg1)
+  {
+    MOZ_ASSERT(mObj);
+    MOZ_ASSERT(mMethod);
+  }
+
+  NS_METHOD
+  Run() MOZ_OVERRIDE
+  {
+    ((*mObj).*mMethod)(mArg1);
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<Obj> mObj;
+  void (Obj::*mMethod)(Arg1);
+  Arg1 mArg1;
+};
+
+//
 // Socket Interface
 //
 
 template<>
 struct interface_traits<BluetoothSocketInterface>
 {
   typedef const btsock_interface_t const_interface_type;
 
@@ -366,16 +423,46 @@ BluetoothAvrcpInterface::SetVolume(uint8
 #endif
 }
 #endif
 
 //
 // Bluetooth Core Interface
 //
 
+typedef
+  BluetoothInterfaceRunnable0<BluetoothResultHandler, void>
+  BluetoothResultRunnable;
+
+typedef
+  BluetoothInterfaceRunnable1<BluetoothResultHandler, void, int>
+  BluetoothErrorRunnable;
+
+static nsresult
+DispatchBluetoothResult(BluetoothResultHandler* aRes,
+                        void (BluetoothResultHandler::*aMethod)(),
+                        int aStatus)
+{
+  MOZ_ASSERT(aRes);
+
+  nsRunnable* runnable;
+
+  if (aStatus == BT_STATUS_SUCCESS) {
+    runnable = new BluetoothResultRunnable(aRes, aMethod);
+  } else {
+    runnable = new
+      BluetoothErrorRunnable(aRes, &BluetoothResultHandler::OnError, aStatus);
+  }
+  nsresult rv = NS_DispatchToMainThread(runnable);
+  if (NS_FAILED(rv)) {
+    BT_WARNING("NS_DispatchToMainThread failed: %X", rv);
+  }
+  return rv;
+}
+
 /* returns the container structure of a variable; _t is the container's
  * type, _v the name of the variable, and _m is _v's field within _t
  */
 #define container(_t, _v, _m) \
   ( (_t*)( ((const unsigned char*)(_v)) - offsetof(_t, _m) ) )
 
 BluetoothInterface*
 BluetoothInterface::GetInstance()
@@ -437,168 +524,313 @@ BluetoothInterface::BluetoothInterface(c
 : mInterface(aInterface)
 {
   MOZ_ASSERT(mInterface);
 }
 
 BluetoothInterface::~BluetoothInterface()
 { }
 
-int
-BluetoothInterface::Init(bt_callbacks_t* aCallbacks)
+void
+BluetoothInterface::Init(bt_callbacks_t* aCallbacks,
+                         BluetoothResultHandler* aRes)
 {
-  return mInterface->init(aCallbacks);
+  int status = mInterface->init(aCallbacks);
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes, &BluetoothResultHandler::Init, status);
+  }
 }
 
 void
-BluetoothInterface::Cleanup()
+BluetoothInterface::Cleanup(BluetoothResultHandler* aRes)
 {
   mInterface->cleanup();
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes, &BluetoothResultHandler::Cleanup,
+                            BT_STATUS_SUCCESS);
+  }
 }
 
-int
-BluetoothInterface::Enable()
+void
+BluetoothInterface::Enable(BluetoothResultHandler* aRes)
 {
-  return mInterface->enable();
+  int status = mInterface->enable();
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes, &BluetoothResultHandler::Enable, status);
+  }
 }
 
-int
-BluetoothInterface::Disable()
+void
+BluetoothInterface::Disable(BluetoothResultHandler* aRes)
 {
-  return mInterface->disable();
+  int status = mInterface->disable();
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes, &BluetoothResultHandler::Disable, status);
+  }
 }
 
 /* Adapter Properties */
 
-int
-BluetoothInterface::GetAdapterProperties()
+void
+BluetoothInterface::GetAdapterProperties(BluetoothResultHandler* aRes)
 {
-  return mInterface->get_adapter_properties();
+  int status = mInterface->get_adapter_properties();
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::GetAdapterProperties,
+                            status);
+  }
 }
 
-int
-BluetoothInterface::GetAdapterProperty(bt_property_type_t aType)
+void
+BluetoothInterface::GetAdapterProperty(bt_property_type_t aType,
+                                       BluetoothResultHandler* aRes)
 {
-  return mInterface->get_adapter_property(aType);
+  int status = mInterface->get_adapter_property(aType);
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::GetAdapterProperty,
+                            status);
+  }
 }
 
-int
-BluetoothInterface::SetAdapterProperty(const bt_property_t* aProperty)
+void
+BluetoothInterface::SetAdapterProperty(const bt_property_t* aProperty,
+                                       BluetoothResultHandler* aRes)
 {
-  return mInterface->set_adapter_property(aProperty);
+  int status = mInterface->set_adapter_property(aProperty);
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::SetAdapterProperty,
+                            status);
+  }
 }
 
 /* Remote Device Properties */
 
-int
-BluetoothInterface::GetRemoteDeviceProperties(bt_bdaddr_t *aRemoteAddr)
+void
+BluetoothInterface::GetRemoteDeviceProperties(bt_bdaddr_t *aRemoteAddr,
+                                              BluetoothResultHandler* aRes)
 {
-  return mInterface->get_remote_device_properties(aRemoteAddr);
+  int status = mInterface->get_remote_device_properties(aRemoteAddr);
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::GetRemoteDeviceProperties,
+                            status);
+  }
 }
 
-int
+void
 BluetoothInterface::GetRemoteDeviceProperty(bt_bdaddr_t* aRemoteAddr,
-                                            bt_property_type_t aType)
+                                            bt_property_type_t aType,
+                                            BluetoothResultHandler* aRes)
 {
-  return mInterface->get_remote_device_property(aRemoteAddr, aType);
+  int status = mInterface->get_remote_device_property(aRemoteAddr, aType);
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::GetRemoteDeviceProperty,
+                            status);
+  }
 }
 
-int
+void
 BluetoothInterface::SetRemoteDeviceProperty(bt_bdaddr_t* aRemoteAddr,
-                                            const bt_property_t* aProperty)
+                                            const bt_property_t* aProperty,
+                                            BluetoothResultHandler* aRes)
 {
-  return mInterface->set_remote_device_property(aRemoteAddr, aProperty);
+  int status = mInterface->set_remote_device_property(aRemoteAddr, aProperty);
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::SetRemoteDeviceProperty,
+                            status);
+  }
 }
 
 /* Remote Services */
 
-int
+void
 BluetoothInterface::GetRemoteServiceRecord(bt_bdaddr_t* aRemoteAddr,
-                                           bt_uuid_t* aUuid)
+                                           bt_uuid_t* aUuid,
+                                           BluetoothResultHandler* aRes)
 {
-  return mInterface->get_remote_service_record(aRemoteAddr, aUuid);
+  int status = mInterface->get_remote_service_record(aRemoteAddr, aUuid);
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::GetRemoteServiceRecord,
+                            status);
+  }
 }
 
-int
-BluetoothInterface::GetRemoteServices(bt_bdaddr_t* aRemoteAddr)
+void
+BluetoothInterface::GetRemoteServices(bt_bdaddr_t* aRemoteAddr,
+                                      BluetoothResultHandler* aRes)
 {
-  return mInterface->get_remote_services(aRemoteAddr);
+  int status = mInterface->get_remote_services(aRemoteAddr);
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::GetRemoteServices,
+                            status);
+  }
 }
 
 /* Discovery */
 
-int
-BluetoothInterface::StartDiscovery()
+void
+BluetoothInterface::StartDiscovery(BluetoothResultHandler* aRes)
 {
-  return mInterface->start_discovery();
+  int status = mInterface->start_discovery();
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::StartDiscovery,
+                            status);
+  }
 }
 
-int
-BluetoothInterface::CancelDiscovery()
+void
+BluetoothInterface::CancelDiscovery(BluetoothResultHandler* aRes)
 {
-  return mInterface->cancel_discovery();
+  int status = mInterface->cancel_discovery();
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::CancelDiscovery,
+                            status);
+  }
 }
 
 /* Bonds */
 
-int
-BluetoothInterface::CreateBond(const bt_bdaddr_t* aBdAddr)
+void
+BluetoothInterface::CreateBond(const bt_bdaddr_t* aBdAddr,
+                               BluetoothResultHandler* aRes)
 {
-  return mInterface->create_bond(aBdAddr);
+  int status = mInterface->create_bond(aBdAddr);
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::CreateBond,
+                            status);
+  }
 }
 
-int
-BluetoothInterface::RemoveBond(const bt_bdaddr_t* aBdAddr)
+void
+BluetoothInterface::RemoveBond(const bt_bdaddr_t* aBdAddr,
+                               BluetoothResultHandler* aRes)
 {
-  return mInterface->remove_bond(aBdAddr);
+  int status = mInterface->remove_bond(aBdAddr);
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::RemoveBond,
+                            status);
+  }
 }
 
-int
-BluetoothInterface::CancelBond(const bt_bdaddr_t* aBdAddr)
+void
+BluetoothInterface::CancelBond(const bt_bdaddr_t* aBdAddr,
+                               BluetoothResultHandler* aRes)
 {
-  return mInterface->cancel_bond(aBdAddr);
+  int status = mInterface->cancel_bond(aBdAddr);
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::CancelBond,
+                            status);
+  }
 }
 
 /* Authentication */
 
-int
+void
 BluetoothInterface::PinReply(const bt_bdaddr_t* aBdAddr, uint8_t aAccept,
-                             uint8_t aPinLen, bt_pin_code_t* aPinCode)
+                             uint8_t aPinLen, bt_pin_code_t* aPinCode,
+                             BluetoothResultHandler* aRes)
 {
-  return mInterface->pin_reply(aBdAddr, aAccept, aPinLen, aPinCode);
+  int status = mInterface->pin_reply(aBdAddr, aAccept, aPinLen, aPinCode);
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::PinReply,
+                            status);
+  }
 }
 
-int
+void
 BluetoothInterface::SspReply(const bt_bdaddr_t* aBdAddr,
                              bt_ssp_variant_t aVariant,
-                             uint8_t aAccept, uint32_t aPasskey)
+                             uint8_t aAccept, uint32_t aPasskey,
+                             BluetoothResultHandler* aRes)
 {
-  return mInterface->ssp_reply(aBdAddr, aVariant, aAccept, aPasskey);
+  int status = mInterface->ssp_reply(aBdAddr, aVariant, aAccept, aPasskey);
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::SspReply,
+                            status);
+  }
 }
 
 /* DUT Mode */
 
-int
-BluetoothInterface::DutModeConfigure(uint8_t aEnable)
+void
+BluetoothInterface::DutModeConfigure(uint8_t aEnable,
+                                     BluetoothResultHandler* aRes)
 {
-  return mInterface->dut_mode_configure(aEnable);
+  int status = mInterface->dut_mode_configure(aEnable);
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::DutModeConfigure,
+                            status);
+  }
 }
 
-int
-BluetoothInterface::DutModeSend(uint16_t aOpcode, uint8_t* aBuf, uint8_t aLen)
+void
+BluetoothInterface::DutModeSend(uint16_t aOpcode, uint8_t* aBuf, uint8_t aLen,
+                                BluetoothResultHandler* aRes)
 {
-  return mInterface->dut_mode_send(aOpcode, aBuf, aLen);
+  int status = mInterface->dut_mode_send(aOpcode, aBuf, aLen);
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::DutModeSend,
+                            status);
+  }
 }
 
 /* LE Mode */
 
-int
-BluetoothInterface::LeTestMode(uint16_t aOpcode, uint8_t* aBuf, uint8_t aLen)
+void
+BluetoothInterface::LeTestMode(uint16_t aOpcode, uint8_t* aBuf, uint8_t aLen,
+                               BluetoothResultHandler* aRes)
 {
-  return mInterface->le_test_mode(aOpcode, aBuf, aLen);
+#if ANDROID_VERSION >= 18
+  int status = mInterface->le_test_mode(aOpcode, aBuf, aLen);
+#else
+  int status = BT_STATUS_UNSUPPORTED;
+#endif
+
+  if (aRes) {
+    DispatchBluetoothResult(aRes,
+                            &BluetoothResultHandler::LeTestMode,
+                            status);
+  }
 }
 
 /* Profile Interfaces */
 
 template <class T>
 T*
 BluetoothInterface::GetProfileInterface()
 {
--- a/dom/bluetooth/bluedroid/BluetoothInterface.h
+++ b/dom/bluetooth/bluedroid/BluetoothInterface.h
@@ -22,17 +22,17 @@ class BluetoothInterface;
 
 //
 // Socket Interface
 //
 
 class BluetoothSocketInterface
 {
 public:
-  friend BluetoothInterface;
+  friend class BluetoothInterface;
 
   // Init and Cleanup is handled by BluetoothInterface
 
   bt_status_t Listen(btsock_type_t aType,
                      const char* aServiceName, const uint8_t* aServiceUuid,
                      int aChannel, int& aSockFd, int aFlags);
 
   bt_status_t Connect(const bt_bdaddr_t* aBdAddr, btsock_type_t aType,
@@ -49,17 +49,17 @@ private:
 
 //
 // Handsfree Interface
 //
 
 class BluetoothHandsfreeInterface
 {
 public:
-  friend BluetoothInterface;
+  friend class BluetoothInterface;
 
   bt_status_t Init(bthf_callbacks_t* aCallbacks);
   void        Cleanup();
 
   /* Connect / Disconnect */
 
   bt_status_t Connect(bt_bdaddr_t* aBdAddr);
   bt_status_t Disconnect(bt_bdaddr_t* aBdAddr);
@@ -111,17 +111,17 @@ private:
 
 //
 // Bluetooth Advanced Audio Interface
 //
 
 class BluetoothA2dpInterface
 {
 public:
-  friend BluetoothInterface;
+  friend class BluetoothInterface;
 
   bt_status_t Init(btav_callbacks_t *aCallbacks);
   void        Cleanup();
 
   bt_status_t Connect(bt_bdaddr_t *aBdAddr);
   bt_status_t Disconnect(bt_bdaddr_t *aBdAddr);
 
 protected:
@@ -135,17 +135,17 @@ private:
 //
 // Bluetooth AVRCP Interface
 //
 
 class BluetoothAvrcpInterface
 {
 #if ANDROID_VERSION >= 18
 public:
-  friend BluetoothInterface;
+  friend class BluetoothInterface;
 
   bt_status_t Init(btrc_callbacks_t* aCallbacks);
   void        Cleanup();
 
   bt_status_t GetPlayStatusRsp(btrc_play_status_t aPlayStatus,
                                uint32_t aSongLen, uint32_t aSongPos);
 
   bt_status_t ListPlayerAppAttrRsp(int aNumAttr, btrc_player_attr_t* aPAttrs);
@@ -176,74 +176,130 @@ private:
   const btrc_interface_t* mInterface;
 #endif
 };
 
 //
 // Bluetooth Core Interface
 //
 
+class BluetoothResultHandler
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BluetoothResultHandler)
+
+  virtual ~BluetoothResultHandler() { }
+
+  virtual void OnError(int aStatus)
+  {
+    BT_LOGR("received error code %d", aStatus);
+  }
+
+  virtual void Init() { }
+  virtual void Cleanup() { }
+  virtual void Enable() { }
+  virtual void Disable() { }
+
+  virtual void GetAdapterProperties() { }
+  virtual void GetAdapterProperty() { }
+  virtual void SetAdapterProperty() { }
+
+  virtual void GetRemoteDeviceProperties() { }
+  virtual void GetRemoteDeviceProperty() { }
+  virtual void SetRemoteDeviceProperty() { }
+
+  virtual void GetRemoteServiceRecord() { }
+  virtual void GetRemoteServices() { }
+
+  virtual void StartDiscovery() { }
+  virtual void CancelDiscovery() { }
+
+  virtual void CreateBond() { }
+  virtual void RemoveBond() { }
+  virtual void CancelBond() { }
+
+  virtual void PinReply() { }
+  virtual void SspReply() { }
+
+  virtual void DutModeConfigure() { }
+  virtual void DutModeSend() { }
+
+  virtual void LeTestMode() { }
+};
+
 class BluetoothInterface
 {
 public:
   static BluetoothInterface* GetInstance();
 
-  int  Init(bt_callbacks_t* aCallbacks);
-  void Cleanup();
+  void Init(bt_callbacks_t* aCallbacks, BluetoothResultHandler* aRes);
+  void Cleanup(BluetoothResultHandler* aRes);
 
-  int Enable();
-  int Disable();
+  void Enable(BluetoothResultHandler* aRes);
+  void Disable(BluetoothResultHandler* aRes);
+
 
   /* Adapter Properties */
 
-  int GetAdapterProperties();
-  int GetAdapterProperty(bt_property_type_t aType);
-  int SetAdapterProperty(const bt_property_t* aProperty);
+  void GetAdapterProperties(BluetoothResultHandler* aRes);
+  void GetAdapterProperty(bt_property_type_t aType,
+                          BluetoothResultHandler* aRes);
+  void SetAdapterProperty(const bt_property_t* aProperty,
+                          BluetoothResultHandler* aRes);
 
   /* Remote Device Properties */
 
-  int GetRemoteDeviceProperties(bt_bdaddr_t *aRemoteAddr);
-  int GetRemoteDeviceProperty(bt_bdaddr_t* aRemoteAddr,
-                              bt_property_type_t aType);
-  int SetRemoteDeviceProperty(bt_bdaddr_t* aRemoteAddr,
-                              const bt_property_t* aProperty);
+  void GetRemoteDeviceProperties(bt_bdaddr_t *aRemoteAddr,
+                                 BluetoothResultHandler* aRes);
+  void GetRemoteDeviceProperty(bt_bdaddr_t* aRemoteAddr,
+                               bt_property_type_t aType,
+                               BluetoothResultHandler* aRes);
+  void SetRemoteDeviceProperty(bt_bdaddr_t* aRemoteAddr,
+                               const bt_property_t* aProperty,
+                               BluetoothResultHandler* aRes);
 
   /* Remote Services */
 
-  int GetRemoteServiceRecord(bt_bdaddr_t* aRemoteAddr,
-                             bt_uuid_t* aUuid);
-  int GetRemoteServices(bt_bdaddr_t* aRemoteAddr);
+  void GetRemoteServiceRecord(bt_bdaddr_t* aRemoteAddr,
+                              bt_uuid_t* aUuid,
+                              BluetoothResultHandler* aRes);
+  void GetRemoteServices(bt_bdaddr_t* aRemoteAddr,
+                         BluetoothResultHandler* aRes);
 
   /* Discovery */
 
-  int StartDiscovery();
-  int CancelDiscovery();
+  void StartDiscovery(BluetoothResultHandler* aRes);
+  void CancelDiscovery(BluetoothResultHandler* aRes);
 
   /* Bonds */
 
-  int CreateBond(const bt_bdaddr_t* aBdAddr);
-  int RemoveBond(const bt_bdaddr_t* aBdAddr);
-  int CancelBond(const bt_bdaddr_t* aBdAddr);
+  void CreateBond(const bt_bdaddr_t* aBdAddr, BluetoothResultHandler* aRes);
+  void RemoveBond(const bt_bdaddr_t* aBdAddr, BluetoothResultHandler* aRes);
+  void CancelBond(const bt_bdaddr_t* aBdAddr, BluetoothResultHandler* aRes);
 
   /* Authentication */
 
-  int PinReply(const bt_bdaddr_t* aBdAddr, uint8_t aAccept,
-               uint8_t aPinLen, bt_pin_code_t* aPinCode);
+  void PinReply(const bt_bdaddr_t* aBdAddr, uint8_t aAccept,
+                uint8_t aPinLen, bt_pin_code_t* aPinCode,
+                BluetoothResultHandler* aRes);
 
-  int SspReply(const bt_bdaddr_t* aBdAddr, bt_ssp_variant_t aVariant,
-               uint8_t aAccept, uint32_t aPasskey);
+  void SspReply(const bt_bdaddr_t* aBdAddr, bt_ssp_variant_t aVariant,
+                uint8_t aAccept, uint32_t aPasskey,
+                BluetoothResultHandler* aRes);
 
   /* DUT Mode */
 
-  int DutModeConfigure(uint8_t aEnable);
-  int DutModeSend(uint16_t aOpcode, uint8_t* aBuf, uint8_t aLen);
+  void DutModeConfigure(uint8_t aEnable, BluetoothResultHandler* aRes);
+  void DutModeSend(uint16_t aOpcode, uint8_t* aBuf, uint8_t aLen,
+                   BluetoothResultHandler* aRes);
 
   /* LE Mode */
 
-  int LeTestMode(uint16_t aOpcode, uint8_t* aBuf, uint8_t aLen);
+  void LeTestMode(uint16_t aOpcode, uint8_t* aBuf, uint8_t aLen,
+                  BluetoothResultHandler* aRes);
 
   /* Profile Interfaces */
 
   BluetoothSocketInterface* GetBluetoothSocketInterface();
   BluetoothHandsfreeInterface* GetBluetoothHandsfreeInterface();
   BluetoothA2dpInterface* GetBluetoothA2dpInterface();
   BluetoothAvrcpInterface* GetBluetoothAvrcpInterface();
 
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
@@ -54,16 +54,17 @@ USING_BLUETOOTH_NAMESPACE
 // TODO: Non thread-safe static variables
 static nsString sAdapterBdAddress;
 static nsString sAdapterBdName;
 static InfallibleTArray<nsString> sAdapterBondedAddressArray;
 
 // Static variables below should only be used on *main thread*
 static BluetoothInterface* sBtInterface;
 static nsTArray<nsRefPtr<BluetoothProfileController> > sControllerArray;
+static InfallibleTArray<BluetoothNamedValue> sRemoteDevicesPack;
 static nsTArray<int> sRequestedDeviceCountArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sSetPropertyRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sGetDeviceRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sBondingRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sUnbondingRunnableArray;
 
 // Static variables below should only be used on *callback thread*
 
@@ -98,16 +99,26 @@ public:
 
 private:
   BluetoothSignal mSignal;
 };
 
 class SetupAfterEnabledTask MOZ_FINAL : public nsRunnable
 {
 public:
+  class SetAdapterPropertyResultHandler MOZ_FINAL
+  : public BluetoothResultHandler
+  {
+  public:
+    void OnError(int aStatus) MOZ_OVERRIDE
+    {
+      BT_LOGR("Fail to set: BT_SCAN_MODE_CONNECTABLE");
+    }
+  };
+
   NS_IMETHOD
   Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     // Bluetooth just enabled, clear profile controllers and runnable arrays.
     sControllerArray.Clear();
     sBondingRunnableArray.Clear();
@@ -118,21 +129,18 @@ public:
     // Bluetooth scan mode is NONE by default
     bt_scan_mode_t mode = BT_SCAN_MODE_CONNECTABLE;
     bt_property_t prop;
     prop.type = BT_PROPERTY_ADAPTER_SCAN_MODE;
     prop.val = (void*)&mode;
     prop.len = sizeof(mode);
 
     NS_ENSURE_TRUE(sBtInterface, NS_ERROR_FAILURE);
-
-    int ret = sBtInterface->SetAdapterProperty(&prop);
-    if (ret != BT_STATUS_SUCCESS) {
-      BT_LOGR("Fail to set: BT_SCAN_MODE_CONNECTABLE");
-    }
+    sBtInterface->SetAdapterProperty(&prop,
+                                     new SetAdapterPropertyResultHandler());
 
     // Try to fire event 'AdapterAdded' to fit the original behaviour when
     // we used BlueZ as backend.
     BluetoothService* bs = BluetoothService::Get();
     NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
 
     bs->AdapterAddedReceived();
     bs->TryFiringAdapterAdded();
@@ -153,17 +161,17 @@ public:
   NS_IMETHOD
   Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     // Cleanup bluetooth interfaces after BT state becomes BT_STATE_OFF.
     BluetoothHfpManager::DeinitHfpInterface();
     BluetoothA2dpManager::DeinitA2dpInterface();
-    sBtInterface->Cleanup();
+    sBtInterface->Cleanup(nullptr);
 
     return NS_OK;
   }
 };
 
 /**
  *  Static callback functions
  */
@@ -444,18 +452,16 @@ public:
                            mRemoteDeviceBdAddress, mProps);
     nsRefPtr<DistributeBluetoothSignalTask>
       t = new DistributeBluetoothSignalTask(signal);
     if (NS_FAILED(NS_DispatchToMainThread(t))) {
       BT_WARNING("Failed to dispatch to main thread!");
       return NS_OK;
     }
 
-    static InfallibleTArray<BluetoothNamedValue> sRemoteDevicesPack;
-
     // Use address as the index
     sRemoteDevicesPack.AppendElement(
       BluetoothNamedValue(mRemoteDeviceBdAddress, mProps));
 
     if (--sRequestedDeviceCountArray[0] == 0) {
       if (!sGetDeviceRunnableArray.IsEmpty()) {
         DispatchBluetoothReply(sGetDeviceRunnableArray[0],
                                sRemoteDevicesPack, EmptyString());
@@ -791,56 +797,122 @@ static bool
 EnsureBluetoothHalLoad()
 {
   sBtInterface = BluetoothInterface::GetInstance();
   NS_ENSURE_TRUE(sBtInterface, false);
 
   return true;
 }
 
-static bool
-EnableInternal()
+class EnableResultHandler MOZ_FINAL : public BluetoothResultHandler
 {
-  int ret = sBtInterface->Init(&sBluetoothCallbacks);
-  if (ret != BT_STATUS_SUCCESS) {
-    BT_LOGR("Error while setting the callbacks");
-    sBtInterface = nullptr;
-    return false;
+public:
+  void OnError(int aStatus) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    BT_LOGR("BluetoothInterface::Enable failed: %d", aStatus);
+
+    nsRefPtr<nsRunnable> runnable = new BluetoothService::ToggleBtAck(false);
+    if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
+      BT_WARNING("Failed to dispatch to main thread!");
+    }
+  }
+};
+
+class InitResultHandler MOZ_FINAL : public BluetoothResultHandler
+{
+public:
+  void Init() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // Register all the bluedroid callbacks before enable() get called
+    // It is required to register a2dp callbacks before a2dp media task starts up.
+    // If any interface cannot be initialized, turn on bluetooth core anyway.
+    BluetoothHfpManager::InitHfpInterface();
+    BluetoothA2dpManager::InitA2dpInterface();
+    sBtInterface->Enable(new EnableResultHandler());
   }
 
-  // Register all the bluedroid callbacks before enable() get called
-  // It is required to register a2dp callbacks before a2dp media task starts up.
-  // If any interface cannot be initialized, turn on bluetooth core anyway.
-  BluetoothHfpManager::InitHfpInterface();
-  BluetoothA2dpManager::InitA2dpInterface();
-  return sBtInterface->Enable();
-}
+  void OnError(int aStatus) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    BT_LOGR("BluetoothInterface::Init failed: %d", aStatus);
+
+    sBtInterface = nullptr;
+
+    nsRefPtr<nsRunnable> runnable = new BluetoothService::ToggleBtAck(false);
+    if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
+      BT_WARNING("Failed to dispatch to main thread!");
+    }
+  }
+};
 
 static nsresult
-StartStopGonkBluetooth(bool aShouldEnable)
+StartGonkBluetooth()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   NS_ENSURE_TRUE(sBtInterface, NS_ERROR_FAILURE);
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
 
-  if (bs->IsEnabled() == aShouldEnable) {
+  if (bs->IsEnabled()) {
     // Keep current enable status
-    nsRefPtr<nsRunnable> runnable =
-      new BluetoothService::ToggleBtAck(aShouldEnable);
+    nsRefPtr<nsRunnable> runnable = new BluetoothService::ToggleBtAck(true);
     if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
       BT_WARNING("Failed to dispatch to main thread!");
     }
     return NS_OK;
   }
 
-  int ret = aShouldEnable ? EnableInternal() : sBtInterface->Disable();
-  NS_ENSURE_TRUE(ret == BT_STATUS_SUCCESS, NS_ERROR_FAILURE);
+  sBtInterface->Init(&sBluetoothCallbacks, new InitResultHandler());
+
+  return NS_OK;
+}
+
+class DisableResultHandler MOZ_FINAL : public BluetoothResultHandler
+{
+public:
+  void OnError(int aStatus) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    BT_LOGR("BluetoothInterface::Disable failed: %d", aStatus);
+
+    nsRefPtr<nsRunnable> runnable = new BluetoothService::ToggleBtAck(true);
+    if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
+      BT_WARNING("Failed to dispatch to main thread!");
+    }
+  }
+};
+
+static nsresult
+StopGonkBluetooth()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  NS_ENSURE_TRUE(sBtInterface, NS_ERROR_FAILURE);
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
+
+  if (!bs->IsEnabled()) {
+    // Keep current enable status
+    nsRefPtr<nsRunnable> runnable = new BluetoothService::ToggleBtAck(false);
+    if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
+      BT_WARNING("Failed to dispatch to main thread!");
+    }
+    return NS_OK;
+  }
+
+  sBtInterface->Disable(new DisableResultHandler());
 
   return NS_OK;
 }
 
 static void
 ReplyStatusError(BluetoothReplyRunnable* aBluetoothReplyRunnable,
                  int aStatusCode, const nsAString& aCustomMsg)
 {
@@ -884,17 +956,17 @@ BluetoothServiceBluedroid::~BluetoothSer
 {
 }
 
 nsresult
 BluetoothServiceBluedroid::StartInternal()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  nsresult ret = StartStopGonkBluetooth(true);
+  nsresult ret = StartGonkBluetooth();
   if (NS_FAILED(ret)) {
     nsRefPtr<nsRunnable> runnable =
       new BluetoothService::ToggleBtAck(false);
     if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
       BT_WARNING("Failed to dispatch to main thread!");
     }
     BT_LOGR("Error");
   }
@@ -902,17 +974,17 @@ BluetoothServiceBluedroid::StartInternal
   return ret;
 }
 
 nsresult
 BluetoothServiceBluedroid::StopInternal()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  nsresult ret = StartStopGonkBluetooth(false);
+  nsresult ret = StopGonkBluetooth();
   if (NS_FAILED(ret)) {
     nsRefPtr<nsRunnable> runnable =
       new BluetoothService::ToggleBtAck(true);
     if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
       BT_WARNING("Failed to dispatch to main thread!");
     }
     BT_LOGR("Error");
   }
@@ -948,16 +1020,48 @@ BluetoothServiceBluedroid::GetDefaultAda
   BT_APPEND_NAMED_VALUE(v.get_ArrayOfBluetoothNamedValue(),
                         "Devices", sAdapterBondedAddressArray);
 
   DispatchBluetoothReply(aRunnable, v, EmptyString());
 
   return NS_OK;
 }
 
+class GetRemoteDevicePropertiesResultHandler MOZ_FINAL
+: public BluetoothResultHandler
+{
+public:
+  GetRemoteDevicePropertiesResultHandler(const nsAString& aDeviceAddress)
+  : mDeviceAddress(aDeviceAddress)
+  { }
+
+  void OnError(int aStatus) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    BT_WARNING("GetRemoteDeviceProperties(%s) failed: %d",
+               mDeviceAddress.get(), aStatus);
+
+    /* dispatch result after final pending operation */
+    if (--sRequestedDeviceCountArray[0] == 0) {
+      if (!sGetDeviceRunnableArray.IsEmpty()) {
+        DispatchBluetoothReply(sGetDeviceRunnableArray[0],
+                               sRemoteDevicesPack, EmptyString());
+        sGetDeviceRunnableArray.RemoveElementAt(0);
+      }
+
+      sRequestedDeviceCountArray.RemoveElementAt(0);
+      sRemoteDevicesPack.Clear();
+    }
+  }
+
+private:
+  nsString mDeviceAddress;
+};
+
 nsresult
 BluetoothServiceBluedroid::GetConnectedDevicePropertiesInternal(
   uint16_t aServiceUuid, BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
 
@@ -979,32 +1083,28 @@ BluetoothServiceBluedroid::GetConnectedD
 
   int requestedDeviceCount = deviceAddresses.Length();
   if (requestedDeviceCount == 0) {
     InfallibleTArray<BluetoothNamedValue> emptyArr;
     DispatchBluetoothReply(aRunnable, emptyArr, EmptyString());
     return NS_OK;
   }
 
+  sRequestedDeviceCountArray.AppendElement(requestedDeviceCount);
+  sGetDeviceRunnableArray.AppendElement(aRunnable);
+
   for (int i = 0; i < requestedDeviceCount; i++) {
     // Retrieve all properties of devices
     bt_bdaddr_t addressType;
     StringToBdAddressType(deviceAddresses[i], &addressType);
 
-    int ret = sBtInterface->GetRemoteDeviceProperties(&addressType);
-    if (ret != BT_STATUS_SUCCESS) {
-      DispatchBluetoothReply(aRunnable, BluetoothValue(true),
-                             NS_LITERAL_STRING("GetConnectedDeviceFailed"));
-      return NS_OK;
-    }
+    sBtInterface->GetRemoteDeviceProperties(&addressType,
+      new GetRemoteDevicePropertiesResultHandler(deviceAddresses[i]));
   }
 
-  sRequestedDeviceCountArray.AppendElement(requestedDeviceCount);
-  sGetDeviceRunnableArray.AppendElement(aRunnable);
-
   return NS_OK;
 }
 
 nsresult
 BluetoothServiceBluedroid::GetPairedDevicePropertiesInternal(
   const nsTArray<nsString>& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -1013,73 +1113,117 @@ BluetoothServiceBluedroid::GetPairedDevi
 
   int requestedDeviceCount = aDeviceAddress.Length();
   if (requestedDeviceCount == 0) {
     InfallibleTArray<BluetoothNamedValue> emptyArr;
     DispatchBluetoothReply(aRunnable, BluetoothValue(emptyArr), EmptyString());
     return NS_OK;
   }
 
+  sRequestedDeviceCountArray.AppendElement(requestedDeviceCount);
+  sGetDeviceRunnableArray.AppendElement(aRunnable);
+
   for (int i = 0; i < requestedDeviceCount; i++) {
     // Retrieve all properties of devices
     bt_bdaddr_t addressType;
     StringToBdAddressType(aDeviceAddress[i], &addressType);
-    int ret = sBtInterface->GetRemoteDeviceProperties(&addressType);
-    if (ret != BT_STATUS_SUCCESS) {
-      DispatchBluetoothReply(aRunnable, BluetoothValue(true),
-                             NS_LITERAL_STRING("GetPairedDeviceFailed"));
-      return NS_OK;
-    }
+
+    sBtInterface->GetRemoteDeviceProperties(&addressType,
+      new GetRemoteDevicePropertiesResultHandler(aDeviceAddress[i]));
   }
 
-  sRequestedDeviceCountArray.AppendElement(requestedDeviceCount);
-  sGetDeviceRunnableArray.AppendElement(aRunnable);
-
   return NS_OK;
 }
 
+class StartDiscoveryResultHandler MOZ_FINAL : public BluetoothResultHandler
+{
+public:
+  StartDiscoveryResultHandler(BluetoothReplyRunnable* aRunnable)
+  : mRunnable(aRunnable)
+  { }
+
+  void StartDiscovery() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    DispatchBluetoothReply(mRunnable, true, EmptyString());
+  }
+
+  void OnError(int aStatus) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    ReplyStatusError(mRunnable, aStatus, NS_LITERAL_STRING("StartDiscovery"));
+  }
+
+private:
+  BluetoothReplyRunnable* mRunnable;
+};
+
 nsresult
 BluetoothServiceBluedroid::StartDiscoveryInternal(
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
-
-  int ret = sBtInterface->StartDiscovery();
-  if (ret != BT_STATUS_SUCCESS) {
-    ReplyStatusError(aRunnable, ret, NS_LITERAL_STRING("StartDiscovery"));
-
-    return NS_OK;
-  }
-
-  DispatchBluetoothReply(aRunnable, true, EmptyString());
+  sBtInterface->StartDiscovery(new StartDiscoveryResultHandler(aRunnable));
 
   return NS_OK;
 }
 
+class CancelDiscoveryResultHandler MOZ_FINAL : public BluetoothResultHandler
+{
+public:
+  CancelDiscoveryResultHandler(BluetoothReplyRunnable* aRunnable)
+  : mRunnable(aRunnable)
+  { }
+
+  void CancelDiscovery() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    DispatchBluetoothReply(mRunnable, true, EmptyString());
+  }
+
+  void OnError(int aStatus) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    ReplyStatusError(mRunnable, aStatus, NS_LITERAL_STRING("StopDiscovery"));
+  }
+
+private:
+  BluetoothReplyRunnable* mRunnable;
+};
+
 nsresult
 BluetoothServiceBluedroid::StopDiscoveryInternal(
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
-
-  int ret = sBtInterface->CancelDiscovery();
-  if (ret != BT_STATUS_SUCCESS) {
-    ReplyStatusError(aRunnable, ret, NS_LITERAL_STRING("StopDiscovery"));
-    return NS_ERROR_FAILURE;
-  }
-
-  DispatchBluetoothReply(aRunnable, true, EmptyString());
+  sBtInterface->CancelDiscovery(new CancelDiscoveryResultHandler(aRunnable));
 
   return NS_OK;
 }
 
+class SetAdapterPropertyResultHandler MOZ_FINAL : public BluetoothResultHandler
+{
+public:
+  SetAdapterPropertyResultHandler(BluetoothReplyRunnable* aRunnable)
+  : mRunnable(aRunnable)
+  { }
+
+  void OnError(int aStatus) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    ReplyStatusError(mRunnable, aStatus, NS_LITERAL_STRING("SetProperty"));
+  }
+private:
+  BluetoothReplyRunnable* mRunnable;
+};
+
 nsresult
 BluetoothServiceBluedroid::SetProperty(BluetoothObjectType aType,
                                        const BluetoothNamedValue& aValue,
                                        BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
@@ -1118,20 +1262,18 @@ BluetoothServiceBluedroid::SetProperty(B
     prop.len = sizeof(scanMode);
   } else {
     BT_LOGR("SetProperty but the property cannot be recognized correctly.");
     return NS_OK;
   }
 
   sSetPropertyRunnableArray.AppendElement(aRunnable);
 
-  int ret = sBtInterface->SetAdapterProperty(&prop);
-  if (ret != BT_STATUS_SUCCESS) {
-    ReplyStatusError(aRunnable, ret, NS_LITERAL_STRING("SetProperty"));
-  }
+  sBtInterface->SetAdapterProperty(&prop,
+    new SetAdapterPropertyResultHandler(aRunnable));
 
   return NS_OK;
 }
 
 nsresult
 BluetoothServiceBluedroid::GetServiceChannel(
   const nsAString& aDeviceAddress,
   const nsAString& aServiceUuid,
@@ -1143,114 +1285,176 @@ BluetoothServiceBluedroid::GetServiceCha
 bool
 BluetoothServiceBluedroid::UpdateSdpRecords(
   const nsAString& aDeviceAddress,
   BluetoothProfileManagerBase* aManager)
 {
   return true;
 }
 
+class CreateBondResultHandler MOZ_FINAL : public BluetoothResultHandler
+{
+public:
+  CreateBondResultHandler(size_t aRunnableIndex)
+  : mRunnableIndex(aRunnableIndex)
+  { }
+
+  void OnError(int aStatus) MOZ_OVERRIDE
+  {
+    BluetoothReplyRunnable* runnable = sBondingRunnableArray[mRunnableIndex];
+    sBondingRunnableArray[mRunnableIndex] = nullptr;
+    ReplyStatusError(runnable, aStatus, NS_LITERAL_STRING("CreatedPairedDevice"));
+  }
+
+private:
+  PRUint32 mRunnableIndex;
+};
+
 nsresult
 BluetoothServiceBluedroid::CreatePairedDeviceInternal(
   const nsAString& aDeviceAddress, int aTimeout,
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
 
   bt_bdaddr_t remoteAddress;
   StringToBdAddressType(aDeviceAddress, &remoteAddress);
 
-  int ret = sBtInterface->CreateBond(&remoteAddress);
-  if (ret != BT_STATUS_SUCCESS) {
-    ReplyStatusError(aRunnable, ret, NS_LITERAL_STRING("CreatedPairedDevice"));
-  } else {
-    sBondingRunnableArray.AppendElement(aRunnable);
-  }
+  PRUint32 i = sBondingRunnableArray.Length();
+  sBondingRunnableArray.AppendElement(aRunnable);
+
+  sBtInterface->CreateBond(&remoteAddress, new CreateBondResultHandler(i));
 
   return NS_OK;
 }
 
+class RemoveBondResultHandler MOZ_FINAL : public BluetoothResultHandler
+{
+public:
+  RemoveBondResultHandler(size_t aRunnableIndex)
+  : mRunnableIndex(aRunnableIndex)
+  { }
+
+  void OnError(int aStatus) MOZ_OVERRIDE
+  {
+    BluetoothReplyRunnable* runnable = sUnbondingRunnableArray[mRunnableIndex];
+    sUnbondingRunnableArray[mRunnableIndex] = nullptr;
+    ReplyStatusError(runnable, aStatus, NS_LITERAL_STRING("RemoveDevice"));
+  }
+
+private:
+  PRUint32 mRunnableIndex;
+};
+
 nsresult
 BluetoothServiceBluedroid::RemoveDeviceInternal(
   const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
 
   bt_bdaddr_t remoteAddress;
   StringToBdAddressType(aDeviceAddress, &remoteAddress);
 
-  int ret = sBtInterface->RemoveBond(&remoteAddress);
-  if (ret != BT_STATUS_SUCCESS) {
-    ReplyStatusError(aRunnable, ret,
-                     NS_LITERAL_STRING("RemoveDevice"));
-  } else {
-    sUnbondingRunnableArray.AppendElement(aRunnable);
-  }
+  PRUint32 i = sUnbondingRunnableArray.Length();
+  sUnbondingRunnableArray.AppendElement(aRunnable);
+
+  sBtInterface->RemoveBond(&remoteAddress, new RemoveBondResultHandler(i));
 
   return NS_OK;
 }
 
+class PinReplyResultHandler MOZ_FINAL : public BluetoothResultHandler
+{
+public:
+  PinReplyResultHandler(BluetoothReplyRunnable* aRunnable)
+  : mRunnable(aRunnable)
+  { }
+
+  void PinReply() MOZ_OVERRIDE
+  {
+    DispatchBluetoothReply(mRunnable, BluetoothValue(true), EmptyString());
+  }
+
+  void OnError(int aStatus) MOZ_OVERRIDE
+  {
+    ReplyStatusError(mRunnable, aStatus, NS_LITERAL_STRING("SetPinCode"));
+  }
+
+private:
+  BluetoothReplyRunnable* mRunnable;
+};
+
 bool
 BluetoothServiceBluedroid::SetPinCodeInternal(
   const nsAString& aDeviceAddress, const nsAString& aPinCode,
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   ENSURE_BLUETOOTH_IS_READY(aRunnable, false);
 
   bt_bdaddr_t remoteAddress;
   StringToBdAddressType(aDeviceAddress, &remoteAddress);
 
-  int ret = sBtInterface->PinReply(
-      &remoteAddress, true, aPinCode.Length(),
-      (bt_pin_code_t*)NS_ConvertUTF16toUTF8(aPinCode).get());
-
-  if (ret != BT_STATUS_SUCCESS) {
-    ReplyStatusError(aRunnable, ret, NS_LITERAL_STRING("SetPinCode"));
-  } else {
-    DispatchBluetoothReply(aRunnable, BluetoothValue(true), EmptyString());
-  }
+  sBtInterface->PinReply(
+    &remoteAddress, true, aPinCode.Length(),
+    (bt_pin_code_t*)NS_ConvertUTF16toUTF8(aPinCode).get(),
+    new PinReplyResultHandler(aRunnable));
 
   return true;
 }
 
 bool
 BluetoothServiceBluedroid::SetPasskeyInternal(
   const nsAString& aDeviceAddress, uint32_t aPasskey,
   BluetoothReplyRunnable* aRunnable)
 {
   return true;
 }
 
+class SspReplyResultHandler MOZ_FINAL : public BluetoothResultHandler
+{
+public:
+  SspReplyResultHandler(BluetoothReplyRunnable* aRunnable)
+  : mRunnable(aRunnable)
+  { }
+
+  void SspReply() MOZ_OVERRIDE
+  {
+    DispatchBluetoothReply(mRunnable, BluetoothValue(true), EmptyString());
+  }
+
+  void OnError(int aStatus) MOZ_OVERRIDE
+  {
+    ReplyStatusError(mRunnable, aStatus,
+                     NS_LITERAL_STRING("SetPairingConfirmation"));
+  }
+
+private:
+  BluetoothReplyRunnable* mRunnable;
+};
+
 bool
 BluetoothServiceBluedroid::SetPairingConfirmationInternal(
   const nsAString& aDeviceAddress, bool aConfirm,
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   ENSURE_BLUETOOTH_IS_READY(aRunnable, false);
 
   bt_bdaddr_t remoteAddress;
   StringToBdAddressType(aDeviceAddress, &remoteAddress);
 
-  int ret = sBtInterface->SspReply(&remoteAddress, (bt_ssp_variant_t)0,
-                                   aConfirm, 0);
-  if (ret != BT_STATUS_SUCCESS) {
-    ReplyStatusError(aRunnable, ret,
-                     NS_LITERAL_STRING("SetPairingConfirmation"));
-  } else {
-    DispatchBluetoothReply(aRunnable, BluetoothValue(true), EmptyString());
-  }
-
+  sBtInterface->SspReply(&remoteAddress, (bt_ssp_variant_t)0, aConfirm, 0,
+                         new SspReplyResultHandler(aRunnable));
   return true;
 }
 
 bool
 BluetoothServiceBluedroid::SetAuthorizationInternal(
   const nsAString& aDeviceAddress, bool aAllow,
   BluetoothReplyRunnable* aRunnable)
 {
--- a/dom/bluetooth/bluedroid/hfp-fallback/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/bluedroid/hfp-fallback/BluetoothHfpManager.cpp
@@ -141,16 +141,42 @@ BluetoothHfpManager::Init()
   if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) {
     BT_WARNING("Failed to add observers!");
     return false;
   }
 
   return true;
 }
 
+// static
+void
+BluetoothHfpManager::InitHfpInterface()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  /**
+   * TODO:
+   *   Implement InitHfpInterface() for applications that want to create SCO
+   *   link without a HFP connection (e.g., VoIP).
+   */
+}
+
+// static
+void
+BluetoothHfpManager::DeinitHfpInterface()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  /**
+   * TODO:
+   *   Implement DeinitHfpInterface() for applications that want to create SCO
+   *   link without a HFP connection (e.g., VoIP).
+   */
+}
+
 void
 BluetoothHfpManager::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   sInShutdown = true;
   sBluetoothHfpManager = nullptr;
 }
 
--- a/dom/bluetooth/bluedroid/hfp-fallback/BluetoothHfpManager.h
+++ b/dom/bluetooth/bluedroid/hfp-fallback/BluetoothHfpManager.h
@@ -25,16 +25,18 @@ public:
   BT_DECL_HFP_MGR_BASE
   virtual void GetName(nsACString& aName)
   {
     aName.AssignLiteral("Fallback HFP/HSP");
   }
 
   static BluetoothHfpManager* Get();
   virtual ~BluetoothHfpManager() { }
+  static void InitHfpInterface();
+  static void DeinitHfpInterface();
 
   bool ConnectSco();
   bool DisconnectSco();
 
 private:
   BluetoothHfpManager() { }
   bool Init();
   void HandleShutdown();
--- a/dom/bluetooth2/bluedroid/hfp-fallback/BluetoothHfpManager.cpp
+++ b/dom/bluetooth2/bluedroid/hfp-fallback/BluetoothHfpManager.cpp
@@ -141,16 +141,42 @@ BluetoothHfpManager::Init()
   if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) {
     BT_WARNING("Failed to add observers!");
     return false;
   }
 
   return true;
 }
 
+// static
+void
+BluetoothHfpManager::InitHfpInterface()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  /**
+   * TODO:
+   *   Implement InitHfpInterface() for applications that want to create SCO
+   *   link without a HFP connection (e.g., VoIP).
+   */
+}
+
+// static
+void
+BluetoothHfpManager::DeinitHfpInterface()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  /**
+   * TODO:
+   *   Implement DeinitHfpInterface() for applications that want to create SCO
+   *   link without a HFP connection (e.g., VoIP).
+   */
+}
+
 void
 BluetoothHfpManager::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   sInShutdown = true;
   sBluetoothHfpManager = nullptr;
 }
 
--- a/dom/bluetooth2/bluedroid/hfp-fallback/BluetoothHfpManager.h
+++ b/dom/bluetooth2/bluedroid/hfp-fallback/BluetoothHfpManager.h
@@ -25,16 +25,18 @@ public:
   BT_DECL_HFP_MGR_BASE
   virtual void GetName(nsACString& aName)
   {
     aName.AssignLiteral("Fallback HFP/HSP");
   }
 
   static BluetoothHfpManager* Get();
   virtual ~BluetoothHfpManager() { }
+  static void InitHfpInterface();
+  static void DeinitHfpInterface();
 
   bool ConnectSco();
   bool DisconnectSco();
 
 private:
   BluetoothHfpManager() { }
   bool Init();
   void HandleShutdown();
--- a/dom/nfc/tests/marionette/head.js
+++ b/dom/nfc/tests/marionette/head.js
@@ -1,15 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+const Cu = SpecialPowers.Cu;
+
 let pendingEmulatorCmdCount = 0;
 
-let Promise =
-  SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise;
+let Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise;
 let nfc = window.navigator.mozNfc;
 
 SpecialPowers.addPermission("nfc-manager", true, document);
 
 /**
  * Emulator helper.
  */
 let emulator = (function() {
@@ -183,18 +184,18 @@ const NDEF = {
     isnot(ndef2, null, "RHS message is not null");
     is(ndef1.length, ndef2.length,
        "NDEF messages have the same number of records");
     ndef1.forEach(function(record1, index) {
       let record2 = this[index];
       is(record1.tnf, record2.tnf, "test for equal TNF fields");
       let fields = ["type", "id", "payload"];
       fields.forEach(function(value) {
-        let field1 = record1[value];
-        let field2 = record2[value];
+        let field1 = Cu.waiveXrays(record1)[value];
+        let field2 = Cu.waiveXrays(record2)[value];
         is(field1.length, field2.length,
            value + " fields have the same length");
         let eq = true;
         for (let i = 0; eq && i < field1.length; ++i) {
           eq = (field1[i] === field2[i]);
         }
         ok(eq, value + " fields contain the same data");
       });
--- a/dom/nfc/tests/marionette/test_nfc_tag.js
+++ b/dom/nfc/tests/marionette/test_nfc_tag.js
@@ -21,17 +21,17 @@ function testUrlTagDiscover(re) {
   let payload = url;
 
   window.navigator.mozSetMessageHandler("nfc-manager-tech-discovered", function(msg) {
     log("Received \'nfc-manager-tech-ndiscovered\'");
     is(msg.type, "techDiscovered", "check for correct message type");
     let index = msg.techList.indexOf("NDEF");
     isnot(index, -1, "check for \'NDEF\' in tech list");
 
-    let records = msg.records;
+    let records = Cu.waiveXrays(msg.records);
     ok(records.length > 0);
 
     is(tnf, records[0].tnf, "check for TNF field in NDEF");
     is(type, NfcUtils.toUTF8(records[0].type), "check for type field in NDEF");
     is(payload, NfcUtils.toUTF8(records[0].payload), "check for payload field in NDEF");
 
     toggleNFC(false).then(runNextTest);
   });
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -2090,18 +2090,16 @@ public class GeckoAppShell
             return null;
         }
     }
 
     private static ContextGetter sContextGetter;
 
     @WrapElementForJNI(allowMultithread = true)
     public static Context getContext() {
-        if (sContextGetter == null)
-            return null;
         return sContextGetter.getContext();
     }
 
     public static void setContextGetter(ContextGetter cg) {
         sContextGetter = cg;
     }
 
     public static SharedPreferences getSharedPreferences() {
--- a/mobile/android/base/GeckoApplication.java
+++ b/mobile/android/base/GeckoApplication.java
@@ -97,18 +97,16 @@ public class GeckoApplication extends Ap
                 }
             });
         }
         GeckoConnectivityReceiver.getInstance().stop();
         GeckoNetworkManager.getInstance().stop();
     }
 
     public void onActivityResume(GeckoActivityStatus activity) {
-	GeckoAppShell.setContextGetter(this);
-
         if (mPausedGecko) {
             GeckoAppShell.sendEventToGecko(GeckoEvent.createAppForegroundingEvent());
             mPausedGecko = false;
         }
 
         final Context applicationContext = getApplicationContext();
         GeckoBatteryManager.getInstance().start(applicationContext);
         GeckoConnectivityReceiver.getInstance().start(applicationContext);
--- a/mobile/android/base/home/HomeConfig.java
+++ b/mobile/android/base/home/HomeConfig.java
@@ -101,24 +101,24 @@ public final class HomeConfig {
         private final PanelType mType;
         private final String mTitle;
         private final String mId;
         private final LayoutType mLayoutType;
         private final List<ViewConfig> mViews;
         private final AuthConfig mAuthConfig;
         private final EnumSet<Flags> mFlags;
 
-        private static final String JSON_KEY_TYPE = "type";
-        private static final String JSON_KEY_TITLE = "title";
-        private static final String JSON_KEY_ID = "id";
-        private static final String JSON_KEY_LAYOUT = "layout";
-        private static final String JSON_KEY_VIEWS = "views";
-        private static final String JSON_KEY_AUTH_CONFIG = "authConfig";
-        private static final String JSON_KEY_DEFAULT = "default";
-        private static final String JSON_KEY_DISABLED = "disabled";
+        static final String JSON_KEY_TYPE = "type";
+        static final String JSON_KEY_TITLE = "title";
+        static final String JSON_KEY_ID = "id";
+        static final String JSON_KEY_LAYOUT = "layout";
+        static final String JSON_KEY_VIEWS = "views";
+        static final String JSON_KEY_AUTH_CONFIG = "authConfig";
+        static final String JSON_KEY_DEFAULT = "default";
+        static final String JSON_KEY_DISABLED = "disabled";
 
         public enum Flags {
             DEFAULT_PANEL,
             DISABLED_PANEL
         }
 
         public PanelConfig(JSONObject json) throws JSONException, IllegalArgumentException {
             final String panelType = json.optString(JSON_KEY_TYPE, null);
@@ -611,24 +611,24 @@ public final class HomeConfig {
         private final String mDatasetId;
         private final ItemType mItemType;
         private final ItemHandler mItemHandler;
         private final String mBackImageUrl;
         private final String mFilter;
         private final EmptyViewConfig mEmptyViewConfig;
         private final EnumSet<Flags> mFlags;
 
-        private static final String JSON_KEY_TYPE = "type";
-        private static final String JSON_KEY_DATASET = "dataset";
-        private static final String JSON_KEY_ITEM_TYPE = "itemType";
-        private static final String JSON_KEY_ITEM_HANDLER = "itemHandler";
-        private static final String JSON_KEY_BACK_IMAGE_URL = "backImageUrl";
-        private static final String JSON_KEY_FILTER = "filter";
-        private static final String JSON_KEY_EMPTY = "empty";
-        private static final String JSON_KEY_REFRESH_ENABLED = "refreshEnabled";
+        static final String JSON_KEY_TYPE = "type";
+        static final String JSON_KEY_DATASET = "dataset";
+        static final String JSON_KEY_ITEM_TYPE = "itemType";
+        static final String JSON_KEY_ITEM_HANDLER = "itemHandler";
+        static final String JSON_KEY_BACK_IMAGE_URL = "backImageUrl";
+        static final String JSON_KEY_FILTER = "filter";
+        static final String JSON_KEY_EMPTY = "empty";
+        static final String JSON_KEY_REFRESH_ENABLED = "refreshEnabled";
 
         public enum Flags {
             REFRESH_ENABLED
         }
 
         public ViewConfig(int index, JSONObject json) throws JSONException, IllegalArgumentException {
             mIndex = index;
             mType = ViewType.fromId(json.getString(JSON_KEY_TYPE));
@@ -813,18 +813,18 @@ public final class HomeConfig {
             }
         };
     }
 
     public static class EmptyViewConfig implements Parcelable {
         private final String mText;
         private final String mImageUrl;
 
-        private static final String JSON_KEY_TEXT = "text";
-        private static final String JSON_KEY_IMAGE_URL = "imageUrl";
+        static final String JSON_KEY_TEXT = "text";
+        static final String JSON_KEY_IMAGE_URL = "imageUrl";
 
         public EmptyViewConfig(JSONObject json) throws JSONException, IllegalArgumentException {
             mText = json.optString(JSON_KEY_TEXT, null);
             mImageUrl = json.optString(JSON_KEY_IMAGE_URL, null);
         }
 
         @SuppressWarnings("unchecked")
         public EmptyViewConfig(Parcel in) {
@@ -883,19 +883,19 @@ public final class HomeConfig {
         };
     }
 
     public static class AuthConfig implements Parcelable {
         private final String mMessageText;
         private final String mButtonText;
         private final String mImageUrl;
 
-        private static final String JSON_KEY_MESSAGE_TEXT = "messageText";
-        private static final String JSON_KEY_BUTTON_TEXT = "buttonText";
-        private static final String JSON_KEY_IMAGE_URL = "imageUrl";
+        static final String JSON_KEY_MESSAGE_TEXT = "messageText";
+        static final String JSON_KEY_BUTTON_TEXT = "buttonText";
+        static final String JSON_KEY_IMAGE_URL = "imageUrl";
 
         public AuthConfig(JSONObject json) throws JSONException, IllegalArgumentException {
             mMessageText = json.optString(JSON_KEY_MESSAGE_TEXT);
             mButtonText = json.optString(JSON_KEY_BUTTON_TEXT);
             mImageUrl = json.optString(JSON_KEY_IMAGE_URL, null);
         }
 
         @SuppressWarnings("unchecked")
--- a/mobile/android/base/home/HomeConfigPrefsBackend.java
+++ b/mobile/android/base/home/HomeConfigPrefsBackend.java
@@ -93,16 +93,32 @@ class HomeConfigPrefsBackend implements 
             panelConfigs.add(0, historyEntry);
             panelConfigs.add(0, recentTabsEntry);
         }
 
         return new State(panelConfigs, true);
     }
 
     /**
+     * Iterate through the panels to check if they are all disabled.
+     */
+    private static boolean allPanelsAreDisabled(JSONArray jsonPanels) throws JSONException {
+        final int count = jsonPanels.length();
+        for (int i = 0; i < count; i++) {
+            final JSONObject jsonPanelConfig = jsonPanels.getJSONObject(i);
+
+            if (!jsonPanelConfig.optBoolean(PanelConfig.JSON_KEY_DISABLED, false)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
      * Migrates JSON config data storage.
      *
      * @param context Context used to get shared preferences and create built-in panel.
      * @param jsonString String currently stored in preferences.
      *
      * @return JSONArray array representing new set of panel configs.
      */
     private static synchronized JSONArray maybePerformMigration(Context context, String jsonString) throws JSONException {
@@ -139,18 +155,23 @@ class HomeConfigPrefsBackend implements 
         final SharedPreferences.Editor prefsEditor = prefs.edit();
 
         for (int v = version + 1; v <= VERSION; v++) {
             Log.d(LOGTAG, "Migrating to version = " + v);
 
             switch (v) {
                 case 1:
                     // Add "Recent Tabs" panel
-                    final PanelConfig recentTabsConfig = createBuiltinPanelConfig(context, PanelType.RECENT_TABS);
-                    final JSONObject jsonRecentTabsConfig = recentTabsConfig.toJSON();
+                    final JSONObject jsonRecentTabsConfig =
+                            createBuiltinPanelConfig(context, PanelType.RECENT_TABS).toJSON();
+
+                    // If any panel is enabled, then we should make the recent tabs
+                    // panel enabled.
+                    jsonRecentTabsConfig.put(PanelConfig.JSON_KEY_DISABLED,
+                                             allPanelsAreDisabled(originalJsonPanels));
 
                     // Add the new panel to the front of the array on phones.
                     if (!HardwareUtils.isTablet()) {
                         newJsonPanels.put(jsonRecentTabsConfig);
                     }
 
                     // Copy the original panel configs.
                     final int count = originalJsonPanels.length();
--- a/mobile/android/base/resources/layout-large-land-v11/tabs_panel.xml
+++ b/mobile/android/base/resources/layout-large-land-v11/tabs_panel.xml
@@ -41,21 +41,20 @@
                                               gecko:tabs="tabs_normal"/>
 
         <org.mozilla.gecko.tabspanel.PrivateTabsPanel
                 android:id="@+id/private_tabs_panel"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:visibility="gone"/>
 
-        <org.mozilla.gecko.tabspanel.RemoteTabsPanel
-                android:id="@+id/remote_tabs"
-                android:layout_height="match_parent"
-                android:layout_width="match_parent"
-                android:visibility="gone"/>
+        <ViewStub android:id="@+id/remote_tabs_panel_stub"
+                  android:layout="@layout/remote_tabs_panel_view"
+                  android:layout_width="match_parent"
+                  android:layout_height="match_parent"/>
 
     </view>
 
     <RelativeLayout android:id="@+id/tabs_panel_footer"
                     android:layout_width="match_parent"
                     android:layout_height="@dimen/browser_toolbar_height">
 
         <view class="org.mozilla.gecko.tabspanel.TabsPanel$TabsPanelToolbar"
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/remote_tabs_panel_view.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<org.mozilla.gecko.tabspanel.RemoteTabsPanel xmlns:android="http://schemas.android.com/apk/res/android"
+                                             android:id="@+id/remote_tabs_panel"
+                                             android:layout_width="match_parent"
+                                             android:layout_height="match_parent"/>
--- a/mobile/android/base/resources/layout/tabs_panel.xml
+++ b/mobile/android/base/resources/layout/tabs_panel.xml
@@ -40,17 +40,16 @@
                                               gecko:tabs="tabs_normal"/>
 
         <org.mozilla.gecko.tabspanel.PrivateTabsPanel
                 android:id="@+id/private_tabs_panel"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:visibility="gone"/>
 
-        <org.mozilla.gecko.tabspanel.RemoteTabsPanel
-                android:id="@+id/remote_tabs"
-                android:layout_height="match_parent"
-                android:layout_width="match_parent"
-                android:visibility="gone"/>
 
+        <ViewStub android:id="@+id/remote_tabs_panel_stub"
+                  android:layout="@layout/remote_tabs_panel_view"
+                  android:layout_width="match_parent"
+                  android:layout_height="match_parent"/>
     </view>
 
 </merge>
--- a/mobile/android/base/tabspanel/TabsPanel.java
+++ b/mobile/android/base/tabspanel/TabsPanel.java
@@ -26,16 +26,17 @@ import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
+import android.view.ViewStub;
 import android.widget.Button;
 import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
 
 public class TabsPanel extends LinearLayout
                        implements GeckoPopupMenu.OnMenuItemClickListener,
@@ -112,16 +113,17 @@ public class TabsPanel extends LinearLay
         LayoutInflater.from(context).inflate(R.layout.tabs_panel, this);
         initialize();
 
         mAppStateListener = new AppStateListener() {
             @Override
             public void onResume() {
                 if (mPanel == mPanelRemote) {
                     // Refresh the remote panel.
+                    initializeRemotePanelView();
                     mPanelRemote.show();
                 }
             }
 
             @Override
             public void onOrientationChanged() {
                 // Remote panel is already refreshed by chrome refresh.
             }
@@ -136,19 +138,16 @@ public class TabsPanel extends LinearLay
         mTabsContainer = (TabsListContainer) findViewById(R.id.tabs_container);
 
         mPanelNormal = (PanelView) findViewById(R.id.normal_tabs);
         mPanelNormal.setTabsPanel(this);
 
         mPanelPrivate = (PanelView) findViewById(R.id.private_tabs_panel);
         mPanelPrivate.setTabsPanel(this);
 
-        mPanelRemote = (PanelView) findViewById(R.id.remote_tabs);
-        mPanelRemote.setTabsPanel(this);
-
         mFooter = (RelativeLayout) findViewById(R.id.tabs_panel_footer);
 
         mAddTab = (ImageButton) findViewById(R.id.add_tab);
         mAddTab.setOnClickListener(new Button.OnClickListener() {
             @Override
             public void onClick(View v) {
                 TabsPanel.this.addTab();
             }
@@ -402,16 +401,17 @@ public class TabsPanel extends LinearLay
         switch (panelToShow) {
             case NORMAL_TABS:
                 mPanel = mPanelNormal;
                 break;
             case PRIVATE_TABS:
                 mPanel = mPanelPrivate;
                 break;
             case REMOTE_TABS:
+                initializeRemotePanelView();
                 mPanel = mPanelRemote;
                 break;
 
             default:
                 throw new IllegalArgumentException("Unknown panel type " + panelToShow);
         }
         mPanel.show();
 
@@ -550,9 +550,16 @@ public class TabsPanel extends LinearLay
      */
     public Drawable getIconDrawable(Panel panel) {
         return mTabWidget.getIconDrawable(panel.ordinal());
     }
 
     public void setIconDrawable(Panel panel, int resource) {
         mTabWidget.setIconDrawable(panel.ordinal(), resource);
     }
+
+    private void initializeRemotePanelView() {
+        if (mPanelRemote == null) {
+            mPanelRemote = (PanelView) ((ViewStub) findViewById(R.id.remote_tabs_panel_stub)).inflate();
+            mPanelRemote.setTabsPanel(TabsPanel.this);
+        }
+    }
 }
--- a/netwerk/wifi/nsWifiMonitor.h
+++ b/netwerk/wifi/nsWifiMonitor.h
@@ -88,15 +88,16 @@ class nsWifiMonitor MOZ_FINAL : nsIWifiM
   ~nsWifiMonitor();
 
   void ClearTimer() {
     if (mTimer) {
       mTimer->Cancel();
       mTimer = nullptr;
     }
   }
+  void StartScan();
   nsCOMArray<nsWifiAccessPoint> mLastAccessPoints;
   nsTArray<nsWifiListener> mListeners;
   nsCOMPtr<nsITimer> mTimer;
 };
 #endif
 
 #endif
--- a/netwerk/wifi/nsWifiMonitorGonk.cpp
+++ b/netwerk/wifi/nsWifiMonitorGonk.cpp
@@ -58,16 +58,17 @@ nsWifiMonitor::StartWatching(nsIWifiList
   }
 
   mListeners.AppendElement(nsWifiListener(new nsMainThreadPtrHolder<nsIWifiListener>(aListener)));
 
   if (!mTimer) {
     mTimer = do_CreateInstance("@mozilla.org/timer;1");
     mTimer->Init(this, 5000, nsITimer::TYPE_REPEATING_SLACK);
   }
+  StartScan();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWifiMonitor::StopWatching(nsIWifiListener *aListener)
 {
   LOG(("@@@@@ nsWifiMonitor::StopWatching\n"));
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
@@ -83,30 +84,34 @@ nsWifiMonitor::StopWatching(nsIWifiListe
   }
 
   if (mListeners.Length() == 0) {
     ClearTimer();
   }
   return NS_OK;
 }
 
+void
+nsWifiMonitor::StartScan()
+{
+  nsCOMPtr<nsIInterfaceRequestor> ir = do_GetService("@mozilla.org/telephony/system-worker-manager;1");
+  nsCOMPtr<nsIWifi> wifi = do_GetInterface(ir);
+  if (!wifi) {
+    return;
+  }
+  wifi->GetWifiScanResults(this);
+}
+
 NS_IMETHODIMP
 nsWifiMonitor::Observe(nsISupports *subject, const char *topic,
                        const char16_t *data)
 {
   if (!strcmp(topic, "timer-callback")) {
     LOG(("timer callback\n"));
-
-    nsCOMPtr<nsIInterfaceRequestor> ir = do_GetService("@mozilla.org/telephony/system-worker-manager;1");
-    nsCOMPtr<nsIWifi> wifi = do_GetInterface(ir);
-    if (!wifi) {
-      return NS_ERROR_UNEXPECTED;
-    }
-
-    wifi->GetWifiScanResults(this);
+    StartScan();
     return NS_OK;
   }
 
   if (!strcmp(topic, "xpcom-shutdown")) {
     LOG(("Shutting down\n"));
     ClearTimer();
     return NS_OK;
   }
--- a/services/mobileid/MobileIdentityClient.jsm
+++ b/services/mobileid/MobileIdentityClient.jsm
@@ -16,19 +16,18 @@ Cu.import("resource://services-common/ha
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-crypto/utils.js");
 Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 this.MobileIdentityClient = function(aServerUrl) {
   let serverUrl = aServerUrl || SERVER_URL;
-  let forceHttps = false;
+  let forceHttps = true;
   try {
-    // TODO: Force https in production. Bug 1021595.
     forceHttps = Services.prefs.getBoolPref(PREF_FORCE_HTTPS);
   } catch(e) {
     log.warn("Getting force HTTPS pref failed. If this was not intentional " +
              "check that " + PREF_FORCE_HTTPS + " is defined");
   }
 
   log.debug("Force HTTPS " + forceHttps);
 
--- a/testing/marionette/client/marionette/geckoinstance.py
+++ b/testing/marionette/client/marionette/geckoinstance.py
@@ -74,16 +74,19 @@ class GeckoInstance(object):
         if self.runner:
             self.runner.stop()
             self.runner.cleanup()
 
 
 class B2GDesktopInstance(GeckoInstance):
     required_prefs = {"focusmanager.testmode": True}
 
+    def __init__(self, **kwargs):
+        super(B2GDesktopInstance, self).__init__(**kwargs)
+        self.app_args += ['-chrome', 'chrome://b2g/content/shell.html']
 
 class NullOutput(object):
     def __call__(self, line):
         pass
 
 
 apps = {'b2g': B2GDesktopInstance,
         'b2gdesktop': B2GDesktopInstance}
--- a/toolkit/content/customizeToolbar.js
+++ b/toolkit/content/customizeToolbar.js
@@ -4,16 +4,18 @@
 
 var gToolboxDocument = null;
 var gToolbox = null;
 var gCurrentDragOverItem = null;
 var gToolboxChanged = false;
 var gToolboxSheet = false;
 var gPaletteBox = null;
 
+Components.utils.import("resource://gre/modules/Services.jsm");
+
 function onLoad()
 {
   if ("arguments" in window && window.arguments[0]) {
     InitWithToolbox(window.arguments[0]);
     repositionDialog(window);
   }
   else if (window.frameElement &&
            "toolbox" in window.frameElement) {
@@ -452,19 +454,17 @@ function setDragActive(aItem, aValue)
       node.setAttribute("dragover", value);
   } else {
     node.removeAttribute("dragover");
   }
 }
 
 function addNewToolbar()
 {
-  var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
-                                .getService(Components.interfaces.nsIPromptService);
-
+  var promptService = Services.prompt;
   var stringBundle = document.getElementById("stringBundle");
   var message = stringBundle.getString("enterToolbarName");
   var title = stringBundle.getString("enterToolbarTitle");
 
   var name = {};
 
   // Quitting from the toolbar dialog while the new toolbar prompt is up
   // can cause things to become unresponsive on the Mac. Until dialog modality
@@ -814,16 +814,22 @@ function onPaletteDrop(aEvent)
     wrapper.parentNode.removeChild(wrapper);
   }
 
   toolboxChanged();
 }
 
 
 function isUnwantedDragEvent(aEvent) {
+  try {
+    if (Services.prefs.getBoolPref("toolkit.customization.unsafe_drag_events")) {
+      return false;
+    }
+  } catch (ex) {}
+
   /* Discard drag events that originated from a separate window to
      prevent content->chrome privilege escalations. */
   let mozSourceNode = aEvent.dataTransfer.mozSourceNode;
   // mozSourceNode is null in the dragStart event handler or if
   // the drag event originated in an external application.
   if (!mozSourceNode) {
     return true;
   }
--- a/toolkit/content/license.html
+++ b/toolkit/content/license.html
@@ -71,16 +71,17 @@
       <li><a href="about:license#apple">Apple License</a></li>
       <li><a href="about:license#apple-mozilla">Apple/Mozilla NPRuntime License</a></li>
       <li><a href="about:license#apple-torch">Apple/Torch Mobile License</a></li>
       <li><a href="about:license#backbone">Backbone License</a></li>
       <li><a href="about:license#bspatch">bspatch License</a></li>
       <li><a href="about:license#cairo">Cairo Component Licenses</a></li>
       <li><a href="about:license#chromium">Chromium License</a></li>
       <li><a href="about:license#codemirror">CodeMirror License</a></li>
+      <li><a href="about:license#cubic-bezier">cubic-bezier License</a></li>
       <li><a href="about:license#dtoa">dtoa License</a></li>
       <li><a href="about:license#hunspell-nl">Dutch Spellchecking Dictionary License</a></li>
       <li><a href="about:license#edl">Eclipse Distribution License</a></li>
       <li><a href="about:license#escodegen">Escodegen License</a></li>
       <li><a href="about:license#hunspell-ee">Estonian Spellchecking Dictionary License</a></li>
       <li><a href="about:license#expat">Expat License</a></li>
       <li><a href="about:license#firebug">Firebug License</a></li>
       <li><a href="about:license#gfx-font-list">gfxFontList License</a></li>
@@ -2023,16 +2024,46 @@ THE SOFTWARE.
 Please note that some subdirectories of the CodeMirror distribution
 include their own LICENSE files, and are released under different
 licences.
 </pre>
 
 
     <hr>
 
+    <h1><a id="cubic-bezier"></a>cubic-bezier License</h1>
+
+    <p>This license applies to the file
+    <span class="path">browser/devtools/shared/widgets/CubicBezierWidget.js
+    </span>.</p>
+<pre>
+Copyright (c) 2013 Lea Verou. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+</pre>
+
+
+    <hr>
+
     <h1><a id="dtoa"></a>dtoa License</h1>
 
     <p>This license applies to the file
     <span class="path">nsprpub/pr/src/misc/dtoa.c</span>.</p>
 
 <pre>
 The author of this software is David M. Gay.
 
--- a/toolkit/devtools/gcli/source/lib/gcli/cli.js
+++ b/toolkit/devtools/gcli/source/lib/gcli/cli.js
@@ -487,16 +487,20 @@ function Requisition(options) {
   this._nextUpdateId = 0;
 
   // We can set a prefix to typed commands to make it easier to focus on
   // Allowing us to type "add -a; commit" in place of "git add -a; git commit"
   this.prefix = '';
 
   addMapping(this);
   this._setBlankAssignment(this.commandAssignment);
+
+  // If a command calls context.update then the UI needs some way to be
+  // informed of the change
+  this.onExternalUpdate = util.createEvent('Requisition.onExternalUpdate');
 }
 
 /**
  * Avoid memory leaks
  */
 Requisition.prototype.destroy = function() {
   this.document = undefined;
   this.environment = undefined;
@@ -579,18 +583,18 @@ Object.defineProperty(Requisition.protot
       Object.defineProperty(this._executionContext, 'shell', {
         get: function() { return requisition.shell; },
         enumerable : true
       });
 
       if (legacy) {
         this._executionContext.createView = view.createView;
         this._executionContext.exec = this.exec.bind(this);
-        this._executionContext.update = this.update.bind(this);
-        this._executionContext.updateExec = this.updateExec.bind(this);
+        this._executionContext.update = this._contextUpdate.bind(this);
+        this._executionContext.updateExec = this._contextUpdateExec.bind(this);
 
         Object.defineProperty(this._executionContext, 'document', {
           get: function() { return requisition.document; },
           enumerable: true
         });
       }
     }
 
@@ -607,18 +611,18 @@ Object.defineProperty(Requisition.protot
     if (this._conversionContext == null) {
       this._conversionContext = {
         defer: function() {
           return Promise.defer();
         },
 
         createView: view.createView,
         exec: this.exec.bind(this),
-        update: this.update.bind(this),
-        updateExec: this.updateExec.bind(this)
+        update: this._contextUpdate.bind(this),
+        updateExec: this._contextUpdateExec.bind(this)
       };
 
       // Alias requisition so we're clear about what's what
       var requisition = this;
 
       Object.defineProperty(this._conversionContext, 'document', {
         get: function() { return requisition.document; },
         enumerable: true
@@ -762,26 +766,45 @@ Requisition.prototype._getFirstBlankPosi
       return true; // i.e. break
     }
     return false;
   }, this);
   return reply;
 };
 
 /**
+ * The update process is asynchronous, so there is (unavoidably) a window
+ * where we've worked out the command but don't yet understand all the params.
+ * If we try to do things to a requisition in this window we may get
+ * inconsistent results. Asynchronous promises have made the window bigger.
+ * The only time we've seen this in practice is during focus events due to
+ * clicking on a shortcut. The focus want to check the cursor position while
+ * the shortcut is updating the command line.
+ * This function allows us to detect and back out of this problem.
+ * We should be able to remove this function when all the state in a
+ * requisition can be encapsulated and updated atomically.
+ */
+Requisition.prototype.isUpToDate = function() {
+  if (!this._args) {
+    return false;
+  }
+  for (var i = 0; i < this._args.length; i++) {
+    if (this._args[i].assignment == null) {
+      return false;
+    }
+  }
+  return true;
+};
+
+/**
  * Look through the arguments attached to our assignments for the assignment
  * at the given position.
  * @param {number} cursor The cursor position to query
  */
 Requisition.prototype.getAssignmentAt = function(cursor) {
-  if (!this._args) {
-    console.trace();
-    throw new Error('Missing args');
-  }
-
   // We short circuit this one because we may have no args, or no args with
   // any size and the alg below only finds arguments with size.
   if (cursor === 0) {
     return this.commandAssignment;
   }
 
   var assignForPos = [];
   var i, j;
@@ -817,24 +840,17 @@ Requisition.prototype.getAssignmentAt = 
     for (j = 0; j < arg.suffix.length; j++) {
       assignForPos.push(assignment);
     }
   }
 
   // Possible shortcut, we don't really need to go through all the args
   // to work out the solution to this
 
-  var reply = assignForPos[cursor - 1];
-
-  if (!reply) {
-    throw new Error('Missing assignment.' +
-        ' cursor=' + cursor + ' text=' + this.toString());
-  }
-
-  return reply;
+  return assignForPos[cursor - 1];
 };
 
 /**
  * Extract a canonical version of the input
  * @return a promise of a string which is the canonical version of what was
  * typed
  */
 Requisition.prototype.toCanonicalString = function() {
@@ -1474,24 +1490,40 @@ function getDataCommandAttribute(element
   if (!command) {
     command = element.querySelector('*[data-command]')
                      .getAttribute('data-command');
   }
   return command;
 }
 
 /**
+ * Designed to be called from context.update(). Acts just like update() except
+ * that it also calls onExternalUpdate() to inform the UI of an unexpected
+ * change to the current command.
+ */
+Requisition.prototype._contextUpdate = function(typed) {
+  return this.update(typed).then(function(reply) {
+    this.onExternalUpdate({ typed: typed });
+    return reply;
+  }.bind(this));
+};
+
+/**
  * Called by the UI when ever the user interacts with a command line input
- * @param typed The contents of the input field
+ * @param typed The contents of the input field OR an HTML element (or an event
+ * that targets an HTML element) which has a data-command attribute or a child
+ * with the same that contains the command to update with
  */
 Requisition.prototype.update = function(typed) {
-  if (typeof HTMLElement !== 'undefined' && typed instanceof HTMLElement) {
+  // Should be "if (typed instanceof HTMLElement)" except Gecko
+  if (typeof typed.querySelector === 'function') {
     typed = getDataCommandAttribute(typed);
   }
-  if (typeof Event !== 'undefined' && typed instanceof Event) {
+  // Should be "if (typed instanceof Event)" except Gecko
+  if (typeof typed.currentTarget === 'object') {
     typed = getDataCommandAttribute(typed.currentTarget);
   }
 
   var updateId = this._beginChange();
 
   this._args = exports.tokenize(typed);
   var args = this._args.slice(0); // i.e. clone
 
@@ -2064,16 +2096,28 @@ Requisition.prototype.exec = function(op
     }
     finally {
       this.clear();
     }
   }
 };
 
 /**
+ * Designed to be called from context.updateExec(). Acts just like updateExec()
+ * except that it also calls onExternalUpdate() to inform the UI of an
+ * unexpected change to the current command.
+ */
+Requisition.prototype._contextUpdateExec = function(typed, options) {
+  return this.updateExec(typed, options).then(function(reply) {
+    this.onExternalUpdate({ typed: typed });
+    return reply;
+  }.bind(this));
+};
+
+/**
  * A shortcut for calling update, resolving the promise and then exec.
  * @param input The string to execute
  * @param options Passed to exec
  * @return A promise of an output object
  */
 Requisition.prototype.updateExec = function(input, options) {
   return this.update(input).then(function() {
     return this.exec(options);
--- a/toolkit/devtools/gcli/source/lib/gcli/languages/command.js
+++ b/toolkit/devtools/gcli/source/lib/gcli/languages/command.js
@@ -101,25 +101,28 @@ var commandLanguage = exports.commandLan
 
     return commandHtmlPromise.then(function(commandHtml) {
       this.commandDom = host.toDom(this.document, commandHtml);
 
       this.requisition.commandOutputManager.onOutput.add(this.outputted, this);
       var mapping = cli.getMapping(this.requisition.executionContext);
       mapping.terminal = this.terminal;
 
+      this.requisition.onExternalUpdate.add(this.textChanged, this);
+
       return this;
     }.bind(this));
   },
 
   destroy: function() {
     var mapping = cli.getMapping(this.requisition.executionContext);
     delete mapping.terminal;
 
     this.requisition.commandOutputManager.onOutput.remove(this.outputted, this);
+    this.requisition.onExternalUpdate.remove(this.textChanged, this);
 
     this.terminal = undefined;
     this.requisition = undefined;
     this.commandDom = undefined;
   },
 
   // From the requisition.textChanged event
   textChanged: function() {
@@ -158,17 +161,24 @@ var commandLanguage = exports.commandLan
     this.terminal.field.update();
     this.terminal.field.setConversion(this.assignment.conversion);
     util.setTextContent(this.terminal.descriptionEle, this.description);
   },
 
   // Called internally whenever we think that the current assignment might
   // have changed, typically on mouse-clicks or key presses.
   caretMoved: function(start) {
+    if (!this.requisition.isUpToDate()) {
+      return;
+    }
     var newAssignment = this.requisition.getAssignmentAt(start);
+    if (newAssignment == null) {
+      return;
+    }
+
     if (this.assignment !== newAssignment) {
       if (this.assignment.param.type.onLeave) {
         this.assignment.param.type.onLeave(this.assignment);
       }
 
       // This can be kicked off either by requisition doing an assign or by
       // terminal noticing a cursor movement out of a command, so we should
       // check that this really is a new assignment
--- a/toolkit/devtools/gcli/source/lib/gcli/mozui/inputter.js
+++ b/toolkit/devtools/gcli/source/lib/gcli/mozui/inputter.js
@@ -82,28 +82,30 @@ function Inputter(options, components) {
 
   this.assignment = this.requisition.getAssignmentAt(0);
   this.onAssignmentChange = util.createEvent('Inputter.onAssignmentChange');
   this.onInputChange = util.createEvent('Inputter.onInputChange');
 
   this.onResize = util.createEvent('Inputter.onResize');
   this.onWindowResize = this.onWindowResize.bind(this);
   this.document.defaultView.addEventListener('resize', this.onWindowResize, false);
+  this.requisition.onExternalUpdate.add(this.textChanged, this);
 
   this._previousValue = undefined;
   this.requisition.update(this.element.value || '');
 }
 
 /**
  * Avoid memory leaks
  */
 Inputter.prototype.destroy = function() {
   this.document.defaultView.removeEventListener('resize', this.onWindowResize, false);
 
   this.requisition.commandOutputManager.onOutput.remove(this.outputted, this);
+  this.requisition.onExternalUpdate.remove(this.textChanged, this);
   if (this.focusManager) {
     this.focusManager.removeMonitoredElement(this.element, 'input');
   }
 
   this.element.removeEventListener('mouseup', this.onMouseUp, false);
   this.element.removeEventListener('keydown', this.onKeyDown, false);
   this.element.removeEventListener('keyup', this.onKeyUp, false);
 
@@ -304,17 +306,23 @@ Inputter.prototype._processCaretChange =
  * @param start Optional - if specified, the cursor position to use in working
  * out the current assignment. This is needed because setting the element
  * selection start is only recognised when the event loop has finished
  */
 Inputter.prototype._checkAssignment = function(start) {
   if (start == null) {
     start = this.element.selectionStart;
   }
+  if (!this.requisition.isUpToDate()) {
+    return;
+  }
   var newAssignment = this.requisition.getAssignmentAt(start);
+  if (newAssignment == null) {
+    return;
+  }
   if (this.assignment !== newAssignment) {
     if (this.assignment.param.type.onLeave) {
       this.assignment.param.type.onLeave(this.assignment);
     }
 
     this.assignment = newAssignment;
     this.onAssignmentChange({ assignment: this.assignment });
 
--- a/toolkit/devtools/server/actors/call-watcher.js
+++ b/toolkit/devtools/server/actors/call-watcher.js
@@ -271,26 +271,27 @@ let CallWatcherActor = exports.CallWatch
     this.finalize();
   },
 
   /**
    * Starts waiting for the current tab actor's document global to be
    * created, in order to instrument the specified objects and become
    * aware of everything the content does with them.
    */
-  setup: method(function({ tracedGlobals, tracedFunctions, startRecording, performReload, holdWeak }) {
+  setup: method(function({ tracedGlobals, tracedFunctions, startRecording, performReload, holdWeak, storeCalls }) {
     if (this._initialized) {
       return;
     }
     this._initialized = true;
 
     this._functionCalls = [];
     this._tracedGlobals = tracedGlobals || [];
     this._tracedFunctions = tracedFunctions || [];
     this._holdWeak = !!holdWeak;
+    this._storeCalls = !!storeCalls;
     this._contentObserver = new ContentObserver(this.tabActor);
 
     on(this._contentObserver, "global-created", this._onGlobalCreated);
     on(this._contentObserver, "global-destroyed", this._onGlobalDestroyed);
 
     if (startRecording) {
       this.resumeRecording();
     }
@@ -298,17 +299,18 @@ let CallWatcherActor = exports.CallWatch
       this.tabActor.window.location.reload();
     }
   }, {
     request: {
       tracedGlobals: Option(0, "nullable:array:string"),
       tracedFunctions: Option(0, "nullable:array:string"),
       startRecording: Option(0, "boolean"),
       performReload: Option(0, "boolean"),
-      holdWeak: Option(0, "boolean")
+      holdWeak: Option(0, "boolean"),
+      storeCalls: Option(0, "boolean")
     },
     oneway: true
   }),
 
   /**
    * Stops listening for document global changes and puts this actor
    * to hibernation. This method is called automatically just before the
    * actor is destroyed.
@@ -536,28 +538,31 @@ let CallWatcherActor = exports.CallWatch
    */
   _onContentFunctionCall: function(...details) {
     // If the consuming tool has finalized call-watcher, ignore the
     // still-instrumented calls.
     if (this._finalized) {
       return;
     }
     let functionCall = new FunctionCallActor(this.conn, details, this._holdWeak);
-    this._functionCalls.push(functionCall);
+
+    if (this._storeCalls) {
+      this._functionCalls.push(functionCall);
+    }
+
     this.onCall(functionCall);
   }
 });
 
 /**
  * The corresponding Front object for the CallWatcherActor.
  */
 let CallWatcherFront = exports.CallWatcherFront = protocol.FrontClass(CallWatcherActor, {
   initialize: function(client, { callWatcherActor }) {
     protocol.Front.prototype.initialize.call(this, client, { actor: callWatcherActor });
-    client.addActorPool(this);
     this.manage(this);
   }
 });
 
 /**
  * Constants.
  */
 CallWatcherFront.METHOD_FUNCTION = 0;
--- a/toolkit/devtools/server/actors/canvas.js
+++ b/toolkit/devtools/server/actors/canvas.js
@@ -251,17 +251,18 @@ let CanvasActor = exports.CanvasActor = 
     }
     this._initialized = true;
 
     this._callWatcher = new CallWatcherActor(this.conn, this.tabActor);
     this._callWatcher.onCall = this._onContentFunctionCall;
     this._callWatcher.setup({
       tracedGlobals: CANVAS_CONTEXTS,
       tracedFunctions: ANIMATION_GENERATORS,
-      performReload: reload
+      performReload: reload,
+      storeCalls: true
     });
   }, {
     request: { reload: Option(0, "boolean") },
     oneway: true
   }),
 
   /**
    * Stops listening for function calls.
@@ -717,17 +718,16 @@ let ContextUtils = {
 };
 
 /**
  * The corresponding Front object for the CanvasActor.
  */
 let CanvasFront = exports.CanvasFront = protocol.FrontClass(CanvasActor, {
   initialize: function(client, { canvasActor }) {
     protocol.Front.prototype.initialize.call(this, client, { actor: canvasActor });
-    client.addActorPool(this);
     this.manage(this);
   }
 });
 
 /**
  * Constants.
  */
 CanvasFront.CANVAS_CONTEXTS = new Set(CANVAS_CONTEXTS);
--- a/toolkit/devtools/server/actors/device.js
+++ b/toolkit/devtools/server/actors/device.js
@@ -192,17 +192,16 @@ let DeviceActor = protocol.ActorClass({
     };
   }, {request: {},response: { value: RetVal("json")}})
 });
 
 let DeviceFront = protocol.FrontClass(DeviceActor, {
   initialize: function(client, form) {
     protocol.Front.prototype.initialize.call(this, client);
     this.actorID = form.deviceActor;
-    client.addActorPool(this);
     this.manage(this);
   },
 
   screenshotToBlob: function() {
     return this.screenshotToDataURL().then(longstr => {
       return longstr.string().then(dataURL => {
         let deferred = promise.defer();
         longstr.release().then(null, Cu.reportError);
--- a/toolkit/devtools/server/actors/eventlooplag.js
+++ b/toolkit/devtools/server/actors/eventlooplag.js
@@ -79,12 +79,11 @@ let EventLoopLagActor = protocol.ActorCl
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 });
 
 exports.EventLoopLagFront = protocol.FrontClass(EventLoopLagActor, {
   initialize: function(client, form) {
     protocol.Front.prototype.initialize.call(this, client);
     this.actorID = form.eventLoopLagActor;
-    client.addActorPool(this);
     this.manage(this);
   },
 });
--- a/toolkit/devtools/server/actors/gcli.js
+++ b/toolkit/devtools/server/actors/gcli.js
@@ -197,17 +197,16 @@ var GcliActor = protocol.ActorClass({
 
 exports.GcliFront = protocol.FrontClass(GcliActor, {
   initialize: function(client, tabForm) {
     protocol.Front.prototype.initialize.call(this, client);
     this.actorID = tabForm.gcliActor;
 
     // XXX: This is the first actor type in its hierarchy to use the protocol
     // library, so we're going to self-own on the client side for now.
-    client.addActorPool(this);
     this.manage(this);
   },
 });
 
 /**
  * Called the framework on DebuggerServer.registerModule()
  */
 exports.register = function(handle) {
--- a/toolkit/devtools/server/actors/inspector.js
+++ b/toolkit/devtools/server/actors/inspector.js
@@ -2719,17 +2719,16 @@ var InspectorActor = protocol.ActorClass
  */
 var InspectorFront = exports.InspectorFront = protocol.FrontClass(InspectorActor, {
   initialize: function(client, tabForm) {
     protocol.Front.prototype.initialize.call(this, client);
     this.actorID = tabForm.inspectorActor;
 
     // XXX: This is the first actor type in its hierarchy to use the protocol
     // library, so we're going to self-own on the client side for now.
-    client.addActorPool(this);
     this.manage(this);
   },
 
   destroy: function() {
     delete this.walker;
     protocol.Front.prototype.destroy.call(this);
   },
 
--- a/toolkit/devtools/server/actors/layout.js
+++ b/toolkit/devtools/server/actors/layout.js
@@ -128,17 +128,16 @@ let ReflowActor = protocol.ActorClass({
  * let front = ReflowFront(toolbox.target.client, toolbox.target.form);
  * front.on("reflows", this._onReflows);
  * front.start();
  * // now wait for events to come
  */
 exports.ReflowFront = protocol.FrontClass(ReflowActor, {
   initialize: function(client, {reflowActor}) {
     protocol.Front.prototype.initialize.call(this, client, {actor: reflowActor});
-    client.addActorPool(this);
     this.manage(this);
   },
 
   destroy: function() {
     protocol.Front.prototype.destroy.call(this);
   },
 });
 
--- a/toolkit/devtools/server/actors/memory.js
+++ b/toolkit/devtools/server/actors/memory.js
@@ -81,17 +81,16 @@ let MemoryActor = protocol.ActorClass({
 });
 
 exports.MemoryActor = MemoryActor;
 
 exports.MemoryFront = protocol.FrontClass(MemoryActor, {
   initialize: function(client, form) {
     protocol.Front.prototype.initialize.call(this, client, form);
     this.actorID = form.memoryActor;
-    client.addActorPool(this);
     this.manage(this);
   }
 });
 
 exports.register = function(handle) {
   handle.addGlobalActor(MemoryActor, "memoryActor");
   handle.addTabActor(MemoryActor, "memoryActor");
 };
--- a/toolkit/devtools/server/actors/preference.js
+++ b/toolkit/devtools/server/actors/preference.js
@@ -104,17 +104,16 @@ let PreferenceActor = protocol.ActorClas
     response: {}
   }),
 });
 
 let PreferenceFront = protocol.FrontClass(PreferenceActor, {
   initialize: function(client, form) {
     protocol.Front.prototype.initialize.call(this, client);
     this.actorID = form.preferenceActor;
-    client.addActorPool(this);
     this.manage(this);
   },
 });
 
 const _knownPreferenceFronts = new WeakMap();
 
 exports.getPreferenceFront = function(client, form) {
   if (_knownPreferenceFronts.has(client))
--- a/toolkit/devtools/server/actors/storage.js
+++ b/toolkit/devtools/server/actors/storage.js
@@ -1697,13 +1697,11 @@ let StorageActor = exports.StorageActor 
 
 /**
  * Front for the Storage Actor.
  */
 let StorageFront = exports.StorageFront = protocol.FrontClass(StorageActor, {
   initialize: function(client, tabForm) {
     protocol.Front.prototype.initialize.call(this, client);
     this.actorID = tabForm.storageActor;
-
-    client.addActorPool(this);
     this.manage(this);
   }
 });
--- a/toolkit/devtools/server/actors/styleeditor.js
+++ b/toolkit/devtools/server/actors/styleeditor.js
@@ -249,18 +249,16 @@ let StyleEditorActor = protocol.ActorCla
 
 /**
  * The corresponding Front object for the StyleEditorActor.
  */
 let StyleEditorFront = protocol.FrontClass(StyleEditorActor, {
   initialize: function(client, tabForm) {
     protocol.Front.prototype.initialize.call(this, client);
     this.actorID = tabForm.styleEditorActor;
-
-    client.addActorPool(this);
     this.manage(this);
   },
 
   getStyleSheets: function() {
     let deferred = promise.defer();
 
     events.once(this, "document-load", (styleSheets) => {
       deferred.resolve(styleSheets);
--- a/toolkit/devtools/server/actors/stylesheets.js
+++ b/toolkit/devtools/server/actors/stylesheets.js
@@ -264,18 +264,16 @@ let StyleSheetsActor = protocol.ActorCla
 
 /**
  * The corresponding Front object for the StyleSheetsActor.
  */
 let StyleSheetsFront = protocol.FrontClass(StyleSheetsActor, {
   initialize: function(client, tabForm) {
     protocol.Front.prototype.initialize.call(this, client);
     this.actorID = tabForm.styleSheetsActor;
-
-    client.addActorPool(this);
     this.manage(this);
   }
 });
 
 /**
  * A MediaRuleActor lives on the server and provides access to properties
  * of a DOM @media rule and emits events when it changes.
  */
--- a/toolkit/devtools/server/actors/webaudio.js
+++ b/toolkit/devtools/server/actors/webaudio.js
@@ -265,17 +265,16 @@ let AudioNodeActor = exports.AudioNodeAc
 });
 
 /**
  * The corresponding Front object for the AudioNodeActor.
  */
 let AudioNodeFront = protocol.FrontClass(AudioNodeActor, {
   initialize: function (client, form) {
     protocol.Front.prototype.initialize.call(this, client, form);
-    client.addActorPool(this);
     this.manage(this);
   }
 });
 
 /**
  * The Web Audio Actor handles simple interaction with an AudioContext
  * high-level methods. After instantiating this actor, you'll need to set it
  * up by calling setup().
@@ -325,17 +324,18 @@ let WebAudioActor = exports.WebAudioActo
     this._initialized = true;
 
     this._callWatcher = new CallWatcherActor(this.conn, this.tabActor);
     this._callWatcher.onCall = this._onContentFunctionCall;
     this._callWatcher.setup({
       tracedGlobals: AUDIO_GLOBALS,
       startRecording: true,
       performReload: reload,
-      holdWeak: true
+      holdWeak: true,
+      storeCalls: false
     });
     // Bind to the `global-destroyed` event on the content observer so we can
     // unbind events between the global destruction and the `finalize` cleanup
     // method on the actor.
     // TODO expose these events on CallWatcherActor itself, bug 1021321
     on(this._callWatcher._contentObserver, "global-destroyed", this._onGlobalDestroyed);
   }, {
     request: { reload: Option(0, "boolean") },
@@ -575,17 +575,16 @@ let WebAudioActor = exports.WebAudioActo
 });
 
 /**
  * The corresponding Front object for the WebAudioActor.
  */
 let WebAudioFront = exports.WebAudioFront = protocol.FrontClass(WebAudioActor, {
   initialize: function(client, { webaudioActor }) {
     protocol.Front.prototype.initialize.call(this, client, { actor: webaudioActor });
-    client.addActorPool(this);
     this.manage(this);
   }
 });
 
 WebAudioFront.NODE_CREATION_METHODS = new Set(NODE_CREATION_METHODS);
 WebAudioFront.NODE_ROUTING_METHODS = new Set(NODE_ROUTING_METHODS);
 
 /**
--- a/toolkit/devtools/server/actors/webgl.js
+++ b/toolkit/devtools/server/actors/webgl.js
@@ -338,17 +338,16 @@ let WebGLActor = exports.WebGLActor = pr
 });
 
 /**
  * The corresponding Front object for the WebGLActor.
  */
 let WebGLFront = exports.WebGLFront = protocol.FrontClass(WebGLActor, {
   initialize: function(client, { webglActor }) {
     protocol.Front.prototype.initialize.call(this, client, { actor: webglActor });
-    client.addActorPool(this);
     this.manage(this);
   }
 });
 
 /**
  * Instruments a HTMLCanvasElement with the appropriate inspection methods.
  */
 let WebGLInstrumenter = {
--- a/widget/gonk/nativewindow/GonkBufferQueueKK.cpp
+++ b/widget/gonk/nativewindow/GonkBufferQueueKK.cpp
@@ -12,37 +12,30 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #define LOG_TAG "GonkBufferQueue"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
-#define LOG_NDEBUG 0
+//#define LOG_NDEBUG 0
 
 #define GL_GLEXT_PROTOTYPES
 #define EGL_EGLEXT_PROTOTYPES
 
 #include <utils/Log.h>
 #include <utils/Trace.h>
 #include <utils/CallStack.h>
 #include <cutils/compiler.h>
 
 #include "mozilla/layers/GrallocTextureClient.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "GonkBufferQueueKK.h"
 
-// Macros for including the GonkBufferQueue name in log messages
-#define ST_LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
-#define ST_LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
-#define ST_LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
-#define ST_LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
-#define ST_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
-
 #define ATRACE_BUFFER_INDEX(index)
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 
 namespace android {
 
@@ -78,21 +71,21 @@ GonkBufferQueue::GonkBufferQueue(bool al
     mBufferHasBeenQueued(false),
     mDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888),
     mConsumerUsageBits(0),
     mTransformHint(0)
 {
     // Choose a name using the PID and a process-unique ID.
     mConsumerName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
 
-    ST_LOGV("GonkBufferQueue");
+    ALOGV("GonkBufferQueue");
 }
 
 GonkBufferQueue::~GonkBufferQueue() {
-    ST_LOGV("~GonkBufferQueue");
+    ALOGV("~GonkBufferQueue");
 }
 
 status_t GonkBufferQueue::setDefaultMaxBufferCountLocked(int count) {
     if (count < 2 || count > NUM_BUFFER_SLOTS)
         return BAD_VALUE;
 
     mDefaultMaxBufferCount = count;
     mDequeueCondition.broadcast();
@@ -113,92 +106,92 @@ status_t GonkBufferQueue::setDefaultBuff
 
 status_t GonkBufferQueue::setConsumerUsageBits(uint32_t usage) {
     Mutex::Autolock lock(mMutex);
     mConsumerUsageBits = usage;
     return NO_ERROR;
 }
 
 status_t GonkBufferQueue::setTransformHint(uint32_t hint) {
-    ST_LOGV("setTransformHint: %02x", hint);
+    ALOGV("setTransformHint: %02x", hint);
     Mutex::Autolock lock(mMutex);
     mTransformHint = hint;
     return NO_ERROR;
 }
 
 TemporaryRef<TextureClient>
 GonkBufferQueue::getTextureClientFromBuffer(ANativeWindowBuffer* buffer)
 {
     Mutex::Autolock _l(mMutex);
     if (buffer == NULL) {
-        ST_LOGE("getSlotFromBufferLocked: encountered NULL buffer");
+        ALOGE("getSlotFromBufferLocked: encountered NULL buffer");
         return nullptr;
     }
 
     for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
         if (mSlots[i].mGraphicBuffer != NULL && mSlots[i].mGraphicBuffer->handle == buffer->handle) {
             return mSlots[i].mTextureClient;
         }
     }
-    ST_LOGE("getSlotFromBufferLocked: unknown buffer: %p", buffer->handle);
+    ALOGE("getSlotFromBufferLocked: unknown buffer: %p", buffer->handle);
     return nullptr;
 }
 
 int GonkBufferQueue::getSlotFromTextureClientLocked(
         TextureClient* client) const
 {
     if (client == NULL) {
-        ST_LOGE("getSlotFromBufferLocked: encountered NULL buffer");
+        ALOGE("getSlotFromBufferLocked: encountered NULL buffer");
         return BAD_VALUE;
     }
 
     for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
         if (mSlots[i].mTextureClient == client) {
             return i;
         }
     }
-    ST_LOGE("getSlotFromBufferLocked: unknown TextureClient: %p", client);
+    ALOGE("getSlotFromBufferLocked: unknown TextureClient: %p", client);
     return BAD_VALUE;
 }
 
 status_t GonkBufferQueue::setBufferCount(int bufferCount) {
-    ST_LOGV("setBufferCount: count=%d", bufferCount);
+    ALOGV("setBufferCount: count=%d", bufferCount);
 
     sp<IConsumerListener> listener;
     {
         Mutex::Autolock lock(mMutex);
 
         if (mAbandoned) {
-            ST_LOGE("setBufferCount: GonkBufferQueue has been abandoned!");
+            ALOGE("setBufferCount: GonkBufferQueue has been abandoned!");
             return NO_INIT;
         }
         if (bufferCount > NUM_BUFFER_SLOTS) {
-            ST_LOGE("setBufferCount: bufferCount too large (max %d)",
+            ALOGE("setBufferCount: bufferCount too large (max %d)",
                     NUM_BUFFER_SLOTS);
             return BAD_VALUE;
         }
 
         // Error out if the user has dequeued buffers
         for (int i=0 ; i<NUM_BUFFER_SLOTS; i++) {
             if (mSlots[i].mBufferState == BufferSlot::DEQUEUED) {
-                ST_LOGE("setBufferCount: client owns some buffers");
+                ALOGE("setBufferCount: client owns some buffers");
                 return -EINVAL;
             }
         }
 
         if (bufferCount == 0) {
             mOverrideMaxBufferCount = 0;
             mDequeueCondition.broadcast();
             return NO_ERROR;
         }
 
         // fine to assume async to false before we're setting the buffer count
         const int minBufferSlots = getMinMaxBufferCountLocked(false);
         if (bufferCount < minBufferSlots) {
-            ST_LOGE("setBufferCount: requested buffer count (%d) is less than "
+            ALOGE("setBufferCount: requested buffer count (%d) is less than "
                     "minimum (%d)", bufferCount, minBufferSlots);
             return BAD_VALUE;
         }
 
         // here we're guaranteed that the client doesn't have dequeued buffers
         // and will release all of its buffer references.  We don't clear the
         // queue, however, so currently queued buffers still get displayed.
         // XXX: Should this use drainQueueAndFreeBuffersLocked instead?
@@ -216,17 +209,17 @@ status_t GonkBufferQueue::setBufferCount
 }
 
 int GonkBufferQueue::query(int what, int* outValue)
 {
     ATRACE_CALL();
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
-        ST_LOGE("query: GonkBufferQueue has been abandoned!");
+        ALOGE("query: GonkBufferQueue has been abandoned!");
         return NO_INIT;
     }
 
     int value;
     switch (what) {
     case NATIVE_WINDOW_WIDTH:
         value = mDefaultWidth;
         break;
@@ -249,43 +242,43 @@ int GonkBufferQueue::query(int what, int
         return BAD_VALUE;
     }
     outValue[0] = value;
     return NO_ERROR;
 }
 
 status_t GonkBufferQueue::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
     ATRACE_CALL();
-    ST_LOGV("requestBuffer: slot=%d", slot);
+    ALOGV("requestBuffer: slot=%d", slot);
     Mutex::Autolock lock(mMutex);
     if (mAbandoned) {
-        ST_LOGE("requestBuffer: GonkBufferQueue has been abandoned!");
+        ALOGE("requestBuffer: GonkBufferQueue has been abandoned!");
         return NO_INIT;
     }
     if (slot < 0 || slot >= NUM_BUFFER_SLOTS) {
-        ST_LOGE("requestBuffer: slot index out of range [0, %d]: %d",
+        ALOGE("requestBuffer: slot index out of range [0, %d]: %d",
                 NUM_BUFFER_SLOTS, slot);
         return BAD_VALUE;
     } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) {
-        ST_LOGE("requestBuffer: slot %d is not owned by the client (state=%d)",
+        ALOGE("requestBuffer: slot %d is not owned by the client (state=%d)",
                 slot, mSlots[slot].mBufferState);
         return BAD_VALUE;
     }
     mSlots[slot].mRequestBufferCalled = true;
     *buf = mSlots[slot].mGraphicBuffer;
     return NO_ERROR;
 }
 
 status_t GonkBufferQueue::dequeueBuffer(int *outBuf, sp<Fence>* outFence, bool async,
             uint32_t w, uint32_t h, uint32_t format, uint32_t usage) {
     ATRACE_CALL();
-    ST_LOGV("dequeueBuffer: w=%d h=%d fmt=%#x usage=%#x", w, h, format, usage);
+    ALOGV("dequeueBuffer: w=%d h=%d fmt=%#x usage=%#x", w, h, format, usage);
 
     if ((w && !h) || (!w && h)) {
-        ST_LOGE("dequeueBuffer: invalid size: w=%u, h=%u", w, h);
+        ALOGE("dequeueBuffer: invalid size: w=%u, h=%u", w, h);
         return BAD_VALUE;
     }
 
     status_t returnFlags(OK);
     int buf = INVALID_BUFFER_SLOT;
 
     { // Scope for the lock
         Mutex::Autolock lock(mMutex);
@@ -295,27 +288,27 @@ status_t GonkBufferQueue::dequeueBuffer(
         }
         // turn on usage bits the consumer requested
         usage |= mConsumerUsageBits;
 
         int found = -1;
         bool tryAgain = true;
         while (tryAgain) {
             if (mAbandoned) {
-                ST_LOGE("dequeueBuffer: GonkBufferQueue has been abandoned!");
+                ALOGE("dequeueBuffer: GonkBufferQueue has been abandoned!");
                 return NO_INIT;
             }
 
             const int maxBufferCount = getMaxBufferCountLocked(async);
             if (async && mOverrideMaxBufferCount) {
                 // FIXME: some drivers are manually setting the buffer-count (which they
                 // shouldn't), so we do this extra test here to handle that case.
                 // This is TEMPORARY, until we get this fixed.
                 if (mOverrideMaxBufferCount < maxBufferCount) {
-                    ST_LOGE("dequeueBuffer: async mode is invalid with buffercount override");
+                    ALOGE("dequeueBuffer: async mode is invalid with buffercount override");
                     return BAD_VALUE;
                 }
             }
 
             // Free up any buffers that are in slots beyond the max buffer
             // count.
             //for (int i = maxBufferCount; i < NUM_BUFFER_SLOTS; i++) {
             //    assert(mSlots[i].mBufferState == BufferSlot::FREE);
@@ -350,31 +343,31 @@ status_t GonkBufferQueue::dequeueBuffer(
                     }
                         break;
                 }
             }
 
             // clients are not allowed to dequeue more than one buffer
             // if they didn't set a buffer count.
             if (!mOverrideMaxBufferCount && dequeuedCount) {
-                ST_LOGE("dequeueBuffer: can't dequeue multiple buffers without "
+                ALOGE("dequeueBuffer: can't dequeue multiple buffers without "
                         "setting the buffer count");
                 return -EINVAL;
             }
 
             // See whether a buffer has been queued since the last
             // setBufferCount so we know whether to perform the min undequeued
             // buffers check below.
             if (mBufferHasBeenQueued) {
                 // make sure the client is not trying to dequeue more buffers
                 // than allowed.
                 const int newUndequeuedCount = maxBufferCount - (dequeuedCount+1);
                 const int minUndequeuedCount = getMinUndequeuedBufferCount(async);
                 if (newUndequeuedCount < minUndequeuedCount) {
-                    ST_LOGE("dequeueBuffer: min undequeued buffer count (%d) "
+                    ALOGE("dequeueBuffer: min undequeued buffer count (%d) "
                             "exceeded (dequeued=%d undequeudCount=%d)",
                             minUndequeuedCount, dequeuedCount,
                             newUndequeuedCount);
                     return -EBUSY;
                 }
             }
 
             // If no buffer is found, wait for a buffer to be released or for
@@ -383,27 +376,27 @@ status_t GonkBufferQueue::dequeueBuffer(
             if (tryAgain) {
                 // return an error if we're in "cannot block" mode (producer and consumer
                 // are controlled by the application) -- however, the consumer is allowed
                 // to acquire briefly an extra buffer (which could cause us to have to wait here)
                 // and that's okay because we know the wait will be brief (it happens
                 // if we dequeue a buffer while the consumer has acquired one but not released
                 // the old one yet -- for e.g.: see GLConsumer::updateTexImage()).
                 if (mDequeueBufferCannotBlock && (acquiredCount <= mMaxAcquiredBufferCount)) {
-                    ST_LOGE("dequeueBuffer: would block! returning an error instead.");
+                    ALOGE("dequeueBuffer: would block! returning an error instead.");
                     return WOULD_BLOCK;
                 }
                 mDequeueCondition.wait(mMutex);
             }
         }
 
 
         if (found == INVALID_BUFFER_SLOT) {
             // This should not happen.
-            ST_LOGE("dequeueBuffer: no available buffer slots");
+            ALOGE("dequeueBuffer: no available buffer slots");
             return -EBUSY;
         }
 
         buf = found;
         *outBuf = found;
 
         const bool useDefaultSize = !w && !h;
         if (useDefaultSize) {
@@ -432,17 +425,17 @@ status_t GonkBufferQueue::dequeueBuffer(
               mSlots[buf].mTextureClient = NULL;
               ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask(FROM_HERE, task);
             }
             returnFlags |= IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION;
         }
 
 
         if (CC_UNLIKELY(mSlots[buf].mFence == NULL)) {
-            ST_LOGE("dequeueBuffer: about to return a NULL fence from mSlot. "
+            ALOGE("dequeueBuffer: about to return a NULL fence from mSlot. "
                     "buf=%d, w=%d, h=%d, format=%d",
                     buf, buffer->width, buffer->height, buffer->format);
         }
         *outFence = mSlots[buf].mFence;
         mSlots[buf].mFence = Fence::NO_FENCE;
     }  // end lock scope
 
     sp<GraphicBuffer> graphicBuffer;
@@ -451,38 +444,38 @@ status_t GonkBufferQueue::dequeueBuffer(
             new GrallocTextureClientOGL(ImageBridgeChild::GetSingleton(),
                                         gfx::SurfaceFormat::UNKNOWN,
                                         gfx::BackendType::NONE,
                                         TextureFlags::DEALLOCATE_CLIENT);
         usage |= GraphicBuffer::USAGE_HW_TEXTURE;
         bool result = textureClient->AllocateGralloc(IntSize(w, h), format, usage);
         sp<GraphicBuffer> graphicBuffer = textureClient->GetGraphicBuffer();
         if (!result || !graphicBuffer.get()) {
-            ST_LOGE("dequeueBuffer: failed to alloc gralloc buffer");
+            ALOGE("dequeueBuffer: failed to alloc gralloc buffer");
             return -ENOMEM;
         }
 
         { // Scope for the lock
             Mutex::Autolock lock(mMutex);
 
             if (mAbandoned) {
-                ST_LOGE("dequeueBuffer: SurfaceTexture has been abandoned!");
+                ALOGE("dequeueBuffer: SurfaceTexture has been abandoned!");
                 return NO_INIT;
             }
 
             mSlots[buf].mGraphicBuffer = graphicBuffer;
             mSlots[buf].mTextureClient = textureClient;
-            ST_LOGD("dequeueBuffer: returning slot=%d buf=%p ", buf,
+            ALOGD("dequeueBuffer: returning slot=%d buf=%p ", buf,
                     mSlots[buf].mGraphicBuffer->handle);
 
         }
 
     }
 
-    ST_LOGV("dequeueBuffer: returning slot=%d/%llu buf=%p flags=%#x", *outBuf,
+    ALOGV("dequeueBuffer: returning slot=%d/%llu buf=%p flags=%#x", *outBuf,
             mSlots[*outBuf].mFrameNumber,
             mSlots[*outBuf].mGraphicBuffer->handle, returnFlags);
 
     return returnFlags;
 }
 
 status_t GonkBufferQueue::queueBuffer(int buf,
         const QueueBufferInput& input, QueueBufferOutput* output) {
@@ -495,82 +488,82 @@ status_t GonkBufferQueue::queueBuffer(in
     bool isAutoTimestamp;
     bool async;
     sp<Fence> fence;
 
     input.deflate(&timestamp, &isAutoTimestamp, &crop, &scalingMode, &transform,
              &async, &fence);
 
     if (fence == NULL) {
-        ST_LOGE("queueBuffer: fence is NULL");
+        ALOGE("queueBuffer: fence is NULL");
         return BAD_VALUE;
     }
 
-    ST_LOGV("queueBuffer: slot=%d time=%#llx crop=[%d,%d,%d,%d] tr=%#x "
+    ALOGV("queueBuffer: slot=%d time=%#llx crop=[%d,%d,%d,%d] tr=%#x "
             "scale=%s",
             buf, timestamp, crop.left, crop.top, crop.right, crop.bottom,
             transform, scalingModeName(scalingMode));
 
     switch (scalingMode) {
         case NATIVE_WINDOW_SCALING_MODE_FREEZE:
         case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW:
         case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP:
         case NATIVE_WINDOW_SCALING_MODE_NO_SCALE_CROP:
             break;
         default:
-            ST_LOGE("unknown scaling mode: %d", scalingMode);
+            ALOGE("unknown scaling mode: %d", scalingMode);
             return -EINVAL;
     }
 
     sp<IConsumerListener> listener;
 
     { // scope for the lock
         Mutex::Autolock lock(mMutex);
 
         if (mAbandoned) {
-            ST_LOGE("queueBuffer: GonkBufferQueue has been abandoned!");
+            ALOGE("queueBuffer: GonkBufferQueue has been abandoned!");
             return NO_INIT;
         }
 
         const int maxBufferCount = getMaxBufferCountLocked(async);
         if (async && mOverrideMaxBufferCount) {
             // FIXME: some drivers are manually setting the buffer-count (which they
             // shouldn't), so we do this extra test here to handle that case.
             // This is TEMPORARY, until we get this fixed.
             if (mOverrideMaxBufferCount < maxBufferCount) {
-                ST_LOGE("queueBuffer: async mode is invalid with buffercount override");
+                ALOGE("queueBuffer: async mode is invalid with buffercount override");
                 return BAD_VALUE;
             }
         }
         if (buf < 0 || buf >= maxBufferCount) {
-            ST_LOGE("queueBuffer: slot index out of range [0, %d]: %d",
+            ALOGE("queueBuffer: slot index out of range [0, %d]: %d",
                     maxBufferCount, buf);
             return -EINVAL;
         } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
-            ST_LOGE("queueBuffer: slot %d is not owned by the client "
+            ALOGE("queueBuffer: slot %d is not owned by the client "
                     "(state=%d)", buf, mSlots[buf].mBufferState);
             return -EINVAL;
         } else if (!mSlots[buf].mRequestBufferCalled) {
-            ST_LOGE("queueBuffer: slot %d was enqueued without requesting a "
+            ALOGE("queueBuffer: slot %d was enqueued without requesting a "
                     "buffer", buf);
             return -EINVAL;
         }
 
-        ST_LOGV("queueBuffer: slot=%d/%llu time=%#llx crop=[%d,%d,%d,%d] "
+        ALOGV("queueBuffer: slot=%d/%llu time=%#llx crop=[%d,%d,%d,%d] "
                 "tr=%#x scale=%s",
                 buf, mFrameCounter + 1, timestamp,
                 crop.left, crop.top, crop.right, crop.bottom,
                 transform, scalingModeName(scalingMode));
 
         const sp<GraphicBuffer>& graphicBuffer(mSlots[buf].mGraphicBuffer);
         Rect bufferRect(graphicBuffer->getWidth(), graphicBuffer->getHeight());
         Rect croppedCrop;
         crop.intersect(bufferRect, &croppedCrop);
         if (croppedCrop != crop) {
-            ST_LOGE("queueBuffer: crop rect is not contained within the "
+            ALOGE("queueBuffer: crop rect is not contained within the "
                     "buffer in slot %d", buf);
             return -EINVAL;
         }
 
         mSlots[buf].mFence = fence;
         mSlots[buf].mBufferState = BufferSlot::QUEUED;
         mFrameCounter++;
         mSlots[buf].mFrameNumber = mFrameCounter;
@@ -626,75 +619,75 @@ status_t GonkBufferQueue::queueBuffer(in
     if (listener != 0) {
         listener->onFrameAvailable();
     }
     return NO_ERROR;
 }
 
 void GonkBufferQueue::cancelBuffer(int buf, const sp<Fence>& fence) {
     ATRACE_CALL();
-    ST_LOGV("cancelBuffer: slot=%d", buf);
+    ALOGV("cancelBuffer: slot=%d", buf);
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
-        ST_LOGW("cancelBuffer: GonkBufferQueue has been abandoned!");
+        ALOGW("cancelBuffer: GonkBufferQueue has been abandoned!");
         return;
     }
 
     if (buf < 0 || buf >= NUM_BUFFER_SLOTS) {
-        ST_LOGE("cancelBuffer: slot index out of range [0, %d]: %d",
+        ALOGE("cancelBuffer: slot index out of range [0, %d]: %d",
                 NUM_BUFFER_SLOTS, buf);
         return;
     } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
-        ST_LOGE("cancelBuffer: slot %d is not owned by the client (state=%d)",
+        ALOGE("cancelBuffer: slot %d is not owned by the client (state=%d)",
                 buf, mSlots[buf].mBufferState);
         return;
     } else if (fence == NULL) {
-        ST_LOGE("cancelBuffer: fence is NULL");
+        ALOGE("cancelBuffer: fence is NULL");
         return;
     }
     mSlots[buf].mBufferState = BufferSlot::FREE;
     mSlots[buf].mFrameNumber = 0;
     mSlots[buf].mFence = fence;
     mDequeueCondition.broadcast();
 }
 
 
 status_t GonkBufferQueue::connect(const sp<IBinder>& token,
         int api, bool producerControlledByApp, QueueBufferOutput* output) {
     ATRACE_CALL();
-    ST_LOGV("connect: api=%d producerControlledByApp=%s", api,
+    ALOGV("connect: api=%d producerControlledByApp=%s", api,
             producerControlledByApp ? "true" : "false");
     Mutex::Autolock lock(mMutex);
 
 retry:
     if (mAbandoned) {
-        ST_LOGE("connect: GonkBufferQueue has been abandoned!");
+        ALOGE("connect: GonkBufferQueue has been abandoned!");
         return NO_INIT;
     }
 
     if (mConsumerListener == NULL) {
-        ST_LOGE("connect: GonkBufferQueue has no consumer!");
+        ALOGE("connect: GonkBufferQueue has no consumer!");
         return NO_INIT;
     }
 
     if (mConnectedApi != NO_CONNECTED_API) {
-        ST_LOGE("connect: already connected (cur=%d, req=%d)",
+        ALOGE("connect: already connected (cur=%d, req=%d)",
                 mConnectedApi, api);
         return -EINVAL;
     }
 
     // If we disconnect and reconnect quickly, we can be in a state where our slots are
     // empty but we have many buffers in the queue.  This can cause us to run out of
     // memory if we outrun the consumer.  Wait here if it looks like we have too many
     // buffers queued up.
     int maxBufferCount = getMaxBufferCountLocked(false);    // worst-case, i.e. largest value
     if (mQueue.size() > (size_t) maxBufferCount) {
         // TODO: make this bound tighter?
-        ST_LOGV("queue size is %d, waiting", mQueue.size());
+        ALOGV("queue size is %d, waiting", mQueue.size());
         mDequeueCondition.wait(mMutex);
         goto retry;
     }
 
     int err = NO_ERROR;
     switch (api) {
         case NATIVE_WINDOW_API_EGL:
         case NATIVE_WINDOW_API_CPU:
@@ -732,17 +725,17 @@ void GonkBufferQueue::binderDied(const w
     // callback upon disconnect. Therefore, it's okay to read mConnectedApi without
     // synchronization here.
     int api = mConnectedApi;
     this->disconnect(api);
 }
 
 status_t GonkBufferQueue::disconnect(int api) {
     ATRACE_CALL();
-    ST_LOGV("disconnect: api=%d", api);
+    ALOGV("disconnect: api=%d", api);
 
     int err = NO_ERROR;
     sp<IConsumerListener> listener;
 
     { // Scope for the lock
         Mutex::Autolock lock(mMutex);
 
         if (mAbandoned) {
@@ -757,23 +750,23 @@ status_t GonkBufferQueue::disconnect(int
             case NATIVE_WINDOW_API_MEDIA:
             case NATIVE_WINDOW_API_CAMERA:
                 if (mConnectedApi == api) {
                     freeAllBuffersLocked();
                     mConnectedApi = NO_CONNECTED_API;
                     mDequeueCondition.broadcast();
                     listener = mConsumerListener;
                 } else {
-                    ST_LOGE("disconnect: connected to another api (cur=%d, req=%d)",
+                    ALOGE("disconnect: connected to another api (cur=%d, req=%d)",
                             mConnectedApi, api);
                     err = -EINVAL;
                 }
                 break;
             default:
-                ST_LOGE("disconnect: unknown API %d", api);
+                ALOGE("disconnect: unknown API %d", api);
                 err = -EINVAL;
                 break;
         }
     }
 
     if (listener != NULL) {
         listener->onBuffersReleased();
     }
@@ -884,17 +877,17 @@ status_t GonkBufferQueue::acquireBuffer(
     // buffer before releasing the old one.
     int numAcquiredBuffers = 0;
     for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
         if (mSlots[i].mBufferState == BufferSlot::ACQUIRED) {
             numAcquiredBuffers++;
         }
     }
     if (numAcquiredBuffers >= mMaxAcquiredBufferCount+1) {
-        ST_LOGE("acquireBuffer: max acquired buffer count reached: %d (max=%d)",
+        ALOGE("acquireBuffer: max acquired buffer count reached: %d (max=%d)",
                 numAcquiredBuffers, mMaxAcquiredBufferCount);
         return INVALID_OPERATION;
     }
 
     // check if queue is empty
     // In asynchronous mode the list is guaranteed to be one buffer
     // deep, while in synchronous mode we use the oldest buffer.
     if (mQueue.empty()) {
@@ -942,54 +935,54 @@ status_t GonkBufferQueue::acquireBuffer(
             // (Vector front is [0], back is [size()-1])
             const BufferItem& bi(mQueue[1]);
             nsecs_t desiredPresent = bi.mTimestamp;
             if (desiredPresent < expectedPresent - MAX_REASONABLE_NSEC ||
                     desiredPresent > expectedPresent) {
                 // This buffer is set to display in the near future, or
                 // desiredPresent is garbage.  Either way we don't want to
                 // drop the previous buffer just to get this on screen sooner.
-                ST_LOGV("pts nodrop: des=%lld expect=%lld (%lld) now=%lld",
+                ALOGV("pts nodrop: des=%lld expect=%lld (%lld) now=%lld",
                         desiredPresent, expectedPresent, desiredPresent - expectedPresent,
                         systemTime(CLOCK_MONOTONIC));
                 break;
             }
-            ST_LOGV("pts drop: queue1des=%lld expect=%lld size=%d",
+            ALOGV("pts drop: queue1des=%lld expect=%lld size=%d",
                     desiredPresent, expectedPresent, mQueue.size());
             if (stillTracking(front)) {
                 // front buffer is still in mSlots, so mark the slot as free
                 mSlots[front->mBuf].mBufferState = BufferSlot::FREE;
             }
             mQueue.erase(front);
             front = mQueue.begin();
         }
 
         // See if the front buffer is due.
         nsecs_t desiredPresent = front->mTimestamp;
         if (desiredPresent > expectedPresent &&
                 desiredPresent < expectedPresent + MAX_REASONABLE_NSEC) {
-            ST_LOGV("pts defer: des=%lld expect=%lld (%lld) now=%lld",
+            ALOGV("pts defer: des=%lld expect=%lld (%lld) now=%lld",
                     desiredPresent, expectedPresent, desiredPresent - expectedPresent,
                     systemTime(CLOCK_MONOTONIC));
             return PRESENT_LATER;
         }
 
-        ST_LOGV("pts accept: des=%lld expect=%lld (%lld) now=%lld",
+        ALOGV("pts accept: des=%lld expect=%lld (%lld) now=%lld",
                 desiredPresent, expectedPresent, desiredPresent - expectedPresent,
                 systemTime(CLOCK_MONOTONIC));
     }
 
     int buf = front->mBuf;
     buffer->mGraphicBuffer = mSlots[buf].mGraphicBuffer;
     buffer->mFrameNumber = mSlots[buf].mFrameNumber;
     buffer->mBuf = buf;
     buffer->mFence = mSlots[buf].mFence;
     ATRACE_BUFFER_INDEX(buf);
 
-    ST_LOGV("acquireBuffer: acquiring { slot=%d/%llu, buffer=%p }",
+    ALOGV("acquireBuffer: acquiring { slot=%d/%llu, buffer=%p }",
             front->mBuf, front->mFrameNumber,
             front->mGraphicBuffer->handle);
     // if front buffer still being tracked update slot state
     if (stillTracking(front)) {
         mSlots[buf].mAcquireCalled = true;
         mSlots[buf].mNeedsCleanupOnRelease = false;
         mSlots[buf].mBufferState = BufferSlot::ACQUIRED;
         mSlots[buf].mFence = Fence::NO_FENCE;
@@ -1037,72 +1030,72 @@ status_t GonkBufferQueue::releaseBuffer(
         front++;
     }
 
     // The buffer can now only be released if its in the acquired state
     if (mSlots[buf].mBufferState == BufferSlot::ACQUIRED) {
         mSlots[buf].mFence = fence;
         mSlots[buf].mBufferState = BufferSlot::FREE;
     } else if (mSlots[buf].mNeedsCleanupOnRelease) {
-        ST_LOGV("releasing a stale buf %d its state was %d", buf, mSlots[buf].mBufferState);
+        ALOGV("releasing a stale buf %d its state was %d", buf, mSlots[buf].mBufferState);
         mSlots[buf].mNeedsCleanupOnRelease = false;
         return STALE_BUFFER_SLOT;
     } else {
-        ST_LOGE("attempted to release buf %d but its state was %d", buf, mSlots[buf].mBufferState);
+        ALOGE("attempted to release buf %d but its state was %d", buf, mSlots[buf].mBufferState);
         return -EINVAL;
     }
 
     mDequeueCondition.broadcast();
     return NO_ERROR;
 }
 
 status_t GonkBufferQueue::consumerConnect(const sp<IConsumerListener>& consumerListener,
         bool controlledByApp) {
-    ST_LOGV("consumerConnect controlledByApp=%s",
+    ALOGV("consumerConnect controlledByApp=%s",
             controlledByApp ? "true" : "false");
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
-        ST_LOGE("consumerConnect: GonkBufferQueue has been abandoned!");
+        ALOGE("consumerConnect: GonkBufferQueue has been abandoned!");
         return NO_INIT;
     }
     if (consumerListener == NULL) {
-        ST_LOGE("consumerConnect: consumerListener may not be NULL");
+        ALOGE("consumerConnect: consumerListener may not be NULL");
         return BAD_VALUE;
     }
 
     mConsumerListener = consumerListener;
     mConsumerControlledByApp = controlledByApp;
 
     return NO_ERROR;
 }
 
 status_t GonkBufferQueue::consumerDisconnect() {
-    ST_LOGV("consumerDisconnect");
+    ALOGV("consumerDisconnect");
     Mutex::Autolock lock(mMutex);
 
     if (mConsumerListener == NULL) {
-        ST_LOGE("consumerDisconnect: No consumer is connected!");
+        ALOGE("consumerDisconnect: No consumer is connected!");
         return -EINVAL;
     }
 
     mAbandoned = true;
     mConsumerListener = NULL;
     mQueue.clear();
     freeAllBuffersLocked();
     mDequeueCondition.broadcast();
     return NO_ERROR;
 }
 
 status_t GonkBufferQueue::getReleasedBuffers(uint32_t* slotMask) {
-    ST_LOGV("getReleasedBuffers");
+    ALOGV("getReleasedBuffers");
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
-        ST_LOGE("getReleasedBuffers: GonkBufferQueue has been abandoned!");
+        ALOGE("getReleasedBuffers: GonkBufferQueue has been abandoned!");
         return NO_INIT;
     }
 
     uint32_t mask = 0;
     for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
         if (!mSlots[i].mAcquireCalled) {
             mask |= 1 << i;
         }
@@ -1115,24 +1108,24 @@ status_t GonkBufferQueue::getReleasedBuf
     while (front != mQueue.end()) {
         if (front->mAcquireCalled)
             mask &= ~(1 << front->mBuf);
         front++;
     }
 
     *slotMask = mask;
 
-    ST_LOGV("getReleasedBuffers: returning mask %#x", mask);
+    ALOGV("getReleasedBuffers: returning mask %#x", mask);
     return NO_ERROR;
 }
 
 status_t GonkBufferQueue::setDefaultBufferSize(uint32_t w, uint32_t h) {
-    ST_LOGV("setDefaultBufferSize: w=%d, h=%d", w, h);
+    ALOGV("setDefaultBufferSize: w=%d, h=%d", w, h);
     if (!w || !h) {
-        ST_LOGE("setDefaultBufferSize: dimensions cannot be 0 (w=%d, h=%d)",
+        ALOGE("setDefaultBufferSize: dimensions cannot be 0 (w=%d, h=%d)",
                 w, h);
         return BAD_VALUE;
     }
 
     Mutex::Autolock lock(mMutex);
     mDefaultWidth = w;
     mDefaultHeight = h;
     return NO_ERROR;
@@ -1143,28 +1136,28 @@ status_t GonkBufferQueue::setDefaultMaxB
     Mutex::Autolock lock(mMutex);
     return setDefaultMaxBufferCountLocked(bufferCount);
 }
 
 status_t GonkBufferQueue::disableAsyncBuffer() {
     ATRACE_CALL();
     Mutex::Autolock lock(mMutex);
     if (mConsumerListener != NULL) {
-        ST_LOGE("disableAsyncBuffer: consumer already connected!");
+        ALOGE("disableAsyncBuffer: consumer already connected!");
         return INVALID_OPERATION;
     }
     mUseAsyncBuffer = false;
     return NO_ERROR;
 }
 
 status_t GonkBufferQueue::setMaxAcquiredBufferCount(int maxAcquiredBuffers) {
     ATRACE_CALL();
     Mutex::Autolock lock(mMutex);
     if (maxAcquiredBuffers < 1 || maxAcquiredBuffers > MAX_MAX_ACQUIRED_BUFFERS) {
-        ST_LOGE("setMaxAcquiredBufferCount: invalid count specified: %d",
+        ALOGE("setMaxAcquiredBufferCount: invalid count specified: %d",
                 maxAcquiredBuffers);
         return BAD_VALUE;
     }
     if (mConnectedApi != NO_CONNECTED_API) {
         return INVALID_OPERATION;
     }
     mMaxAcquiredBufferCount = maxAcquiredBuffers;
     return NO_ERROR;
@@ -1212,17 +1205,17 @@ int GonkBufferQueue::getMaxBufferCountLo
     }
 
     return maxBufferCount;
 }
 
 bool GonkBufferQueue::stillTracking(const BufferItem *item) const {
     const BufferSlot &slot = mSlots[item->mBuf];
 
-    ST_LOGV("stillTracking?: item: { slot=%d/%llu, buffer=%p }, "
+    ALOGV("stillTracking?: item: { slot=%d/%llu, buffer=%p }, "
             "slot: { slot=%d/%llu, buffer=%p }",
             item->mBuf, item->mFrameNumber,
             (item->mGraphicBuffer.get() ? item->mGraphicBuffer->handle : 0),
             item->mBuf, slot.mFrameNumber,
             (slot.mGraphicBuffer.get() ? slot.mGraphicBuffer->handle : 0));
 
     // Compare item with its original buffer slot.  We can check the slot
     // as the buffer would not be moved to a different slot by the producer.
--- a/widget/gonk/nativewindow/GonkConsumerBaseKK.cpp
+++ b/widget/gonk/nativewindow/GonkConsumerBaseKK.cpp
@@ -24,23 +24,16 @@
 #include <hardware/hardware.h>
 
 #include <gui/IGraphicBufferAlloc.h>
 #include <utils/Log.h>
 #include <utils/String8.h>
 
 #include "GonkConsumerBaseKK.h"
 
-// Macros for including the GonkConsumerBase name in log messages
-#define CB_LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
-#define CB_LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
-#define CB_LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
-#define CB_LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
-#define CB_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
-
 namespace android {
 
 // Get an ID that's unique within this process.
 static int32_t createProcessUniqueId() {
     static volatile int32_t globalCounter = 0;
     return android_atomic_inc(&globalCounter);
 }
 
@@ -54,109 +47,109 @@ GonkConsumerBase::GonkConsumerBase(const
     // reference once the ctor ends, as that would cause the refcount of 'this'
     // dropping to 0 at the end of the ctor.  Since all we need is a wp<...>
     // that's what we create.
     wp<ConsumerListener> listener = static_cast<ConsumerListener*>(this);
     sp<IConsumerListener> proxy = new GonkBufferQueue::ProxyConsumerListener(listener);
 
     status_t err = mConsumer->consumerConnect(proxy, controlledByApp);
     if (err != NO_ERROR) {
-        CB_LOGE("GonkConsumerBase: error connecting to GonkBufferQueue: %s (%d)",
+        ALOGE("GonkConsumerBase: error connecting to GonkBufferQueue: %s (%d)",
                 strerror(-err), err);
     } else {
         mConsumer->setConsumerName(mName);
     }
 }
 
 GonkConsumerBase::~GonkConsumerBase() {
-    CB_LOGV("~GonkConsumerBase");
+    ALOGV("~GonkConsumerBase");
     Mutex::Autolock lock(mMutex);
 
     // Verify that abandon() has been called before we get here.  This should
     // be done by GonkConsumerBase::onLastStrongRef(), but it's possible for a
     // derived class to override that method and not call
     // GonkConsumerBase::onLastStrongRef().
     LOG_ALWAYS_FATAL_IF(!mAbandoned, "[%s] ~GonkConsumerBase was called, but the "
         "consumer is not abandoned!", mName.string());
 }
 
 void GonkConsumerBase::onLastStrongRef(const void* id) {
     abandon();
 }
 
 void GonkConsumerBase::freeBufferLocked(int slotIndex) {
-    CB_LOGV("freeBufferLocked: slotIndex=%d", slotIndex);
+    ALOGV("freeBufferLocked: slotIndex=%d", slotIndex);
     mSlots[slotIndex].mGraphicBuffer = 0;
     mSlots[slotIndex].mFence = Fence::NO_FENCE;
     mSlots[slotIndex].mFrameNumber = 0;
 }
 
 // Used for refactoring, should not be in final interface
 sp<GonkBufferQueue> GonkConsumerBase::getBufferQueue() const {
     Mutex::Autolock lock(mMutex);
     return mConsumer;
 }
 
 void GonkConsumerBase::onFrameAvailable() {
-    CB_LOGV("onFrameAvailable");
+    ALOGV("onFrameAvailable");
 
     sp<FrameAvailableListener> listener;
     { // scope for the lock
         Mutex::Autolock lock(mMutex);
         listener = mFrameAvailableListener.promote();
     }
 
     if (listener != NULL) {
-        CB_LOGV("actually calling onFrameAvailable");
+        ALOGV("actually calling onFrameAvailable");
         listener->onFrameAvailable();
     }
 }
 
 void GonkConsumerBase::onBuffersReleased() {
     Mutex::Autolock lock(mMutex);
 
-    CB_LOGV("onBuffersReleased");
+    ALOGV("onBuffersReleased");
 
     if (mAbandoned) {
         // Nothing to do if we're already abandoned.
         return;
     }
 
     uint32_t mask = 0;
     mConsumer->getReleasedBuffers(&mask);
     for (int i = 0; i < GonkBufferQueue::NUM_BUFFER_SLOTS; i++) {
         if (mask & (1 << i)) {
             freeBufferLocked(i);
         }
     }
 }
 
 void GonkConsumerBase::abandon() {
-    CB_LOGV("abandon");
+    ALOGV("abandon");
     Mutex::Autolock lock(mMutex);
 
     if (!mAbandoned) {
         abandonLocked();
         mAbandoned = true;
     }
 }
 
 void GonkConsumerBase::abandonLocked() {
-	CB_LOGV("abandonLocked");
+	ALOGV("abandonLocked");
     for (int i =0; i < GonkBufferQueue::NUM_BUFFER_SLOTS; i++) {
         freeBufferLocked(i);
     }
     // disconnect from the BufferQueue
     mConsumer->consumerDisconnect();
     mConsumer.clear();
 }
 
 void GonkConsumerBase::setFrameAvailableListener(
         const wp<FrameAvailableListener>& listener) {
-    CB_LOGV("setFrameAvailableListener");
+    ALOGV("setFrameAvailableListener");
     Mutex::Autolock lock(mMutex);
     mFrameAvailableListener = listener;
 }
 
 void GonkConsumerBase::dump(String8& result) const {
     dump(result, "");
 }
 
@@ -182,45 +175,45 @@ status_t GonkConsumerBase::acquireBuffer
 
     if (item->mGraphicBuffer != NULL) {
         mSlots[item->mBuf].mGraphicBuffer = item->mGraphicBuffer;
     }
 
     mSlots[item->mBuf].mFrameNumber = item->mFrameNumber;
     mSlots[item->mBuf].mFence = item->mFence;
 
-    CB_LOGV("acquireBufferLocked: -> slot=%d", item->mBuf);
+    ALOGV("acquireBufferLocked: -> slot=%d", item->mBuf);
 
     return OK;
 }
 
 status_t GonkConsumerBase::addReleaseFence(int slot,
         const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence) {
     Mutex::Autolock lock(mMutex);
     return addReleaseFenceLocked(slot, graphicBuffer, fence);
 }
 
 status_t GonkConsumerBase::addReleaseFenceLocked(int slot,
         const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence) {
-    CB_LOGV("addReleaseFenceLocked: slot=%d", slot);
+    ALOGV("addReleaseFenceLocked: slot=%d", slot);
 
     // If consumer no longer tracks this graphicBuffer, we can safely
     // drop this fence, as it will never be received by the producer.
     if (!stillTracking(slot, graphicBuffer)) {
         return OK;
     }
 
     if (!mSlots[slot].mFence.get()) {
         mSlots[slot].mFence = fence;
     } else {
         sp<Fence> mergedFence = Fence::merge(
                 String8::format("%.28s:%d", mName.string(), slot),
                 mSlots[slot].mFence, fence);
         if (!mergedFence.get()) {
-            CB_LOGE("failed to merge release fences");
+            ALOGE("failed to merge release fences");
             // synchronization is broken, the best we can do is hope fences
             // signal in order so the new fence will act like a union
             mSlots[slot].mFence = fence;
             return BAD_VALUE;
         }
         mSlots[slot].mFence = mergedFence;
     }
 
@@ -230,17 +223,17 @@ status_t GonkConsumerBase::addReleaseFen
 status_t GonkConsumerBase::releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer) {
     // If consumer no longer tracks this graphicBuffer (we received a new
     // buffer on the same slot), the buffer producer is definitely no longer
     // tracking it.
     if (!stillTracking(slot, graphicBuffer)) {
         return OK;
     }
 
-    CB_LOGV("releaseBufferLocked: slot=%d/%llu",
+    ALOGV("releaseBufferLocked: slot=%d/%llu",
             slot, mSlots[slot].mFrameNumber);
     status_t err = mConsumer->releaseBuffer(slot, mSlots[slot].mFrameNumber, mSlots[slot].mFence);
     if (err == GonkBufferQueue::STALE_BUFFER_SLOT) {
         freeBufferLocked(slot);
     }
 
     mSlots[slot].mFence = Fence::NO_FENCE;
 
--- a/widget/gonk/nativewindow/GonkNativeWindowKK.cpp
+++ b/widget/gonk/nativewindow/GonkNativeWindowKK.cpp
@@ -18,22 +18,16 @@
 //#define LOG_NDEBUG 0
 #define LOG_TAG "GonkNativeWindow"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 #include <utils/Log.h>
 
 #include "GonkNativeWindowKK.h"
 #include "GrallocImages.h"
 
-#define BI_LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
-#define BI_LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
-#define BI_LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
-#define BI_LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
-#define BI_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
-
 using namespace mozilla;
 using namespace mozilla::layers;
 
 namespace android {
 
 GonkNativeWindow::GonkNativeWindow(int bufferCount) :
     GonkConsumerBase(new GonkBufferQueue(true), false)
 {
@@ -63,25 +57,25 @@ status_t GonkNativeWindow::acquireBuffer
 
     if (!item) return BAD_VALUE;
 
     Mutex::Autolock _l(mMutex);
 
     err = acquireBufferLocked(item, presentWhen);
     if (err != OK) {
         if (err != NO_BUFFER_AVAILABLE) {
-            BI_LOGE("Error acquiring buffer: %s (%d)", strerror(err), err);
+            ALOGE("Error acquiring buffer: %s (%d)", strerror(err), err);
         }
         return err;
     }
 
     if (waitForFence) {
         err = item->mFence->waitForever("GonkNativeWindow::acquireBuffer");
         if (err != OK) {
-            BI_LOGE("Failed to wait for fence of acquired buffer: %s (%d)",
+            ALOGE("Failed to wait for fence of acquired buffer: %s (%d)",
                     strerror(-err), err);
             return err;
         }
     }
 
     item->mGraphicBuffer = mSlots[item->mBuf].mGraphicBuffer;
 
     return OK;
@@ -92,17 +86,17 @@ status_t GonkNativeWindow::releaseBuffer
     status_t err;
 
     Mutex::Autolock _l(mMutex);
 
     err = addReleaseFenceLocked(item.mBuf, item.mGraphicBuffer, releaseFence);
 
     err = releaseBufferLocked(item.mBuf, item.mGraphicBuffer);
     if (err != OK) {
-        BI_LOGE("Failed to release buffer: %s (%d)",
+        ALOGE("Failed to release buffer: %s (%d)",
                 strerror(-err), err);
     }
     return err;
 }
 
 status_t GonkNativeWindow::setDefaultBufferSize(uint32_t w, uint32_t h) {
     Mutex::Autolock _l(mMutex);
     return mConsumer->setDefaultBufferSize(w, h);
@@ -139,17 +133,17 @@ GonkNativeWindow::RecycleCallback(Textur
   GonkNativeWindow* nativeWindow =
     static_cast<GonkNativeWindow*>(closure);
 
   client->ClearRecycleCallback();
   nativeWindow->returnBuffer(client);
 }
 
 void GonkNativeWindow::returnBuffer(TextureClient* client) {
-    BI_LOGD("GonkNativeWindow::returnBuffer");
+    ALOGD("GonkNativeWindow::returnBuffer");
     Mutex::Autolock lock(mMutex);
 
     int index =  mConsumer->getSlotFromTextureClientLocked(client);
     if (index < 0) {
     }
 
     sp<Fence> fence = client->GetReleaseFenceHandle().mFence;
     if (!fence.get()) {
@@ -167,17 +161,17 @@ void GonkNativeWindow::returnBuffer(Text
 TemporaryRef<TextureClient>
 GonkNativeWindow::getTextureClientFromBuffer(ANativeWindowBuffer* buffer) {
     Mutex::Autolock lock(mMutex);
     return mConsumer->getTextureClientFromBuffer(buffer);
 }
 
 void GonkNativeWindow::setNewFrameCallback(
         GonkNativeWindowNewFrameCallback* callback) {
-    BI_LOGD("setNewFrameCallback");
+    ALOGD("setNewFrameCallback");
     Mutex::Autolock lock(mMutex);
     mNewFrameCallback = callback;
 }
 
 void GonkNativeWindow::onFrameAvailable() {
     GonkConsumerBase::onFrameAvailable();
 
     if (mNewFrameCallback) {