Merge m-c to inbound on a CLOSED TREE. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 05 Aug 2014 16:01:50 -0400
changeset 219631 3dbc718058d2a7a3b92256455d794211e2af0f7e
parent 219630 b2ba34d2805b843e9d848802fa0d3b771fbfaf16 (current diff)
parent 219525 191e834ff32ba45bf323edef57849c0c872922e6 (diff)
child 219632 6cbdd4d523a7cb521ccbe993e995e6f97dcfa5ae
child 219677 755f2612e6e364fb9300f1b402aa2e0a57d9728a
push id3979
push userraliiev@mozilla.com
push dateMon, 13 Oct 2014 16:35:44 +0000
treeherdermozilla-beta@30f2cc610691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone34.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
browser/devtools/webide/locales/en-US/webide.dtd
browser/devtools/webide/locales/en-US/webide.properties
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,22 +14,22 @@
   <!--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="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="e93780f9da8b34f370a4113abd4df9780d58e443"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="d85bbae28dd9ab9679b42d8d37c84810059e097c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2249037c79fda1d2eede2d085716577d5357e796"/>
   <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="227354333a185180b85471f2cc6abfb029e44718"/>
-  <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="71f5a35e3bc1801847413cff1f14fc3b5cd991ca"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,19 +12,19 @@
   <!--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="3aa6abd313f965a84aa86c6b213dc154e4875139">
     <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="e93780f9da8b34f370a4113abd4df9780d58e443"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d85bbae28dd9ab9679b42d8d37c84810059e097c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2249037c79fda1d2eede2d085716577d5357e796"/>
-  <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="71f5a35e3bc1801847413cff1f14fc3b5cd991ca"/>
   <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"/>
   <project groups="linux" 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="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" 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="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,21 +10,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"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="999e945b85c578c503ad445c2285940f16aacdae">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="e93780f9da8b34f370a4113abd4df9780d58e443"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d85bbae28dd9ab9679b42d8d37c84810059e097c"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2249037c79fda1d2eede2d085716577d5357e796"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
-  <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <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="71f5a35e3bc1801847413cff1f14fc3b5cd991ca"/>
   <!-- Stock Android things -->
   <project groups="linux" 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="f92a936f2aa97526d4593386754bdbf02db07a12"/>
   <project groups="linux" 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="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="1950e4760fa14688b83cdbb5acaa1af9f82ef434"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,22 +14,22 @@
   <!--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="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="e93780f9da8b34f370a4113abd4df9780d58e443"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="d85bbae28dd9ab9679b42d8d37c84810059e097c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2249037c79fda1d2eede2d085716577d5357e796"/>
   <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="227354333a185180b85471f2cc6abfb029e44718"/>
-  <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="71f5a35e3bc1801847413cff1f14fc3b5cd991ca"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,19 +12,19 @@
   <!--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="3aa6abd313f965a84aa86c6b213dc154e4875139">
     <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="e93780f9da8b34f370a4113abd4df9780d58e443"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d85bbae28dd9ab9679b42d8d37c84810059e097c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2249037c79fda1d2eede2d085716577d5357e796"/>
-  <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="71f5a35e3bc1801847413cff1f14fc3b5cd991ca"/>
   <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"/>
   <project groups="linux" 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="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" 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="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "f47342e0a4e802b69ec1da8b641f24ae35016d9d", 
+    "revision": "cf6d955a1703c6b7a947d0f01cb60955e7e87b0d", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,21 +12,21 @@
   <!--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="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="e93780f9da8b34f370a4113abd4df9780d58e443"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="d85bbae28dd9ab9679b42d8d37c84810059e097c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2249037c79fda1d2eede2d085716577d5357e796"/>
   <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="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="71f5a35e3bc1801847413cff1f14fc3b5cd991ca"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="746bc48f34f5060f90801925dcdd964030c1ab6d"/>
   <project name="platform/development" path="development" revision="2460485184bc8535440bb63876d4e63ec1b4770c"/>
   <project name="device/common" path="device/common" revision="0dcc1e03659db33b77392529466f9eb685cdd3c7"/>
   <project name="device/sample" path="device/sample" revision="68b1cb978a20806176123b959cb05d4fa8adaea4"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,21 +10,21 @@
   <!--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="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="e93780f9da8b34f370a4113abd4df9780d58e443"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="d85bbae28dd9ab9679b42d8d37c84810059e097c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2249037c79fda1d2eede2d085716577d5357e796"/>
   <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="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <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"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="575fdbf046e966a5915b1f1e800e5d6ad0ea14c0"/>
   <project name="platform/development" path="development" revision="b1025ec93beeb480caaf3049d171283c3846461d"/>
   <project name="device/common" path="device/common" revision="0dcc1e03659db33b77392529466f9eb685cdd3c7"/>
   <project name="device/sample" path="device/sample" revision="68b1cb978a20806176123b959cb05d4fa8adaea4"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,19 +12,19 @@
   <!--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="3aa6abd313f965a84aa86c6b213dc154e4875139">
     <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="e93780f9da8b34f370a4113abd4df9780d58e443"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d85bbae28dd9ab9679b42d8d37c84810059e097c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2249037c79fda1d2eede2d085716577d5357e796"/>
-  <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="71f5a35e3bc1801847413cff1f14fc3b5cd991ca"/>
   <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"/>
   <project groups="linux" 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="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" 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="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,21 +12,21 @@
   <!--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="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="e93780f9da8b34f370a4113abd4df9780d58e443"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="d85bbae28dd9ab9679b42d8d37c84810059e097c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2249037c79fda1d2eede2d085716577d5357e796"/>
   <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="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
+  <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="71f5a35e3bc1801847413cff1f14fc3b5cd991ca"/>
   <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="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="e0a9ac010df3afaa47ba107192c05ac8b5516435"/>
   <project name="platform/development" path="development" revision="a384622f5fcb1d2bebb9102591ff7ae91fe8ed2d"/>
   <project name="device/common" path="device/common" revision="7c65ea240157763b8ded6154a17d3c033167afb7"/>
--- a/browser/components/loop/README.txt
+++ b/browser/components/loop/README.txt
@@ -8,21 +8,45 @@ modern browser that supports WebRTC.
 The standalone client is a set of web pages intended to be hosted on a
 standalone server referenced by the loop-server.
 
 The standalone client exists in standalone/ but shares items
 (from content/shared/) with the desktop implementation. See the README.md
 file in the standalone/ directory for how to run the server locally.
 
 
-Working with JSX
-================
+Hacking
+=======
+Please be sure to execute
+
+  browser/components/loop/run-all-loop-tests.sh
+
+from the top level before requesting review on a patch.
+
 
-You need to install the JSX compiler using npm in order to compile the .jsx
-files into regular .js ones:
+Functional Tests
+================
+These are currently a work in progress, but it's already possible to run a test
+if you have a [loop-server](https://github.com/mozilla-services/loop-server)
+install that is properly configured.  From the top-level gecko directory,
+execute:
+
+  export LOOP_SERVER=/Users/larry/src/loop-server
+  ./mach marionette-test browser/components/loop/test/functional/manifest.ini
+
+Once the automation is complete, we'll include this in run-all-loop-tests.sh
+as well.
+
+
+Working with React JSX files
+============================
+
+Our views use [React](http://facebook.github.io/react/) written in JSX files
+and transpiled to JS before we commit. You need to install the JSX compiler
+using npm in order to compile the .jsx files into regular .js ones:
 
     npm install -g react-tools
 
 Once installed, run build-jsx with the --watch option from
 browser/components/loop, eg.:
 
     cd browser/components/loop
     ./build-jsx --watch
--- a/browser/components/loop/content/js/conversation.js
+++ b/browser/components/loop/content/js/conversation.js
@@ -153,39 +153,52 @@ loop.conversation = (function(OT, mozL10
      * Incoming call route.
      *
      * @param {String} loopVersion The version from the push notification, set
      *                             by the router from the URL.
      */
     incoming: function(loopVersion) {
       navigator.mozLoop.startAlerting();
       this._conversation.set({loopVersion: loopVersion});
-      this._conversation.once("accept", function() {
+      this._conversation.once("accept", () => {
         this.navigate("call/accept", {trigger: true});
-      }.bind(this));
-      this._conversation.once("decline", function() {
+      });
+      this._conversation.once("decline", () => {
         this.navigate("call/decline", {trigger: true});
-      }.bind(this));
-      this._conversation.once("declineAndBlock", function() {
+      });
+      this._conversation.once("declineAndBlock", () => {
         this.navigate("call/declineAndBlock", {trigger: true});
-      }.bind(this));
-      this.loadReactComponent(loop.conversation.IncomingCallView({
-        model: this._conversation
-      }));
+      });
+      this._conversation.once("call:incoming", this.startCall, this);
+      this._client.requestCallsInfo(loopVersion, (err, sessionData) => {
+        if (err) {
+          console.error("Failed to get the sessionData", err);
+          // XXX Not the ideal response, but bug 1047410 will be replacing
+          //this by better "call failed" UI.
+          this._notifier.errorL10n("cannot_start_call_session_not_ready");
+          return;
+        }
+        // XXX For incoming calls we might have more than one call queued.
+        // For now, we'll just assume the first call is the right information.
+        // We'll probably really want to be getting this data from the
+        // background worker on the desktop client.
+        // Bug 1032700 should fix this.
+        this._conversation.setSessionData(sessionData[0]);
+        this.loadReactComponent(loop.conversation.IncomingCallView({
+          model: this._conversation
+        }));
+      });
     },
 
     /**
      * Accepts an incoming call.
      */
     accept: function() {
       navigator.mozLoop.stopAlerting();
-      this._conversation.initiate({
-        client: new loop.Client(),
-        outgoing: false
-      });
+      this._conversation.incoming();
     },
 
     /**
      * Declines an incoming call.
      */
     decline: function() {
       navigator.mozLoop.stopAlerting();
       // XXX For now, we just close the window
@@ -196,20 +209,20 @@ loop.conversation = (function(OT, mozL10
      * Decline and block an incoming call
      * @note:
      * - loopToken is the callUrl identifier. It gets set in the panel
      *   after a callUrl is received
      */
     declineAndBlock: function() {
       navigator.mozLoop.stopAlerting();
       var token = navigator.mozLoop.getLoopCharPref('loopToken');
-      var client = new loop.Client();
-      client.deleteCallUrl(token, function(error) {
+      this._client.deleteCallUrl(token, function(error) {
         // XXX The conversation window will be closed when this cb is triggered
         // figure out if there is a better way to report the error to the user
+        // (bug 1048909).
         console.log(error);
       });
       window.close();
     },
 
     /**
      * conversation is the route when the conversation is active. The start
      * route should be navigated to first.
@@ -249,18 +262,22 @@ loop.conversation = (function(OT, mozL10
    */
   function init() {
     // Do the initial L10n setup, we do this before anything
     // else to ensure the L10n environment is setup correctly.
     mozL10n.initialize(navigator.mozLoop);
 
     document.title = mozL10n.get("incoming_call_title");
 
+    var client = new loop.Client();
     router = new ConversationRouter({
-      conversation: new loop.shared.models.ConversationModel({}, {sdk: OT}),
+      client: client,
+      conversation: new loop.shared.models.ConversationModel(
+        {},         // Model attributes
+        {sdk: OT}), // Model dependencies
       notifier: new sharedViews.NotificationListView({el: "#messages"})
     });
     Backbone.history.start();
   }
 
   return {
     ConversationRouter: ConversationRouter,
     IncomingCallView: IncomingCallView,
--- a/browser/components/loop/content/js/conversation.jsx
+++ b/browser/components/loop/content/js/conversation.jsx
@@ -153,39 +153,52 @@ loop.conversation = (function(OT, mozL10
      * Incoming call route.
      *
      * @param {String} loopVersion The version from the push notification, set
      *                             by the router from the URL.
      */
     incoming: function(loopVersion) {
       navigator.mozLoop.startAlerting();
       this._conversation.set({loopVersion: loopVersion});
-      this._conversation.once("accept", function() {
+      this._conversation.once("accept", () => {
         this.navigate("call/accept", {trigger: true});
-      }.bind(this));
-      this._conversation.once("decline", function() {
+      });
+      this._conversation.once("decline", () => {
         this.navigate("call/decline", {trigger: true});
-      }.bind(this));
-      this._conversation.once("declineAndBlock", function() {
+      });
+      this._conversation.once("declineAndBlock", () => {
         this.navigate("call/declineAndBlock", {trigger: true});
-      }.bind(this));
-      this.loadReactComponent(loop.conversation.IncomingCallView({
-        model: this._conversation
-      }));
+      });
+      this._conversation.once("call:incoming", this.startCall, this);
+      this._client.requestCallsInfo(loopVersion, (err, sessionData) => {
+        if (err) {
+          console.error("Failed to get the sessionData", err);
+          // XXX Not the ideal response, but bug 1047410 will be replacing
+          //this by better "call failed" UI.
+          this._notifier.errorL10n("cannot_start_call_session_not_ready");
+          return;
+        }
+        // XXX For incoming calls we might have more than one call queued.
+        // For now, we'll just assume the first call is the right information.
+        // We'll probably really want to be getting this data from the
+        // background worker on the desktop client.
+        // Bug 1032700 should fix this.
+        this._conversation.setSessionData(sessionData[0]);
+        this.loadReactComponent(loop.conversation.IncomingCallView({
+          model: this._conversation
+        }));
+      });
     },
 
     /**
      * Accepts an incoming call.
      */
     accept: function() {
       navigator.mozLoop.stopAlerting();
-      this._conversation.initiate({
-        client: new loop.Client(),
-        outgoing: false
-      });
+      this._conversation.incoming();
     },
 
     /**
      * Declines an incoming call.
      */
     decline: function() {
       navigator.mozLoop.stopAlerting();
       // XXX For now, we just close the window
@@ -196,20 +209,20 @@ loop.conversation = (function(OT, mozL10
      * Decline and block an incoming call
      * @note:
      * - loopToken is the callUrl identifier. It gets set in the panel
      *   after a callUrl is received
      */
     declineAndBlock: function() {
       navigator.mozLoop.stopAlerting();
       var token = navigator.mozLoop.getLoopCharPref('loopToken');
-      var client = new loop.Client();
-      client.deleteCallUrl(token, function(error) {
+      this._client.deleteCallUrl(token, function(error) {
         // XXX The conversation window will be closed when this cb is triggered
         // figure out if there is a better way to report the error to the user
+        // (bug 1048909).
         console.log(error);
       });
       window.close();
     },
 
     /**
      * conversation is the route when the conversation is active. The start
      * route should be navigated to first.
@@ -249,18 +262,22 @@ loop.conversation = (function(OT, mozL10
    */
   function init() {
     // Do the initial L10n setup, we do this before anything
     // else to ensure the L10n environment is setup correctly.
     mozL10n.initialize(navigator.mozLoop);
 
     document.title = mozL10n.get("incoming_call_title");
 
+    var client = new loop.Client();
     router = new ConversationRouter({
-      conversation: new loop.shared.models.ConversationModel({}, {sdk: OT}),
+      client: client,
+      conversation: new loop.shared.models.ConversationModel(
+        {},         // Model attributes
+        {sdk: OT}), // Model dependencies
       notifier: new sharedViews.NotificationListView({el: "#messages"})
     });
     Backbone.history.start();
   }
 
   return {
     ConversationRouter: ConversationRouter,
     IncomingCallView: IncomingCallView,
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -204,21 +204,25 @@ loop.panel = (function(_, mozL10n) {
     },
 
     render: function() {
       // XXX setting elem value from a state (in the callUrl input)
       // makes it immutable ie read only but that is fine in our case.
       // readOnly attr will suppress a warning regarding this issue
       // from the react lib.
       var cx = React.addons.classSet;
+      var inputCSSClass = {
+        "pending": this.state.pending,
+        "callUrl": !this.state.pending
+      };
       return (
         PanelLayout({summary: __("share_link_header_text")}, 
           React.DOM.div({className: "invite"}, 
             React.DOM.input({type: "url", value: this.state.callUrl, readOnly: "true", 
-                   className: cx({'pending': this.state.pending})})
+                   className: cx(inputCSSClass)})
           )
         )
       );
     }
   });
 
   /**
    * Panel view.
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -204,21 +204,25 @@ loop.panel = (function(_, mozL10n) {
     },
 
     render: function() {
       // XXX setting elem value from a state (in the callUrl input)
       // makes it immutable ie read only but that is fine in our case.
       // readOnly attr will suppress a warning regarding this issue
       // from the react lib.
       var cx = React.addons.classSet;
+      var inputCSSClass = {
+        "pending": this.state.pending,
+        "callUrl": !this.state.pending
+      };
       return (
         <PanelLayout summary={__("share_link_header_text")}>
           <div className="invite">
             <input type="url" value={this.state.callUrl} readOnly="true"
-                   className={cx({'pending': this.state.pending})} />
+                   className={cx(inputCSSClass)} />
           </div>
         </PanelLayout>
       );
     }
   });
 
   /**
    * Panel view.
--- a/browser/components/loop/content/shared/js/models.js
+++ b/browser/components/loop/content/shared/js/models.js
@@ -74,106 +74,76 @@ loop.shared.models = (function() {
       this.sdk = options.sdk;
       this.pendingCallTimeout = options.pendingCallTimeout || 20000;
 
       // Ensure that any pending call timer is cleared on disconnect/error
       this.on("session:ended session:error", this._clearPendingCallTimer, this);
     },
 
     /**
-     * Initiates a conversation, requesting call session information to the Loop
-     * server and updates appropriately the current model attributes with the
-     * data.
-     *
-     * Available options:
+     * Starts an incoming conversation.
+     */
+    incoming: function() {
+      this.trigger("call:incoming");
+    },
+
+    /**
+     * Used to indicate that an outgoing call should start any necessary
+     * set-up.
+     */
+    setupOutgoingCall: function() {
+      this.trigger("call:outgoing:setup");
+    },
+
+    /**
+     * Starts an outgoing conversation.
      *
-     * - {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
+     * @param {Object} sessionData The session data received from the
+     *                             server for the outgoing call.
      */
-    initiate: function(options) {
-      options = options || {};
+    outgoing: function(sessionData) {
+      this._clearPendingCallTimer();
 
       // Outgoing call has never reached destination, closing - see bug 1020448
       function handleOutgoingCallTimeout() {
         /*jshint validthis:true */
         if (!this.get("ongoing")) {
           this.trigger("timeout").endSession();
         }
       }
 
-      function handleResult(err, sessionData) {
-        /*jshint validthis:true */
-        this._clearPendingCallTimer();
-
-        if (err) {
-          this._handleServerError(err);
-          return;
-        }
+      // Setup pending call timeout.
+      this._pendingCallTimer = setTimeout(
+        handleOutgoingCallTimeout.bind(this), this.pendingCallTimeout);
 
-        if (options.outgoing) {
-          // Setup pending call timeout.
-          this._pendingCallTimer = setTimeout(
-            handleOutgoingCallTimeout.bind(this), this.pendingCallTimeout);
-        } else {
-          // XXX For incoming calls we might have more than one call queued.
-          // For now, we'll just assume the first call is the right information.
-          // We'll probably really want to be getting this data from the
-          // background worker on the desktop client.
-          // Bug 990714 should fix this.
-          sessionData = sessionData[0];
-        }
-
-        this.setReady(sessionData);
-      }
-
-      if (options.outgoing) {
-        options.client.requestCallInfo(this.get("loopToken"), options.callType,
-          handleResult.bind(this));
-      }
-      else {
-        options.client.requestCallsInfo(this.get("loopVersion"),
-          handleResult.bind(this));
-      }
+      this.setSessionData(sessionData);
+      this.trigger("call:outgoing");
     },
 
     /**
      * Checks that the session is ready.
      *
      * @return {Boolean}
      */
     isSessionReady: function() {
       return !!this.get("sessionId");
     },
 
     /**
-     * Sets session information and triggers the `session:ready` event.
+     * Sets session information.
      *
      * @param {Object} sessionData Conversation session information.
      */
-    setReady: function(sessionData) {
+    setSessionData: function(sessionData) {
       // Explicit property assignment to prevent later "surprises"
       this.set({
         sessionId:    sessionData.sessionId,
         sessionToken: sessionData.sessionToken,
         apiKey:       sessionData.apiKey
-      }).trigger("session:ready", this);
-      return this;
+      });
     },
 
     /**
      * Starts a SDK session and subscribe to call events.
      */
     startSession: function() {
       if (!this.isSessionReady()) {
         throw new Error("Can't start session as it's not ready");
--- a/browser/components/loop/content/shared/js/router.js
+++ b/browser/components/loop/content/shared/js/router.js
@@ -116,19 +116,22 @@ loop.shared.router = (function(l10n) {
      *
      * @param {Object} options Options object.
      */
     constructor: function(options) {
       options = options || {};
       if (!options.conversation) {
         throw new Error("missing required conversation");
       }
+      if (!options.client) {
+        throw new Error("missing required client");
+      }
       this._conversation = options.conversation;
+      this._client = options.client;
 
-      this.listenTo(this._conversation, "session:ready", this._onSessionReady);
       this.listenTo(this._conversation, "session:ended", this._onSessionEnded);
       this.listenTo(this._conversation, "session:peer-hungup",
                                         this._onPeerHungup);
       this.listenTo(this._conversation, "session:network-disconnected",
                                         this._onNetworkDisconnected);
       this.listenTo(this._conversation, "session:connection-error",
                     this._notifyError);
 
@@ -141,33 +144,21 @@ loop.shared.router = (function(l10n) {
      */
     _notifyError: function(error) {
       console.log(error);
       this._notifier.errorL10n("connection_error_see_console_notification");
       this.endCall();
     },
 
     /**
-     * Starts the call. This method should be overriden.
-     */
-    startCall: function() {},
-
-    /**
      * Ends the call. This method should be overriden.
      */
     endCall: function() {},
 
     /**
-     * Session is ready.
-     */
-    _onSessionReady: function() {
-      this.startCall();
-    },
-
-    /**
      * Session has ended. Notifies the user and ends the call.
      */
     _onSessionEnded: function() {
       this.endCall();
     },
 
     /**
      * Peer hung up. Notifies the user and ends the call.
--- a/browser/components/loop/standalone/Makefile
+++ b/browser/components/loop/standalone/Makefile
@@ -10,18 +10,18 @@ install:
 	@npm install
 
 test:
 	@echo "Not implemented yet."
 
 lint:
 	@$(NODE_LOCAL_BIN)/jshint *.js content test
 
-runserver: config
-	@node server.js
+runserver: remove_old_config
+	node server.js
 
 frontend:
 	@echo "Not implemented yet."
 
 # Try hg first, if not fall back to git.
 SOURCE_STAMP := $(shell hg parent --template '{node|short}\n' 2> /dev/null)
 ifndef SOURCE_STAMP
 SOURCE_STAMP := $(shell git describe --always --tag)
@@ -31,13 +31,26 @@ SOURCE_DATE := $(shell hg parent --templ
 ifndef SOURCE_DATE
 SOURCE_DATE := $(shell git log -1 --format="%H%n%aD")
 endif
 
 version:
 	@echo $(SOURCE_STAMP) > content/VERSION.txt
 	@echo $(SOURCE_DATE) >> content/VERSION.txt
 
+
+# The local node server used for client dev (server.js) used to use a static
+# content/config.js.  Now that information is server up dynamically.  This
+# target is depended on by runserver, and removes any copies of that to avoid
+# confusion.
+remove_old_config:
+	@rm -f content/config.js
+
+
+# The services development deployment, however, still wants a static config
+# file, and needs an easy way to generate one.  This target is for folks
+# working with that deployment.
+.PHONY: config
 config:
 	@echo "var loop = loop || {};" > content/config.js
 	@echo "loop.config = loop.config || {};" >> content/config.js
 	@echo "loop.config.serverUrl          = '`echo $(LOOP_SERVER_URL)`';" >> content/config.js
 	@echo "loop.config.pendingCallTimeout = `echo $(LOOP_PENDING_CALL_TIMEOUT)`;" >> content/config.js
--- a/browser/components/loop/standalone/README.md
+++ b/browser/components/loop/standalone/README.md
@@ -9,17 +9,18 @@ NodeJS and npm installed.
 Installation
 ------------
 
     $ make install
 
 Configuration
 -------------
 
-You will need to generate a configuration file, you can do so with:
+If you need a static config.js file for deployment (most people wont; only
+folks deploying the development server will!), you can generate one like this:
 
     $ make config
 
 It will read the configuration from the following env variables and generate the
 appropriate configuration file:
 
 - `LOOP_SERVER_URL` defines the root url of the loop server, without trailing
   slash (default: `http://localhost:5000`).
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -126,17 +126,17 @@ loop.webapp = (function($, _, OT, webL10
       );
     }
   });
 
   /**
    * Conversation launcher view. A ConversationModel is associated and attached
    * as a `model` property.
    */
-  var ConversationFormView = React.createClass({displayName: 'ConversationFormView',
+  var StartConversationView = React.createClass({displayName: 'StartConversationView',
     /**
      * Constructor.
      *
      * Required options:
      * - {loop.shared.model.ConversationModel}    model    Conversation model.
      * - {loop.shared.views.NotificationListView} notifier Notifier component.
      *
      */
@@ -169,29 +169,19 @@ loop.webapp = (function($, _, OT, webL10
     _onSessionError: function(error) {
       console.error(error);
       this.props.notifier.errorL10n("unable_retrieve_call_info");
     },
 
     /**
      * Initiates the call.
      */
-    _initiate: function() {
-      this.props.model.initiate({
-        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",
-        loopServer: loop.config.serverUrl
-      });
-
+    _initiateOutgoingCall: function() {
       this.setState({disableCallButton: true});
+      this.props.model.setupOutgoingCall();
     },
 
     _setConversationTimestamp: function(err, callUrlInfo) {
       if (err) {
         this.props.notifier.errorL10n("unable_retrieve_call_info");
       } else {
         var date = (new Date(callUrlInfo.urlCreationDate * 1000));
         var options = {year: "numeric", month: "long", day: "numeric"};
@@ -226,17 +216,17 @@ loop.webapp = (function($, _, OT, webL10
             React.DOM.p({className: "large-font light-weight-font"}, 
               __("initiate_call_button_label")
             ), 
 
             React.DOM.div({id: "messages"}), 
 
             React.DOM.div({className: "button-group"}, 
               React.DOM.div({className: "flex-padding-1"}), 
-              React.DOM.button({ref: "submitButton", onClick: this._initiate, 
+              React.DOM.button({ref: "submitButton", onClick: this._initiateOutgoingCall, 
                 className: callButtonClasses, 
                 disabled: this.state.disableCallButton}, 
                 __("initiate_call_button"), 
                 React.DOM.i({className: "icon icon-video"})
               ), 
               React.DOM.div({className: "flex-padding-1"})
             ), 
 
@@ -262,40 +252,75 @@ loop.webapp = (function($, _, OT, webL10
       "call/expired":        "expired",
       "call/ongoing/:token": "loadConversation",
       "call/:token":         "initiate"
     },
 
     initialize: function(options) {
       this.helper = options.helper;
       if (!this.helper) {
-        throw new Error("WebappRouter requires an helper object");
+        throw new Error("WebappRouter requires a helper object");
       }
 
       // Load default view
       this.loadView(new HomeView());
 
       this.listenTo(this._conversation, "timeout", this._onTimeout);
-      this.listenTo(this._conversation, "session:expired",
-                    this._onSessionExpired);
     },
 
     _onSessionExpired: function() {
       this.navigate("/call/expired", {trigger: true});
     },
 
     /**
-     * @override {loop.shared.router.BaseConversationRouter.startCall}
+     * Starts the set up of a call, obtaining the required information from the
+     * server.
      */
-    startCall: function() {
-      if (!this._conversation.get("loopToken")) {
+    setupOutgoingCall: function() {
+      var loopToken = this._conversation.get("loopToken");
+      if (!loopToken) {
         this._notifier.errorL10n("missing_conversation_info");
         this.navigate("home", {trigger: true});
       } else {
-        this.navigate("call/ongoing/" + this._conversation.get("loopToken"), {
+        this._conversation.once("call:outgoing", this.startCall, this);
+
+        // XXX For now, we assume both audio and video as there is no
+        // other option to select (bug 1048333)
+        this._client.requestCallInfo(this._conversation.get("loopToken"), "audio-video",
+                                     (err, sessionData) => {
+          if (err) {
+            switch (err.errno) {
+              // loop-server sends 404 + INVALID_TOKEN (errno 105) whenever a token is
+              // missing OR expired; we treat this information as if the url is always
+              // expired.
+              case 105:
+                this._onSessionExpired();
+                break;
+              default:
+                this._notifier.errorL10n("missing_conversation_info");
+                this.navigate("home", {trigger: true});
+                break;
+            }
+            return;
+          }
+          this._conversation.outgoing(sessionData);
+        });
+      }
+    },
+
+    /**
+     * Actually starts the call.
+     */
+    startCall: function() {
+      var loopToken = this._conversation.get("loopToken");
+      if (!loopToken) {
+        this._notifier.errorL10n("missing_conversation_info");
+        this.navigate("home", {trigger: true});
+      } else {
+        this.navigate("call/ongoing/" + loopToken, {
           trigger: true
         });
       }
     },
 
     /**
      * @override {loop.shared.router.BaseConversationRouter.endCall}
      */
@@ -338,23 +363,24 @@ loop.webapp = (function($, _, OT, webL10
      * @param  {String} loopToken Loop conversation token.
      */
     initiate: function(loopToken) {
       // Check if a session is ongoing; if so, terminate it
       if (this._conversation.get("ongoing")) {
         this._conversation.endSession();
       }
       this._conversation.set("loopToken", loopToken);
-      this.loadReactComponent(ConversationFormView({
+
+      var startView = StartConversationView({
         model: this._conversation,
         notifier: this._notifier,
-        client: new loop.StandaloneClient({
-          baseServerUrl: loop.config.serverUrl
-        })
-      }));
+        client: this._client
+      });
+      this._conversation.once("call:outgoing:setup", this.setupOutgoingCall, this);
+      this.loadReactComponent(startView);
     },
 
     /**
      * Loads conversation establishment view.
      *
      */
     loadConversation: function(loopToken) {
       if (!this._conversation.isSessionReady()) {
@@ -385,19 +411,23 @@ loop.webapp = (function($, _, OT, webL10
     }
   };
 
   /**
    * App initialization.
    */
   function init() {
     var helper = new WebappHelper();
+    var client = new loop.StandaloneClient({
+      baseServerUrl: baseServerUrl
+    }),
     router = new WebappRouter({
       helper: helper,
       notifier: new sharedViews.NotificationListView({el: "#messages"}),
+      client: client,
       conversation: new sharedModels.ConversationModel({}, {
         sdk: OT,
         pendingCallTimeout: loop.config.pendingCallTimeout
       })
     });
     Backbone.history.start();
     if (helper.isIOS(navigator.platform)) {
       router.navigate("unsupportedDevice", {trigger: true});
@@ -407,16 +437,16 @@ loop.webapp = (function($, _, OT, webL10
     // Set the 'lang' and 'dir' attributes to <html> when the page is translated
     document.documentElement.lang = document.webL10n.getLanguage();
     document.documentElement.dir = document.webL10n.getDirection();
   }
 
   return {
     baseServerUrl: baseServerUrl,
     CallUrlExpiredView: CallUrlExpiredView,
-    ConversationFormView: ConversationFormView,
+    StartConversationView: StartConversationView,
     HomeView: HomeView,
     init: init,
     PromoteFirefoxView: PromoteFirefoxView,
     WebappHelper: WebappHelper,
     WebappRouter: WebappRouter
   };
 })(jQuery, _, window.OT, document.webL10n);
--- a/browser/components/loop/standalone/content/js/webapp.jsx
+++ b/browser/components/loop/standalone/content/js/webapp.jsx
@@ -126,17 +126,17 @@ loop.webapp = (function($, _, OT, webL10
       );
     }
   });
 
   /**
    * Conversation launcher view. A ConversationModel is associated and attached
    * as a `model` property.
    */
-  var ConversationFormView = React.createClass({
+  var StartConversationView = React.createClass({
     /**
      * Constructor.
      *
      * Required options:
      * - {loop.shared.model.ConversationModel}    model    Conversation model.
      * - {loop.shared.views.NotificationListView} notifier Notifier component.
      *
      */
@@ -169,29 +169,19 @@ loop.webapp = (function($, _, OT, webL10
     _onSessionError: function(error) {
       console.error(error);
       this.props.notifier.errorL10n("unable_retrieve_call_info");
     },
 
     /**
      * Initiates the call.
      */
-    _initiate: function() {
-      this.props.model.initiate({
-        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",
-        loopServer: loop.config.serverUrl
-      });
-
+    _initiateOutgoingCall: function() {
       this.setState({disableCallButton: true});
+      this.props.model.setupOutgoingCall();
     },
 
     _setConversationTimestamp: function(err, callUrlInfo) {
       if (err) {
         this.props.notifier.errorL10n("unable_retrieve_call_info");
       } else {
         var date = (new Date(callUrlInfo.urlCreationDate * 1000));
         var options = {year: "numeric", month: "long", day: "numeric"};
@@ -226,17 +216,17 @@ loop.webapp = (function($, _, OT, webL10
             <p className="large-font light-weight-font">
               {__("initiate_call_button_label")}
             </p>
 
             <div id="messages"></div>
 
             <div className="button-group">
               <div className="flex-padding-1"></div>
-              <button ref="submitButton" onClick={this._initiate}
+              <button ref="submitButton" onClick={this._initiateOutgoingCall}
                 className={callButtonClasses}
                 disabled={this.state.disableCallButton}>
                 {__("initiate_call_button")}
                 <i className="icon icon-video"></i>
               </button>
               <div className="flex-padding-1"></div>
             </div>
 
@@ -262,40 +252,75 @@ loop.webapp = (function($, _, OT, webL10
       "call/expired":        "expired",
       "call/ongoing/:token": "loadConversation",
       "call/:token":         "initiate"
     },
 
     initialize: function(options) {
       this.helper = options.helper;
       if (!this.helper) {
-        throw new Error("WebappRouter requires an helper object");
+        throw new Error("WebappRouter requires a helper object");
       }
 
       // Load default view
       this.loadView(new HomeView());
 
       this.listenTo(this._conversation, "timeout", this._onTimeout);
-      this.listenTo(this._conversation, "session:expired",
-                    this._onSessionExpired);
     },
 
     _onSessionExpired: function() {
       this.navigate("/call/expired", {trigger: true});
     },
 
     /**
-     * @override {loop.shared.router.BaseConversationRouter.startCall}
+     * Starts the set up of a call, obtaining the required information from the
+     * server.
      */
-    startCall: function() {
-      if (!this._conversation.get("loopToken")) {
+    setupOutgoingCall: function() {
+      var loopToken = this._conversation.get("loopToken");
+      if (!loopToken) {
         this._notifier.errorL10n("missing_conversation_info");
         this.navigate("home", {trigger: true});
       } else {
-        this.navigate("call/ongoing/" + this._conversation.get("loopToken"), {
+        this._conversation.once("call:outgoing", this.startCall, this);
+
+        // XXX For now, we assume both audio and video as there is no
+        // other option to select (bug 1048333)
+        this._client.requestCallInfo(this._conversation.get("loopToken"), "audio-video",
+                                     (err, sessionData) => {
+          if (err) {
+            switch (err.errno) {
+              // loop-server sends 404 + INVALID_TOKEN (errno 105) whenever a token is
+              // missing OR expired; we treat this information as if the url is always
+              // expired.
+              case 105:
+                this._onSessionExpired();
+                break;
+              default:
+                this._notifier.errorL10n("missing_conversation_info");
+                this.navigate("home", {trigger: true});
+                break;
+            }
+            return;
+          }
+          this._conversation.outgoing(sessionData);
+        });
+      }
+    },
+
+    /**
+     * Actually starts the call.
+     */
+    startCall: function() {
+      var loopToken = this._conversation.get("loopToken");
+      if (!loopToken) {
+        this._notifier.errorL10n("missing_conversation_info");
+        this.navigate("home", {trigger: true});
+      } else {
+        this.navigate("call/ongoing/" + loopToken, {
           trigger: true
         });
       }
     },
 
     /**
      * @override {loop.shared.router.BaseConversationRouter.endCall}
      */
@@ -338,23 +363,24 @@ loop.webapp = (function($, _, OT, webL10
      * @param  {String} loopToken Loop conversation token.
      */
     initiate: function(loopToken) {
       // Check if a session is ongoing; if so, terminate it
       if (this._conversation.get("ongoing")) {
         this._conversation.endSession();
       }
       this._conversation.set("loopToken", loopToken);
-      this.loadReactComponent(ConversationFormView({
+
+      var startView = StartConversationView({
         model: this._conversation,
         notifier: this._notifier,
-        client: new loop.StandaloneClient({
-          baseServerUrl: loop.config.serverUrl
-        })
-      }));
+        client: this._client
+      });
+      this._conversation.once("call:outgoing:setup", this.setupOutgoingCall, this);
+      this.loadReactComponent(startView);
     },
 
     /**
      * Loads conversation establishment view.
      *
      */
     loadConversation: function(loopToken) {
       if (!this._conversation.isSessionReady()) {
@@ -385,19 +411,23 @@ loop.webapp = (function($, _, OT, webL10
     }
   };
 
   /**
    * App initialization.
    */
   function init() {
     var helper = new WebappHelper();
+    var client = new loop.StandaloneClient({
+      baseServerUrl: baseServerUrl
+    }),
     router = new WebappRouter({
       helper: helper,
       notifier: new sharedViews.NotificationListView({el: "#messages"}),
+      client: client,
       conversation: new sharedModels.ConversationModel({}, {
         sdk: OT,
         pendingCallTimeout: loop.config.pendingCallTimeout
       })
     });
     Backbone.history.start();
     if (helper.isIOS(navigator.platform)) {
       router.navigate("unsupportedDevice", {trigger: true});
@@ -407,16 +437,16 @@ loop.webapp = (function($, _, OT, webL10
     // Set the 'lang' and 'dir' attributes to <html> when the page is translated
     document.documentElement.lang = document.webL10n.getLanguage();
     document.documentElement.dir = document.webL10n.getDirection();
   }
 
   return {
     baseServerUrl: baseServerUrl,
     CallUrlExpiredView: CallUrlExpiredView,
-    ConversationFormView: ConversationFormView,
+    StartConversationView: StartConversationView,
     HomeView: HomeView,
     init: init,
     PromoteFirefoxView: PromoteFirefoxView,
     WebappHelper: WebappHelper,
     WebappRouter: WebappRouter
   };
 })(jQuery, _, window.OT, document.webL10n);
--- a/browser/components/loop/standalone/server.js
+++ b/browser/components/loop/standalone/server.js
@@ -1,17 +1,55 @@
 /* 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/. */
 
 var express = require('express');
 var app = express();
 
+var port = process.env.PORT || 3000;
+var loopServerPort = process.env.LOOP_SERVER_PORT || 5000;
+
+app.get('/content/config.js', function (req, res) {
+  "use strict";
+
+  res.set('Content-Type', 'text/javascript');
+  res.send(
+    "var loop = loop || {};" +
+    "loop.config = loop.config || {};" +
+    "loop.config.serverUrl = 'http://localhost:" + loopServerPort + "';" +
+    "loop.config.pendingCallTimeout = 20000;"
+  );
+
+});
+
 // This lets /test/ be mapped to the right place for running tests
-app.use(express.static(__dirname + '/../'));
-// This lets /content/ be mappy right for the static contents.
-app.use(express.static(__dirname + '/'));
+app.use('/', express.static(__dirname + '/../'));
+// This lets /content/ be mapped right for the static contents.
+app.use('/', express.static(__dirname + '/'));
+
+var server = app.listen(port);
 
-app.listen(3000);
-console.log("Serving repository root over HTTP at http://localhost:3000/");
-console.log("Static contents are available at http://localhost:3000/content/");
-console.log("Tests are viewable at http://localhost:3000/test/");
+var baseUrl = "http://localhost:" + port + "/";
+
+console.log("Serving repository root over HTTP at " + baseUrl);
+console.log("Static contents are available at " + baseUrl + "content/");
+console.log("Tests are viewable at " + baseUrl + "test/");
 console.log("Use this for development only.");
+
+// Handle SIGTERM signal.
+function shutdown(cb) {
+  "use strict";
+
+  try {
+    server.close(function () {
+      process.exit(0);
+      if (cb !== undefined) {
+        cb();
+      }
+    });
+
+  } catch (ex) {
+    console.log(ex + " while calling server.close)");
+  }
+}
+
+process.on('SIGTERM', shutdown);
--- a/browser/components/loop/test/desktop-local/conversation_test.js
+++ b/browser/components/loop/test/desktop-local/conversation_test.js
@@ -98,35 +98,39 @@ describe("loop.conversation", function()
     it("should start Backbone history", function() {
       loop.conversation.init();
 
       sinon.assert.calledOnce(Backbone.history.start);
     });
   });
 
   describe("ConversationRouter", function() {
-    var conversation;
+    var conversation, client;
 
     beforeEach(function() {
+      client = new loop.Client();
       conversation = new loop.shared.models.ConversationModel({}, {
         sdk: {},
-        pendingCallTimeout: 1000
+        pendingCallTimeout: 1000,
       });
-      sandbox.stub(conversation, "initiate");
+      sandbox.stub(client, "requestCallsInfo");
+      sandbox.stub(conversation, "setSessionData");
     });
 
     describe("Routes", function() {
       var router;
 
       beforeEach(function() {
         router = new ConversationRouter({
+          client: client,
           conversation: conversation,
           notifier: notifier
         });
         sandbox.stub(router, "loadView");
+        sandbox.stub(conversation, "incoming");
       });
 
       describe("#incoming", function() {
 
         // XXX refactor to Just Work with "sandbox.stubComponent" or else
         // just pass in the sandbox and put somewhere generally usable
 
         function stubComponent(obj, component, mockTagName){
@@ -139,56 +143,88 @@ describe("loop.conversation", function()
           return sandbox.stub(obj, component, reactClass);
         }
 
         beforeEach(function() {
           sandbox.stub(router, "loadReactComponent");
           stubComponent(loop.conversation, "IncomingCallView");
         });
 
+        it("should start alerting", function() {
+          sandbox.stub(navigator.mozLoop, "startAlerting");
+          router.incoming("fakeVersion");
+
+          sinon.assert.calledOnce(navigator.mozLoop.startAlerting);
+        });
+
         it("should set the loopVersion on the conversation model", function() {
           router.incoming("fakeVersion");
 
           expect(conversation.get("loopVersion")).to.equal("fakeVersion");
         });
 
-        it("should display the incoming call view", function() {
+        it("should call requestCallsInfo on the client",
+          function() {
+            router.incoming(42);
+
+            sinon.assert.calledOnce(client.requestCallsInfo);
+            sinon.assert.calledWith(client.requestCallsInfo, 42);
+          });
+
+        it("should display an error if requestCallsInfo returns an error",
+          function(){
+            client.requestCallsInfo.callsArgWith(1, "failed");
+
+            router.incoming(42);
+
+            sinon.assert.calledOnce(notifier.errorL10n);
+          });
+
+        describe("requestCallsInfo successful", function() {
+          var fakeSessionData;
+
+          beforeEach(function() {
+            fakeSessionData  = {
+              sessionId:    "sessionId",
+              sessionToken: "sessionToken",
+              apiKey:       "apiKey"
+            };
+
+            client.requestCallsInfo.callsArgWith(1, null, [fakeSessionData]);
+          });
+
+          it("should store the session data", function() {
+            router.incoming(42);
+
+            sinon.assert.calledOnce(conversation.setSessionData);
+            sinon.assert.calledWithExactly(conversation.setSessionData,
+                                           fakeSessionData);
+          });
+
+          it("should display the incoming call view", function() {
             router.incoming("fakeVersion");
 
             sinon.assert.calledOnce(loop.conversation.IncomingCallView);
             sinon.assert.calledWithExactly(loop.conversation.IncomingCallView,
                                            {model: conversation});
             sinon.assert.calledOnce(router.loadReactComponent);
             sinon.assert.calledWith(router.loadReactComponent,
               sinon.match(function(value) {
                 return TestUtils.isDescriptorOfType(value,
                   loop.conversation.IncomingCallView);
               }));
-        });
-
-        it("should start alerting", function() {
-          sandbox.stub(navigator.mozLoop, "startAlerting");
-          router.incoming("fakeVersion");
-
-          sinon.assert.calledOnce(navigator.mozLoop.startAlerting);
+          });
         });
       });
 
       describe("#accept", function() {
         it("should initiate the conversation", function() {
           router.accept();
 
-          sinon.assert.calledOnce(conversation.initiate);
-          sinon.assert.calledWithMatch(conversation.initiate, {
-            client: {
-              mozLoop: navigator.mozLoop,
-              settings: {}
-            },
-            outgoing: false
-          });
+          sinon.assert.calledOnce(conversation.incoming);
         });
 
         it("should stop alerting", function() {
           sandbox.stub(navigator.mozLoop, "stopAlerting");
           router.accept();
 
           sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
         });
@@ -332,24 +368,27 @@ describe("loop.conversation", function()
           sessionId:    "sessionId",
           sessionToken: "sessionToken",
           apiKey:       "apiKey"
         };
         sandbox.stub(loop.conversation.ConversationRouter.prototype,
                      "navigate");
         conversation.set("loopToken", "fakeToken");
         router = new loop.conversation.ConversationRouter({
+          client: client,
           conversation: conversation,
           notifier: notifier
         });
       });
 
-      it("should navigate to call/ongoing once the call session is ready",
+      it("should navigate to call/ongoing once the call is ready",
         function() {
-          conversation.setReady(fakeSessionData);
+          router.incoming(42);
+
+          conversation.incoming();
 
           sinon.assert.calledOnce(router.navigate);
           sinon.assert.calledWith(router.navigate, "call/ongoing");
         });
 
       it("should navigate to call/feedback when the call session ends",
         function() {
           conversation.trigger("session:ended");
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/functional/hanging_threads.py
@@ -0,0 +1,27 @@
+import sys
+import threading
+import time
+
+# XXX We want to convince ourselves that the race condition this file works
+# around, tracked by bug 1046873, is gone, and get rid of this file entirely
+# before we hook this up to Tbpl, so that we don't introduce an intermittent
+# failure.
+#
+# reduced down from <https://gist.github.com/niccokunzmann/6038331>; importing
+# this class just so happens to allow mach to exit rather than hanging after
+# our tests are run.
+
+
+def monitor():
+    while 1:
+        time.sleep(1.)
+        _ = sys._current_frames()
+
+
+def start_monitoring():
+    thread = threading.Thread(target=monitor)
+    thread.daemon = True
+    thread.start()
+    return thread
+
+start_monitoring()
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/functional/manifest.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+b2g = false
+browser = true
+qemu = false
+
+[test_get_url.py]
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/functional/test_get_url.py
@@ -0,0 +1,145 @@
+from marionette_test import MarionetteTestCase
+from marionette.errors import NoSuchElementException
+from marionette.errors import StaleElementException
+from by import By
+# noinspection PyUnresolvedReferences
+from marionette.wait import Wait
+import os
+
+# noinspection PyUnresolvedReferences
+from mozprocess import processhandler
+import urlparse
+
+# XXX We want to convince ourselves that the race condition that importing
+# hanging_threads.py works around, tracked by bug 1046873, is gone, and get
+# rid of this inclusion entirely before we hook our functional tests up to
+# Tbpl, so that we don't introduce an intermittent failure.
+import sys
+sys.path.append(os.path.dirname(__file__))
+import hanging_threads
+
+CONTENT_SERVER_PORT = 3001
+LOOP_SERVER_PORT = 5001
+
+CONTENT_SERVER_COMMAND = ["make", "runserver"]
+CONTENT_SERVER_ENV = os.environ.copy()
+# Set PORT so that it does not interfere with any other
+# development server that might be running
+CONTENT_SERVER_ENV.update({"PORT": str(CONTENT_SERVER_PORT),
+                           "LOOP_SERVER_PORT": str(LOOP_SERVER_PORT)})
+
+WEB_APP_URL = "http://localhost:" + str(CONTENT_SERVER_PORT) + \
+              "/content/#call/{token}"
+
+LOOP_SERVER_COMMAND = ["make", "runserver"]
+LOOP_SERVER_ENV = os.environ.copy()
+# Set PORT so that it does not interfere with any other
+# development server that might be running
+LOOP_SERVER_ENV.update({"NODE_ENV": "dev", "PORT": str(LOOP_SERVER_PORT),
+                        "WEB_APP_URL": WEB_APP_URL})
+
+
+class LoopTestServers:
+    def __init__(self):
+        self.loop_server = self.start_loop_server()
+        self.content_server = self.start_content_server()
+
+    @staticmethod
+    def start_loop_server():
+        loop_server_location = os.environ.get('LOOP_SERVER')
+        if loop_server_location is None:
+            raise Exception('LOOP_SERVER variable not set')
+
+        os.chdir(loop_server_location)
+
+        p = processhandler.ProcessHandler(LOOP_SERVER_COMMAND,
+                                          env=LOOP_SERVER_ENV)
+        p.run()
+        return p
+
+    @staticmethod
+    def start_content_server():
+        content_server_location = os.path.join(os.path.dirname(__file__),
+                                               "../../standalone")
+        os.chdir(content_server_location)
+
+        p = processhandler.ProcessHandler(CONTENT_SERVER_COMMAND,
+                                          env=CONTENT_SERVER_ENV)
+        p.run()
+        return p
+
+    def shutdown(self):
+        self.content_server.kill()
+        self.loop_server.kill()
+
+
+class TestGetUrl(MarionetteTestCase):
+    # XXX Move this to setup class so it doesn't restart the server
+    # after every test.  This can happen when we write our second test,
+    # expected to be in bug 976116.
+    def setUp(self):
+        # start server
+        self.loop_test_servers = LoopTestServers()
+
+        MarionetteTestCase.setUp(self)
+
+        # Unfortunately, enforcing preferences currently comes with the side
+        # effect of launching and restarting the browser before running the
+        # real functional tests.  Bug 1048554 has been filed to track this.
+        preferences = {"loop.server": "http://localhost:" + str(LOOP_SERVER_PORT)}
+        self.marionette.enforce_gecko_prefs(preferences)
+
+        # this is browser chrome, kids, not the content window just yet
+        self.marionette.set_context("chrome")
+
+    def switch_to_panel(self):
+        button = self.marionette.find_element(By.ID, "loop-call-button")
+
+        # click the element
+        button.click()
+
+        # switch to the frame
+        frame = self.marionette.find_element(By.ID, "loop")
+        self.marionette.switch_to_frame(frame)
+
+    # taken from https://github.com/mozilla-b2g/gaia/blob/master/tests/python/gaia-ui-tests/gaiatest/gaia_test.py#L858
+    # XXX factor out into utility object for use by other tests, this can
+    # when we write our second test, in bug 976116
+    def wait_for_element_displayed(self, by, locator, timeout=None):
+        Wait(self.marionette, timeout,
+             ignored_exceptions=[NoSuchElementException, StaleElementException])\
+            .until(lambda m: m.find_element(by, locator).is_displayed())
+        return self.marionette.find_element(by, locator)
+
+    def test_get_url(self):
+        self.switch_to_panel()
+
+        # get and check for a call url
+        url_input_element = self.wait_for_element_displayed(By.TAG_NAME,
+                                                            "input")
+
+        # wait for pending state to finish
+        self.assertEqual(url_input_element.get_attribute("class"), "pending",
+                         "expect the input to be pending")
+
+        # get and check the input (the "callUrl" class is only added after
+        # the pending class is removed and the URL has arrived).
+        #
+        # XXX should investigate getting rid of the fragile and otherwise
+        # unnecessary callUrl class and replacing this with a By.CSS_SELECTOR
+        # and some possible combination of :not and/or an attribute selector
+        # once bug 1048551 is fixed.
+        url_input_element = self.wait_for_element_displayed(By.CLASS_NAME,
+                                                            "callUrl")
+        call_url = url_input_element.get_attribute("value")
+        self.assertNotEqual(call_url, u'',
+                            "input is populated with call URL after pending"
+                            " is finished")
+
+        self.assertIn(urlparse.urlparse(call_url).scheme, ['http', 'https'],
+                      "call URL returned by server " + call_url +
+                      " has invalid scheme")
+
+    def tearDown(self):
+        self.loop_test_servers.shutdown()
+        MarionetteTestCase.tearDown(self)
--- a/browser/components/loop/test/shared/models_test.js
+++ b/browser/components/loop/test/shared/models_test.js
@@ -73,161 +73,84 @@ describe("loop.shared.models", function(
         fakeClient = {
           requestCallInfo: sandbox.stub(),
           requestCallsInfo: sandbox.stub()
         };
         requestCallInfoStub = fakeClient.requestCallInfo;
         requestCallsInfoStub = fakeClient.requestCallsInfo;
       });
 
-      describe("#initiate", function() {
-        beforeEach(function() {
-          sandbox.stub(conversation, "endSession");
-        });
-
-        it("call requestCallInfo on the client for outgoing calls",
-          function() {
-            conversation.initiate({
-              client: fakeClient,
-              outgoing: true,
-              callType: "audio"
-            });
-
-            sinon.assert.calledOnce(requestCallInfoStub);
-            sinon.assert.calledWith(requestCallInfoStub, "fakeToken", "audio");
+      describe("#incoming", function() {
+        it("should trigger a `call:incoming` event", function(done) {
+          conversation.once("call:incoming", function() {
+            done();
           });
 
-        it("should not call requestCallsInfo on the client for outgoing calls",
-          function() {
-            conversation.initiate({
-              client: fakeClient,
-              outgoing: true,
-              callType: "audio"
-            });
-
-            sinon.assert.notCalled(requestCallsInfoStub);
-          });
+          conversation.incoming();
+        });
+      });
 
-        it("call requestCallsInfo on the client for incoming calls",
-          function() {
-            conversation.set("loopVersion", 42);
-            conversation.initiate({
-              client: fakeClient,
-              outgoing: false
-            });
-
-            sinon.assert.calledOnce(requestCallsInfoStub);
-            sinon.assert.calledWith(requestCallsInfoStub, 42);
-          });
-
-        it("should not call requestCallInfo on the client for incoming calls",
-          function() {
-            conversation.initiate({
-              client: fakeClient,
-              outgoing: false
-            });
-
-            sinon.assert.notCalled(requestCallInfoStub);
+      describe("#setupOutgoingCall", function() {
+        it("should trigger a `call:outgoing:setup` event", function(done) {
+          conversation.once("call:outgoing:setup", function() {
+            done();
           });
 
-        it("should update conversation session information from server data",
-          function() {
-            sandbox.stub(conversation, "setReady");
-            requestCallInfoStub.callsArgWith(2, null, fakeSessionData);
+          conversation.setupOutgoingCall();
+        });
+      });
 
-            conversation.initiate({
-              client: fakeClient,
-              outgoing: true
-            });
+      describe("#outgoing", function() {
+        beforeEach(function() {
+          sandbox.stub(conversation, "endSession");
+          sandbox.stub(conversation, "setSessionData");
+        });
 
-            sinon.assert.calledOnce(conversation.setReady);
-            sinon.assert.calledWith(conversation.setReady, fakeSessionData);
-          });
+        it("should save the sessionData", function() {
+          conversation.outgoing(fakeSessionData);
 
-        it("should trigger a `session:error` event errno is undefined",
-          function(done) {
-            var errMsg = "HTTP 500 Server Error; fake";
-            var err = new Error(errMsg);
-            requestCallInfoStub.callsArgWith(2, err);
+          sinon.assert.calledOnce(conversation.setSessionData);
+        });
 
-            conversation.on("session:error", function(err) {
-              expect(err.message).eql(errMsg);
-              done();
-            }).initiate({ client: fakeClient, outgoing: true });
+        it("should trigger a `call:outgoing` event", function(done) {
+          conversation.once("call:outgoing", function() {
+            done();
           });
 
-        it("should trigger a `session:error` event when errno is not 105",
-          function(done) {
-            var errMsg = "HTTP 400 Bad Request; fake";
-            var err = new Error(errMsg);
-            err.errno = 101;
-            requestCallInfoStub.callsArgWith(2, err);
-
-            conversation.on("session:error", function(err) {
-              expect(err.message).eql(errMsg);
-              done();
-            }).initiate({ client: fakeClient, outgoing: true });
-          });
-
-        it("should trigger a `session:expired` event when errno is 105",
-          function(done) {
-            var err = new Error("HTTP 404 Not Found; fake");
-            err.errno = 105;
-            requestCallInfoStub.callsArgWith(2, err);
-
-            conversation.on("session:expired", function(err2) {
-              expect(err2).eql(err);
-              done();
-            }).initiate({ client: fakeClient, outgoing: true });
-          });
+          conversation.outgoing();
+        });
 
         it("should end the session on outgoing call timeout", function() {
-          requestCallInfoStub.callsArgWith(2, null, fakeSessionData);
-
-          conversation.initiate({
-            client: fakeClient,
-            outgoing: true
-          });
+          conversation.outgoing();
 
           sandbox.clock.tick(1001);
 
           sinon.assert.calledOnce(conversation.endSession);
         });
 
         it("should trigger a `timeout` event on outgoing call timeout",
           function(done) {
-            requestCallInfoStub.callsArgWith(2, null, fakeSessionData);
-
             conversation.once("timeout", function() {
               done();
             });
 
-            conversation.initiate({
-              client: fakeClient,
-              outgoing: true
-            });
+            conversation.outgoing();
 
             sandbox.clock.tick(1001);
           });
       });
 
-      describe("#setReady", function() {
+      describe("#setSessionData", function() {
         it("should update conversation session information", function() {
-          conversation.setReady(fakeSessionData);
+          conversation.setSessionData(fakeSessionData);
 
           expect(conversation.get("sessionId")).eql("sessionId");
           expect(conversation.get("sessionToken")).eql("sessionToken");
           expect(conversation.get("apiKey")).eql("apiKey");
         });
-
-        it("should trigger a `session:ready` event", function(done) {
-          conversation.on("session:ready", function() {
-            done();
-          }).setReady(fakeSessionData);
-        });
       });
 
       describe("#startSession", function() {
         var model;
 
         beforeEach(function() {
           sandbox.stub(sharedModels.ConversationModel.prototype,
                        "_clearPendingCallTimer");
--- a/browser/components/loop/test/shared/router_test.js
+++ b/browser/components/loop/test/shared/router_test.js
@@ -92,47 +92,52 @@ describe("loop.shared.router", function(
     });
   });
 
   describe("BaseConversationRouter", function() {
     var conversation, TestRouter;
 
     beforeEach(function() {
       TestRouter = loop.shared.router.BaseConversationRouter.extend({
-        startCall: sandbox.spy(),
         endCall: sandbox.spy()
       });
       conversation = new loop.shared.models.ConversationModel({
         loopToken: "fakeToken"
       }, {
         sdk: {},
         pendingCallTimeout: 1000
       });
     });
 
     describe("#constructor", function() {
       it("should require a ConversationModel instance", function() {
         expect(function() {
-          new TestRouter();
+          new TestRouter({ client: {} });
         }).to.Throw(Error, /missing required conversation/);
       });
+      it("should require a Client instance", function() {
+        expect(function() {
+          new TestRouter({ conversation: {} });
+        }).to.Throw(Error, /missing required client/);
+      });
     });
 
     describe("Events", function() {
       var router, fakeSessionData;
 
       beforeEach(function() {
         fakeSessionData = {
           sessionId:    "sessionId",
           sessionToken: "sessionToken",
           apiKey:       "apiKey"
         };
         router = new TestRouter({
           conversation: conversation,
-          notifier: notifier
+          notifier: notifier,
+          client: {}
         });
       });
 
       describe("session:connection-error", function() {
 
         it("should warn the user when .connect() call fails", function() {
           conversation.trigger("session:connection-error");
 
@@ -144,22 +149,16 @@ describe("loop.shared.router", function(
           conversation.trigger("session:connection-error");
 
           sinon.assert.calledOnce(router.endCall);
           sinon.assert.calledWithExactly(router.endCall);
         });
 
       });
 
-      it("should call startCall() once the call session is ready", function() {
-        conversation.trigger("session:ready");
-
-        sinon.assert.calledOnce(router.startCall);
-      });
-
       it("should call endCall() when conversation ended", function() {
         conversation.trigger("session:ended");
 
         sinon.assert.calledOnce(router.endCall);
       });
 
       it("should warn the user when peer hangs up", function() {
         conversation.trigger("session:peer-hungup");
--- a/browser/components/loop/test/standalone/webapp_test.js
+++ b/browser/components/loop/test/standalone/webapp_test.js
@@ -12,16 +12,19 @@ describe("loop.webapp", function() {
 
   var sharedModels = loop.shared.models,
       sharedViews = loop.shared.views,
       sandbox,
       notifier;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
+    // conversation#outgoing sets timers, so we need to use fake ones
+    // to prevent random failures.
+    sandbox.useFakeTimers();
     notifier = {
       notify: sandbox.spy(),
       warn: sandbox.spy(),
       warnL10n: sandbox.spy(),
       error: sandbox.spy(),
       errorL10n: sandbox.spy(),
     };
     loop.config.pendingCallTimeout = 1000;
@@ -64,26 +67,31 @@ describe("loop.webapp", function() {
 
       sinon.assert.calledOnce(WebappRouter.prototype.navigate);
       sinon.assert.calledWithExactly(WebappRouter.prototype.navigate,
                                      "unsupportedBrowser", {trigger: true});
     });
   });
 
   describe("WebappRouter", function() {
-    var router, conversation;
+    var router, conversation, client;
 
     beforeEach(function() {
+      client = new loop.StandaloneClient({
+        baseServerUrl: "http://fake.example.com"
+      });
+      sandbox.stub(client, "requestCallInfo");
       conversation = new sharedModels.ConversationModel({}, {
         sdk: {},
         pendingCallTimeout: 1000
       });
       sandbox.stub(loop.webapp.WebappRouter.prototype, "loadReactComponent");
       router = new loop.webapp.WebappRouter({
         helper: {},
+        client: client,
         conversation: conversation,
         notifier: notifier
       });
       sandbox.stub(router, "loadView");
       sandbox.stub(router, "navigate");
     });
 
     describe("#startCall", function() {
@@ -157,24 +165,24 @@ describe("loop.webapp", function() {
 
       describe("#initiate", function() {
         it("should set the token on the conversation model", function() {
           router.initiate("fakeToken");
 
           expect(conversation.get("loopToken")).eql("fakeToken");
         });
 
-        it("should load the ConversationFormView", function() {
+        it("should load the StartConversationView", function() {
           router.initiate("fakeToken");
 
           sinon.assert.calledOnce(router.loadReactComponent);
           sinon.assert.calledWithExactly(router.loadReactComponent,
             sinon.match(function(value) {
               return React.addons.TestUtils.isDescriptorOfType(
-                value, loop.webapp.ConversationFormView);
+                value, loop.webapp.StartConversationView);
             }));
         });
 
         // https://bugzilla.mozilla.org/show_bug.cgi?id=991118
         it("should terminate any ongoing call session", function() {
           sinon.stub(conversation, "endSession");
           conversation.set("ongoing", true);
 
@@ -237,17 +245,18 @@ describe("loop.webapp", function() {
           sessionToken: "sessionToken",
           apiKey:       "apiKey"
         };
         conversation.set("loopToken", "fakeToken");
       });
 
       it("should navigate to call/ongoing/:token once call session is ready",
         function() {
-          conversation.trigger("session:ready");
+          router.setupOutgoingCall();
+          conversation.outgoing(fakeSessionData);
 
           sinon.assert.calledOnce(router.navigate);
           sinon.assert.calledWith(router.navigate, "call/ongoing/fakeToken");
         });
 
       it("should navigate to call/{token} when conversation ended", function() {
         conversation.trigger("session:ended");
 
@@ -265,27 +274,91 @@ describe("loop.webapp", function() {
       it("should navigate to call/{token} when network disconnects",
         function() {
           conversation.trigger("session:network-disconnected");
 
           sinon.assert.calledOnce(router.navigate);
           sinon.assert.calledWithMatch(router.navigate, "call/fakeToken");
         });
 
-      it("should navigate to call/expired when a session:expired event is " +
-         "received", function() {
-        conversation.trigger("session:expired");
+      describe("#setupOutgoingCall", function() {
+        beforeEach(function() {
+          router.initiate();
+        });
+
+        describe("No loop token", function() {
+          it("should navigate to home", function() {
+            conversation.setupOutgoingCall();
+
+            sinon.assert.calledOnce(router.navigate);
+            sinon.assert.calledWithMatch(router.navigate, "home");
+          });
+
+          it("should display an error", function() {
+            conversation.setupOutgoingCall();
+
+            sinon.assert.calledOnce(notifier.errorL10n);
+          });
+        });
+
+        describe("Has loop token", function() {
+          beforeEach(function() {
+            conversation.set("loopToken", "fakeToken");
+            sandbox.stub(conversation, "outgoing");
+          });
+
+          it("should call requestCallInfo on the client",
+            function() {
+              conversation.setupOutgoingCall();
+
+              sinon.assert.calledOnce(client.requestCallInfo);
+              sinon.assert.calledWith(client.requestCallInfo, "fakeToken",
+                                      "audio-video");
+            });
 
-        sinon.assert.calledOnce(router.navigate);
-        sinon.assert.calledWith(router.navigate, "/call/expired");
+          describe("requestCallInfo response handling", function() {
+            it("should navigate to call/expired when a session has expired",
+               function() {
+                client.requestCallInfo.callsArgWith(2, {errno: 105});
+                conversation.setupOutgoingCall();
+
+                sinon.assert.calledOnce(router.navigate);
+                sinon.assert.calledWith(router.navigate, "/call/expired");
+              });
+
+            it("should navigate to home on any other error", function() {
+              client.requestCallInfo.callsArgWith(2, {errno: 104});
+              conversation.setupOutgoingCall();
+
+              sinon.assert.calledOnce(router.navigate);
+              sinon.assert.calledWith(router.navigate, "home");
+              });
+
+            it("should notify the user on any other error", function() {
+              client.requestCallInfo.callsArgWith(2, {errno: 104});
+              conversation.setupOutgoingCall();
+
+              sinon.assert.calledOnce(notifier.errorL10n);
+            });
+
+            it("should call outgoing on the conversation model when details " +
+               "are successfully received", function() {
+                client.requestCallInfo.callsArgWith(2, null, fakeSessionData);
+                conversation.setupOutgoingCall();
+
+                sinon.assert.calledOnce(conversation.outgoing);
+                sinon.assert.calledWithExactly(conversation.outgoing, fakeSessionData);
+              });
+          });
+        });
       });
     });
   });
 
-  describe("ConversationFormView", function() {
+  describe("StartConversationView", function() {
     var conversation;
 
     beforeEach(function() {
       conversation = new sharedModels.ConversationModel({}, {
         sdk: {},
         pendingCallTimeout: 1000});
     });
 
@@ -293,52 +366,49 @@ describe("loop.webapp", function() {
       it("should require a conversation option", function() {
         expect(function() {
           new loop.webapp.WebappRouter();
         }).to.Throw(Error, /missing required conversation/);
       });
     });
 
     describe("#initiate", function() {
-      var conversation, initiate, view, fakeSubmitEvent, requestCallUrlInfo;
+      var conversation, setupOutgoingCall, view, fakeSubmitEvent,
+          requestCallUrlInfo;
 
       beforeEach(function() {
         conversation = new sharedModels.ConversationModel({}, {
           sdk: {},
           pendingCallTimeout: 1000
         });
 
         fakeSubmitEvent = {preventDefault: sinon.spy()};
-        initiate = sinon.stub(conversation, "initiate");
+        setupOutgoingCall = sinon.stub(conversation, "setupOutgoingCall");
 
         var standaloneClientStub = {
           requestCallUrlInfo: function(token, cb) {
             cb(null, {urlCreationDate: 0});
           },
           settings: {baseServerUrl: loop.webapp.baseServerUrl}
-        }
+        };
 
         view = React.addons.TestUtils.renderIntoDocument(
-            loop.webapp.ConversationFormView({
+            loop.webapp.StartConversationView({
               model: conversation,
               notifier: notifier,
               client: standaloneClientStub
             })
         );
       });
 
       it("should start the conversation establishment process", function() {
         var button = view.getDOMNode().querySelector("button");
         React.addons.TestUtils.Simulate.click(button);
 
-        sinon.assert.calledOnce(initiate);
-        sinon.assert.calledWith(initiate, sinon.match(function (value) {
-          return !!value.outgoing &&
-            (value.client.settings.baseServerUrl === loop.webapp.baseServerUrl)
-        }, "outgoing: true && correct baseServerUrl"));
+        sinon.assert.calledOnce(setupOutgoingCall);
       });
 
       it("should disable current form once session is initiated", function() {
         conversation.set("loopToken", "fake");
 
         var button = view.getDOMNode().querySelector("button");
         React.addons.TestUtils.Simulate.click(button);
 
@@ -368,17 +438,17 @@ describe("loop.webapp", function() {
           sdk: {},
           pendingCallTimeout: 1000
         });
 
         sandbox.spy(conversation, "listenTo");
         requestCallUrlInfo = sandbox.stub();
 
         view = React.addons.TestUtils.renderIntoDocument(
-            loop.webapp.ConversationFormView({
+            loop.webapp.StartConversationView({
               model: conversation,
               notifier: notifier,
               client: {requestCallUrlInfo: requestCallUrlInfo}
             })
           );
       });
 
       it("should call requestCallUrlInfo", function() {
--- a/browser/components/loop/ui/index.html
+++ b/browser/components/loop/ui/index.html
@@ -11,17 +11,17 @@
     <link rel="stylesheet" type="text/css" href="../content/shared/css/panel.css">
     <link rel="stylesheet" type="text/css" href="ui-showcase.css">
  </head>
   <body>
     <div id="main"></div>
     <script src="fake-mozLoop.js"></script>
     <script src="fake-l10n.js"></script>
     <script src="../content/libs/sdk.js"></script>
-    <script src="../content/shared/libs/react-0.10.0.js"></script>
+    <script src="../content/shared/libs/react-0.11.1.js"></script>
     <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/js/feedbackApiClient.js"></script>
     <script src="../content/shared/js/utils.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>
--- a/browser/devtools/webide/content/addons.js
+++ b/browser/devtools/webide/content/addons.js
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const Cu = Components.utils;
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
 const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
 const {GetAvailableAddons, ForgetAddonsList} = require("devtools/webide/addons");
-const Strings = Services.strings.createBundle("chrome://webide/content/webide.properties");
+const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
 
 window.addEventListener("load", function onLoad() {
   window.removeEventListener("load", onLoad);
   document.querySelector("#aboutaddons").onclick = function() {
     window.parent.UI.openInBrowser("about:addons");
   }
   document.querySelector("#close").onclick = CloseUI;
   GetAvailableAddons().then(BuildUI, (e) => {
--- a/browser/devtools/webide/content/addons.xhtml
+++ b/browser/devtools/webide/content/addons.xhtml
@@ -1,16 +1,16 @@
 <?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 [
-  <!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
+  <!ENTITY % webideDTD SYSTEM "chrome://browser/locale/devtools/webide.dtd" >
   %webideDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <meta charset="utf8"/>
     <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
     <link rel="stylesheet" href="chrome://webide/skin/addons.css" type="text/css"/>
--- a/browser/devtools/webide/content/details.xhtml
+++ b/browser/devtools/webide/content/details.xhtml
@@ -1,16 +1,16 @@
 <?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 [
-  <!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
+  <!ENTITY % webideDTD SYSTEM "chrome://browser/locale/devtools/webide.dtd" >
   %webideDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <meta charset="utf8"/>
     <link rel="stylesheet" href="chrome://webide/skin/details.css" type="text/css"/>
     <script type="application/javascript;version=1.8" src="chrome://webide/content/details.js"></script>
--- a/browser/devtools/webide/content/jar.mn
+++ b/browser/devtools/webide/content/jar.mn
@@ -15,14 +15,8 @@ webide.jar:
     content/permissionstable.js       (permissionstable.js)
     content/permissionstable.xhtml    (permissionstable.xhtml)
     content/runtimedetails.js         (runtimedetails.js)
     content/runtimedetails.xhtml      (runtimedetails.xhtml)
     content/prefs.js                  (prefs.js)
     content/prefs.xhtml               (prefs.xhtml)
     content/monitor.xhtml             (monitor.xhtml)
     content/monitor.js                (monitor.js)
-
-# Temporarily include locales in content, until we're ready
-# to localize webide
-
-    content/webide.dtd                (../locales/en-US/webide.dtd)
-    content/webide.properties         (../locales/en-US/webide.properties)
--- a/browser/devtools/webide/content/monitor.xhtml
+++ b/browser/devtools/webide/content/monitor.xhtml
@@ -1,16 +1,16 @@
 <?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 [
-  <!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
+  <!ENTITY % webideDTD SYSTEM "chrome://browser/locale/devtools/webide.dtd" >
   %webideDTD;
 ]>
 
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <meta charset="utf8"/>
     <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
--- a/browser/devtools/webide/content/newapp.xul
+++ b/browser/devtools/webide/content/newapp.xul
@@ -1,16 +1,16 @@
 <?xml version="1.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/. -->
 
 <!DOCTYPE window [
-  <!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
+  <!ENTITY % webideDTD SYSTEM "chrome://browser/locale/devtools/webide.dtd" >
   %webideDTD;
 ]>
 
 <?xml-stylesheet href="chrome://global/skin/global.css"?>
 <?xml-stylesheet href="chrome://webide/skin/newapp.css"?>
 
 <dialog id="webide:newapp" title="&newAppWindowTitle;"
   width="600" height="400"
--- a/browser/devtools/webide/content/permissionstable.xhtml
+++ b/browser/devtools/webide/content/permissionstable.xhtml
@@ -1,16 +1,16 @@
 <?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 [
-  <!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
+  <!ENTITY % webideDTD SYSTEM "chrome://browser/locale/devtools/webide.dtd" >
   %webideDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <meta charset="utf8"/>
     <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
     <link rel="stylesheet" href="chrome://webide/skin/permissionstable.css" type="text/css"/>
--- a/browser/devtools/webide/content/prefs.xhtml
+++ b/browser/devtools/webide/content/prefs.xhtml
@@ -1,16 +1,16 @@
 <?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 [
-  <!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
+  <!ENTITY % webideDTD SYSTEM "chrome://browser/locale/devtools/webide.dtd" >
   %webideDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <meta charset="utf8"/>
     <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
     <link rel="stylesheet" href="chrome://webide/skin/prefs.css" type="text/css"/>
--- a/browser/devtools/webide/content/runtimedetails.js
+++ b/browser/devtools/webide/content/runtimedetails.js
@@ -4,17 +4,17 @@
 
 const Cu = Components.utils;
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
 const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
 const {AppManager} = require("devtools/webide/app-manager");
 const {Connection} = require("devtools/client/connection-manager");
 const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
 const {USBRuntime} = require("devtools/webide/runtimes");
-const Strings = Services.strings.createBundle("chrome://webide/content/webide.properties");
+const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
 
 window.addEventListener("load", function onLoad() {
   window.removeEventListener("load", onLoad);
   document.querySelector("#close").onclick = CloseUI;
   document.querySelector("#certified-check button").onclick = EnableCertApps;
   document.querySelector("#adb-check button").onclick = RootADB;
   AppManager.on("app-manager-update", OnAppManagerUpdate);
   BuildUI();
--- a/browser/devtools/webide/content/runtimedetails.xhtml
+++ b/browser/devtools/webide/content/runtimedetails.xhtml
@@ -1,16 +1,16 @@
 <?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 [
-  <!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
+  <!ENTITY % webideDTD SYSTEM "chrome://browser/locale/devtools/webide.dtd" >
   %webideDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <meta charset="utf8"/>
     <link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
     <link rel="stylesheet" href="chrome://webide/skin/runtimedetails.css" type="text/css"/>
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -17,17 +17,17 @@ const {AppProjects} = require("devtools/
 const {Connection} = require("devtools/client/connection-manager");
 const {AppManager} = require("devtools/webide/app-manager");
 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 const ProjectEditor = require("projecteditor/projecteditor");
 const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
 const {GetAvailableAddons} = require("devtools/webide/addons");
 const {GetTemplatesJSON, GetAddonsJSON} = require("devtools/webide/remote-resources");
 
-const Strings = Services.strings.createBundle("chrome://webide/content/webide.properties");
+const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
 
 const HTML = "http://www.w3.org/1999/xhtml";
 const HELP_URL = "https://developer.mozilla.org/docs/Tools/WebIDE/Troubleshooting";
 
 // download template index early
 GetTemplatesJSON(true);
 
 // See bug 989619
--- a/browser/devtools/webide/content/webide.xul
+++ b/browser/devtools/webide/content/webide.xul
@@ -1,16 +1,16 @@
 <?xml version="1.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/. -->
 
 <!DOCTYPE window [
-  <!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
+  <!ENTITY % webideDTD SYSTEM "chrome://browser/locale/devtools/webide.dtd" >
   %webideDTD;
 ]>
 
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 
 <?xml-stylesheet href="chrome://global/skin/global.css"?>
 <?xml-stylesheet href="chrome://webide/skin/webide.css"?>
 
deleted file mode 100644
--- a/browser/devtools/webide/locales/en-US/webide.dtd
+++ /dev/null
@@ -1,138 +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/. -->
-
-<!ENTITY windowTitle "Firefox WebIDE">
-
-<!ENTITY projectMenu_label "Project">
-<!ENTITY projectMenu_accesskey "P">
-<!ENTITY projectMenu_newApp_label "New App…">
-<!ENTITY projectMenu_newApp_accesskey "N">
-<!ENTITY projectMenu_importPackagedApp_label "Open Packaged App…">
-<!ENTITY projectMenu_importPackagedApp_accesskey "P">
-<!ENTITY projectMenu_importHostedApp_label "Open Hosted App…">
-<!ENTITY projectMenu_importHostedApp_accesskey "H">
-<!ENTITY projectMenu_selectApp_label "Open App…">
-<!ENTITY projectMenu_selectApp_accessley "S">
-<!ENTITY projectMenu_play_label "Install and Run">
-<!ENTITY projectMenu_play_accesskey "I">
-<!ENTITY projectMenu_stop_label "Stop App">
-<!ENTITY projectMenu_stop_accesskey "S">
-<!ENTITY projectMenu_debug_label "Debug App">
-<!ENTITY projectMenu_debug_accesskey "D">
-<!ENTITY projectMenu_remove_label "Remove Project">
-<!ENTITY projectMenu_remove_accesskey "R">
-<!ENTITY projectMenu_showPrefs_label "Preferences">
-<!ENTITY projectMenu_showPrefs_accesskey "e">
-
-<!ENTITY runtimeMenu_label "Runtime">
-<!ENTITY runtimeMenu_accesskey "R">
-<!ENTITY runtimeMenu_disconnect_label "Disconnect">
-<!ENTITY runtimeMenu_disconnect_accesskey "D">
-<!ENTITY runtimeMenu_showPermissionTable_label "Permissions Table">
-<!ENTITY runtimeMenu_showPermissionTable_accesskey "P">
-<!ENTITY runtimeMenu_takeScreenshot_label "Screenshot">
-<!ENTITY runtimeMenu_takeScreenshot_accesskey "S">
-<!ENTITY runtimeMenu_showDetails_label "Runtime Info">
-<!ENTITY runtimeMenu_showDetails_accesskey "E">
-<!ENTITY runtimeMenu_showMonitor_label "Monitor">
-<!ENTITY runtimeMenu_showMonitor_accesskey "M">
-
-<!ENTITY viewMenu_label "View">
-<!ENTITY viewMenu_accesskey "V">
-<!ENTITY viewMenu_toggleEditor_label "Toggle Editor">
-<!ENTITY viewMenu_toggleEditor_accesskey "E">
-<!ENTITY viewMenu_showAddons_label "Manage Simulators">
-<!ENTITY viewMenu_showAddons_accesskey "M">
-
-<!ENTITY projectButton_label "Open App">
-<!ENTITY runtimeButton_label "Select Runtime">
-
-<!-- We try to repicate Firefox' bindings: -->
-<!-- quit app -->
-<!ENTITY key_quit "W">
-<!-- open menu -->
-<!ENTITY key_showProjectPanel "O">
-<!-- reload app -->
-<!ENTITY key_play "R">
-<!-- show toolbox -->
-<!ENTITY key_toggleToolbox "VK_F12">
-<!-- toggle sidebar -->
-<!ENTITY key_toggleEditor "B">
-
-<!ENTITY projectPanel_myProjects "My Projects">
-<!ENTITY projectPanel_runtimeApps "Runtime Apps">
-<!ENTITY runtimePanel_USBDevices "USB Devices">
-<!ENTITY runtimePanel_WiFiDevices "WiFi Devices">
-<!ENTITY runtimePanel_simulators "Simulators">
-<!ENTITY runtimePanel_custom "Custom">
-<!ENTITY runtimePanel_installsimulator "Install Simulator">
-<!ENTITY runtimePanel_noadbhelper "Install ADB Helper">
-<!ENTITY runtimePanel_nousbdevice "Can't see your device?">
-
-<!-- Lense -->
-<!ENTITY details_valid_header "valid">
-<!ENTITY details_warning_header "warnings">
-<!ENTITY details_error_header "errors">
-<!ENTITY details_description "Description">
-<!ENTITY details_location "Location">
-<!ENTITY details_manifestURL "App ID">
-<!ENTITY details_removeProject_button "Remove Project">
-
-<!-- New App -->
-<!ENTITY newAppWindowTitle "New App">
-<!ENTITY newAppHeader "Select template">
-<!ENTITY newAppLoadingTemplate "Loading templates…">
-<!ENTITY newAppProjectName "Project Name:">
-
-
-<!-- Decks -->
-
-<!ENTITY deck_close "Close">
-
-<!-- Addons -->
-<!ENTITY addons_title "Extra Components">
-<!ENTITY addons_aboutaddons "Open Add-ons Manager">
-
-<!-- Prefs -->
-<!ENTITY prefs_title "Preferences">
-<!ENTITY prefs_editor_title "Editor">
-<!ENTITY prefs_general_title "General">
-<!ENTITY prefs_restore "Restore Defaults">
-<!ENTITY prefs_simulators "Manage Simulators">
-<!ENTITY prefs_options_enablelocalruntime "Enable local runtime">
-<!ENTITY prefs_options_enablelocalruntime_tooltip "Allow WebIDE to connect to its own runtime (running browser instance)">
-<!ENTITY prefs_options_rememberlastproject "Remember last project">
-<!ENTITY prefs_options_rememberlastproject_tooltip "Restore previous project when WebIDE starts">
-<!ENTITY prefs_options_templatesurl "Templates URL">
-<!ENTITY prefs_options_templatesurl_tooltip "Index of available templates">
-<!ENTITY prefs_options_showeditor "Show editor">
-<!ENTITY prefs_options_showeditor_tooltip "Show internal editor">
-<!ENTITY prefs_options_tabsize "Tab size">
-<!ENTITY prefs_options_expandtab "Soft tabs">
-<!ENTITY prefs_options_expandtab_tooltip "Use spaces instead of the tab character">
-<!ENTITY prefs_options_detectindentation "Autoindent">
-<!ENTITY prefs_options_detectindentation_tooltip "Guess indentation based on source content">
-<!ENTITY prefs_options_autocomplete "Autocomplete">
-<!ENTITY prefs_options_autocomplete_tooltip "Enable code autocompletion">
-<!ENTITY prefs_options_autoclosebrackets "Autoclose brackets">
-<!ENTITY prefs_options_autoclosebrackets_tooltip "Automatically insert closing brackets">
-<!ENTITY prefs_options_keybindings "Keybindings">
-<!ENTITY prefs_options_keybindings_default "Default">
-
-<!-- Permissions Table -->
-<!ENTITY permissionstable_title "Permissions Table">
-<!ENTITY permissionstable_name_header "Name">
-
-<!-- Runtime Details -->
-<!ENTITY runtimedetails_title "Runtime Info">
-<!ENTITY runtimedetails_adbIsRoot "ADB is root: ">
-<!ENTITY runtimedetails_summonADBRoot "root device">
-<!ENTITY runtimedetails_ADBRootWarning "(requires unlocked bootloader)">
-<!ENTITY runtimedetails_restrictedPrivileges "DevTools restricted privileges: ">
-<!ENTITY runtimedetails_requestPrivileges "request higher privileges">
-<!ENTITY runtimedetails_privilegesWarning "(Will reboot device. Requires root access.)">
-
-<!-- Monitor -->
-<!ENTITY monitor_title "Monitor">
-<!ENTITY monitor_help "Help">
deleted file mode 100644
--- a/browser/devtools/webide/locales/en-US/webide.properties
+++ /dev/null
@@ -1,50 +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/.
-
-title_noApp=Firefox WebIDE
-title_app=Firefox WebIDE: %S
-
-runtimeButton_label=Select Runtime
-projectButton_label=Open App
-
-mainProcess_label=Main Process
-
-local_runtime=Local Runtime
-remote_runtime=Remote Runtime
-remote_runtime_promptTitle=Remote Runtime
-remote_runtime_promptMessage=hostname:port
-
-importPackagedApp_title=Select Directory
-importHostedApp_title=Open Hosted App
-importHostedApp_header=Enter Manifest URL
-
-notification_showTroubleShooting_label=troubleshooting
-notification_showTroubleShooting_accesskey=t
-
-error_operationTimeout=Operation timed out: %1$S
-error_operationFail=Operation failed: %1$S
-error_listRunningApps=Can't get app list from device
-error_cantConnectToApp=Can't connect to app: %1$S
-error_cantInstallNotFullyConnected=Can't install project. Not fully connected.
-error_cantInstallValidationErrors=Can't install project. Validation errors.
-error_cantFetchAddonsJSON=Can't fetch the add-on list: %S
-
-addons_stable=stable
-addons_unstable=unstable
-addons_simulator_label=Firefox OS %1$S Simulator (%2$S)
-addons_install_button=install
-addons_uninstall_button=uninstall
-addons_adb_label=ADB Helper Add-on
-addons_adb_warning=USB devices won't be detected without this add-on
-addons_status_unknown=?
-addons_status_installed=Installed
-addons_status_uninstalled=Not Installed
-addons_status_preparing=preparing
-addons_status_downloading=downloading
-addons_status_installing=installing
-
-runtimedetails_checkno=no
-runtimedetails_checkyes=yes
-runtimedetails_checkunkown=unknown
-runtimedetails_notUSBDevice=Not a USB device
--- a/browser/devtools/webide/modules/app-manager.js
+++ b/browser/devtools/webide/modules/app-manager.js
@@ -21,17 +21,17 @@ const AppActorFront = require("devtools/
 const {getDeviceFront} = require("devtools/server/actors/device");
 const {getPreferenceFront} = require("devtools/server/actors/preference");
 const {setTimeout} = require("sdk/timers");
 const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
 const {USBRuntime, WiFiRuntime, SimulatorRuntime,
        gLocalRuntime, gRemoteRuntime} = require("devtools/webide/runtimes");
 const discovery = require("devtools/toolkit/discovery/discovery");
 
-const Strings = Services.strings.createBundle("chrome://webide/content/webide.properties");
+const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
 
 const WIFI_SCANNING_PREF = "devtools.remote.wifi.scan";
 
 exports.AppManager = AppManager = {
 
   // FIXME: will break when devtools/app-manager will be removed:
   DEFAULT_PROJECT_ICON: "chrome://browser/skin/devtools/app-manager/default-app-icon.png",
   DEFAULT_PROJECT_NAME: "--",
--- a/browser/devtools/webide/modules/runtimes.js
+++ b/browser/devtools/webide/modules/runtimes.js
@@ -6,17 +6,17 @@ const {Cu} = require("chrome");
 const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
 const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
 const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
 const {DebuggerServer} = require("resource://gre/modules/devtools/dbg-server.jsm");
 const discovery = require("devtools/toolkit/discovery/discovery");
 const promise = require("promise");
 
-const Strings = Services.strings.createBundle("chrome://webide/content/webide.properties");
+const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
 
 function USBRuntime(id) {
   this.id = id;
 }
 
 USBRuntime.prototype = {
   connect: function(connection) {
     let device = Devices.getByName(this.id);
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/webide.dtd
@@ -0,0 +1,138 @@
+<!-- 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/. -->
+
+<!ENTITY windowTitle "Firefox WebIDE">
+
+<!ENTITY projectMenu_label "Project">
+<!ENTITY projectMenu_accesskey "P">
+<!ENTITY projectMenu_newApp_label "New App…">
+<!ENTITY projectMenu_newApp_accesskey "N">
+<!ENTITY projectMenu_importPackagedApp_label "Open Packaged App…">
+<!ENTITY projectMenu_importPackagedApp_accesskey "P">
+<!ENTITY projectMenu_importHostedApp_label "Open Hosted App…">
+<!ENTITY projectMenu_importHostedApp_accesskey "H">
+<!ENTITY projectMenu_selectApp_label "Open App…">
+<!ENTITY projectMenu_selectApp_accessley "S">
+<!ENTITY projectMenu_play_label "Install and Run">
+<!ENTITY projectMenu_play_accesskey "I">
+<!ENTITY projectMenu_stop_label "Stop App">
+<!ENTITY projectMenu_stop_accesskey "S">
+<!ENTITY projectMenu_debug_label "Debug App">
+<!ENTITY projectMenu_debug_accesskey "D">
+<!ENTITY projectMenu_remove_label "Remove Project">
+<!ENTITY projectMenu_remove_accesskey "R">
+<!ENTITY projectMenu_showPrefs_label "Preferences">
+<!ENTITY projectMenu_showPrefs_accesskey "e">
+
+<!ENTITY runtimeMenu_label "Runtime">
+<!ENTITY runtimeMenu_accesskey "R">
+<!ENTITY runtimeMenu_disconnect_label "Disconnect">
+<!ENTITY runtimeMenu_disconnect_accesskey "D">
+<!ENTITY runtimeMenu_showPermissionTable_label "Permissions Table">
+<!ENTITY runtimeMenu_showPermissionTable_accesskey "P">
+<!ENTITY runtimeMenu_takeScreenshot_label "Screenshot">
+<!ENTITY runtimeMenu_takeScreenshot_accesskey "S">
+<!ENTITY runtimeMenu_showDetails_label "Runtime Info">
+<!ENTITY runtimeMenu_showDetails_accesskey "E">
+<!ENTITY runtimeMenu_showMonitor_label "Monitor">
+<!ENTITY runtimeMenu_showMonitor_accesskey "M">
+
+<!ENTITY viewMenu_label "View">
+<!ENTITY viewMenu_accesskey "V">
+<!ENTITY viewMenu_toggleEditor_label "Toggle Editor">
+<!ENTITY viewMenu_toggleEditor_accesskey "E">
+<!ENTITY viewMenu_showAddons_label "Manage Simulators">
+<!ENTITY viewMenu_showAddons_accesskey "M">
+
+<!ENTITY projectButton_label "Open App">
+<!ENTITY runtimeButton_label "Select Runtime">
+
+<!-- We try to repicate Firefox' bindings: -->
+<!-- quit app -->
+<!ENTITY key_quit "W">
+<!-- open menu -->
+<!ENTITY key_showProjectPanel "O">
+<!-- reload app -->
+<!ENTITY key_play "R">
+<!-- show toolbox -->
+<!ENTITY key_toggleToolbox "VK_F12">
+<!-- toggle sidebar -->
+<!ENTITY key_toggleEditor "B">
+
+<!ENTITY projectPanel_myProjects "My Projects">
+<!ENTITY projectPanel_runtimeApps "Runtime Apps">
+<!ENTITY runtimePanel_USBDevices "USB Devices">
+<!ENTITY runtimePanel_WiFiDevices "Wi-Fi Devices">
+<!ENTITY runtimePanel_simulators "Simulators">
+<!ENTITY runtimePanel_custom "Custom">
+<!ENTITY runtimePanel_installsimulator "Install Simulator">
+<!ENTITY runtimePanel_noadbhelper "Install ADB Helper">
+<!ENTITY runtimePanel_nousbdevice "Can't see your device?">
+
+<!-- Lense -->
+<!ENTITY details_valid_header "valid">
+<!ENTITY details_warning_header "warnings">
+<!ENTITY details_error_header "errors">
+<!ENTITY details_description "Description">
+<!ENTITY details_location "Location">
+<!ENTITY details_manifestURL "App ID">
+<!ENTITY details_removeProject_button "Remove Project">
+
+<!-- New App -->
+<!ENTITY newAppWindowTitle "New App">
+<!ENTITY newAppHeader "Select template">
+<!ENTITY newAppLoadingTemplate "Loading templates…">
+<!ENTITY newAppProjectName "Project Name:">
+
+
+<!-- Decks -->
+
+<!ENTITY deck_close "Close">
+
+<!-- Addons -->
+<!ENTITY addons_title "Extra Components">
+<!ENTITY addons_aboutaddons "Open Add-ons Manager">
+
+<!-- Prefs -->
+<!ENTITY prefs_title "Preferences">
+<!ENTITY prefs_editor_title "Editor">
+<!ENTITY prefs_general_title "General">
+<!ENTITY prefs_restore "Restore Defaults">
+<!ENTITY prefs_simulators "Manage Simulators">
+<!ENTITY prefs_options_enablelocalruntime "Enable local runtime">
+<!ENTITY prefs_options_enablelocalruntime_tooltip "Allow WebIDE to connect to its own runtime (running browser instance)">
+<!ENTITY prefs_options_rememberlastproject "Remember last project">
+<!ENTITY prefs_options_rememberlastproject_tooltip "Restore previous project when WebIDE starts">
+<!ENTITY prefs_options_templatesurl "Templates URL">
+<!ENTITY prefs_options_templatesurl_tooltip "Index of available templates">
+<!ENTITY prefs_options_showeditor "Show editor">
+<!ENTITY prefs_options_showeditor_tooltip "Show internal editor">
+<!ENTITY prefs_options_tabsize "Tab size">
+<!ENTITY prefs_options_expandtab "Soft tabs">
+<!ENTITY prefs_options_expandtab_tooltip "Use spaces instead of the tab character">
+<!ENTITY prefs_options_detectindentation "Autoindent">
+<!ENTITY prefs_options_detectindentation_tooltip "Guess indentation based on source content">
+<!ENTITY prefs_options_autocomplete "Autocomplete">
+<!ENTITY prefs_options_autocomplete_tooltip "Enable code autocompletion">
+<!ENTITY prefs_options_autoclosebrackets "Autoclose brackets">
+<!ENTITY prefs_options_autoclosebrackets_tooltip "Automatically insert closing brackets">
+<!ENTITY prefs_options_keybindings "Keybindings">
+<!ENTITY prefs_options_keybindings_default "Default">
+
+<!-- Permissions Table -->
+<!ENTITY permissionstable_title "Permissions Table">
+<!ENTITY permissionstable_name_header "Name">
+
+<!-- Runtime Details -->
+<!ENTITY runtimedetails_title "Runtime Info">
+<!ENTITY runtimedetails_adbIsRoot "ADB is root: ">
+<!ENTITY runtimedetails_summonADBRoot "root device">
+<!ENTITY runtimedetails_ADBRootWarning "(requires unlocked bootloader)">
+<!ENTITY runtimedetails_restrictedPrivileges "DevTools restricted privileges: ">
+<!ENTITY runtimedetails_requestPrivileges "request higher privileges">
+<!ENTITY runtimedetails_privilegesWarning "(Will reboot device. Requires root access.)">
+
+<!-- Monitor -->
+<!ENTITY monitor_title "Monitor">
+<!ENTITY monitor_help "Help">
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/webide.properties
@@ -0,0 +1,58 @@
+# 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/.
+
+title_noApp=Firefox WebIDE
+title_app=Firefox WebIDE: %S
+
+runtimeButton_label=Select Runtime
+projectButton_label=Open App
+
+mainProcess_label=Main Process
+
+local_runtime=Local Runtime
+remote_runtime=Remote Runtime
+remote_runtime_promptTitle=Remote Runtime
+remote_runtime_promptMessage=hostname:port
+
+importPackagedApp_title=Select Directory
+importHostedApp_title=Open Hosted App
+importHostedApp_header=Enter Manifest URL
+
+notification_showTroubleShooting_label=troubleshooting
+notification_showTroubleShooting_accesskey=t
+
+# These messages appear in a notification box when an error occur.
+
+error_cantInstallNotFullyConnected=Can't install project. Not fully connected.
+error_cantInstallValidationErrors=Can't install project. Validation errors.
+error_listRunningApps=Can't get app list from device
+
+# Variable: name of the operation (in english)
+error_operationTimeout=Operation timed out: %1$S
+error_operationFail=Operation failed: %1$S
+
+# Variable: app name
+error_cantConnectToApp=Can't connect to app: %1$S
+
+# Variable: error message (in english)
+error_cantFetchAddonsJSON=Can't fetch the add-on list: %S
+
+addons_stable=stable
+addons_unstable=unstable
+addons_simulator_label=Firefox OS %1$S Simulator (%2$S)
+addons_install_button=install
+addons_uninstall_button=uninstall
+addons_adb_label=ADB Helper Add-on
+addons_adb_warning=USB devices won't be detected without this add-on
+addons_status_unknown=?
+addons_status_installed=Installed
+addons_status_uninstalled=Not Installed
+addons_status_preparing=preparing
+addons_status_downloading=downloading
+addons_status_installing=installing
+
+runtimedetails_checkno=no
+runtimedetails_checkyes=yes
+runtimedetails_checkunkown=unknown
+runtimedetails_notUSBDevice=Not a USB device
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -59,16 +59,18 @@
     locale/browser/devtools/inspector.dtd          (%chrome/browser/devtools/inspector.dtd)
     locale/browser/devtools/projecteditor.properties     (%chrome/browser/devtools/projecteditor.properties)
     locale/browser/devtools/eyedropper.properties     (%chrome/browser/devtools/eyedropper.properties)
     locale/browser/devtools/connection-screen.dtd  (%chrome/browser/devtools/connection-screen.dtd)
     locale/browser/devtools/connection-screen.properties (%chrome/browser/devtools/connection-screen.properties)
     locale/browser/devtools/font-inspector.dtd     (%chrome/browser/devtools/font-inspector.dtd)
     locale/browser/devtools/app-manager.dtd        (%chrome/browser/devtools/app-manager.dtd)
     locale/browser/devtools/app-manager.properties (%chrome/browser/devtools/app-manager.properties)
+    locale/browser/devtools/webide.dtd             (%chrome/browser/devtools/webide.dtd)
+    locale/browser/devtools/webide.properties      (%chrome/browser/devtools/webide.properties)
     locale/browser/loop/loop.properties            (%chrome/browser/loop/loop.properties)
     locale/browser/newTab.dtd                      (%chrome/browser/newTab.dtd)
     locale/browser/newTab.properties               (%chrome/browser/newTab.properties)
     locale/browser/pageInfo.dtd                    (%chrome/browser/pageInfo.dtd)
     locale/browser/pageInfo.properties             (%chrome/browser/pageInfo.properties)
     locale/browser/quitDialog.properties           (%chrome/browser/quitDialog.properties)
     locale/browser/safeMode.dtd                    (%chrome/browser/safeMode.dtd)
     locale/browser/sanitize.dtd                    (%chrome/browser/sanitize.dtd)
index dc0329419f3d0f9d9cd19fb4de36f73dc9471258..87e54cd7f80bfb1c4dbbca0f1d62defc1889cc32
GIT binary patch
literal 3995
zc$@*74`lF(P)<h;3K|Lk000e1NJLTq004df000sQ1^@s6ofT@S000kSNkl<ZcmeI1
zd301&g2uml@4IiSy^=~o7M75OKtK|dMIi(<ZVauq*g8$C=x8G@ZL7FoOOG;W_Z+od
zPLDm!%-GJHo|chu5a?qgEu?Lw+ocg@F>D%^V5kI=RHe4JPfaBjAVpGxf6n~QsZ%+1
zZvE=3`@MVbE5QHre-nZnl#Z#JB?xRW7o|CZz(-I*G|RU48iu~XvaA;YbYmDy!w?A}
zw+Niyz&USVl=(mqu`GMLWg4r~o?ijbkq3c9Y?e&vVjp93T#RP&7`C&w-?BDnj+0NG
z3`c|3EGCh~vPYOBDs%*)&@98*tH<pPrf%o$VJ4j=%5<^Y$LGi{mi7?MOnZ|3<_2A}
z^3QX~KZ`JFF%vy=C|5^-P|dN8y|y0T;8=Rz9*sM*h^Q<kYG4kL+#{T{2bQtdNk%rn
zNagepf5Uuyh1z#OrIfC6x!emqULV|EFBC-ur4+VpLrW&n-ye;~<Nb>P{5#G<UvKYZ
z&iU(#>Z()}#c-*v9kML<TDDbSnC3*&v;-}gIG9K#z71eI&VqEjOBD%ORVs=LD-{Xl
zq6ojhAQC$8Ii`trO^<(Wm_z3IM2)LVAgesZd|{wMLZD27D=0t^Dbj@_(PyIfh#rp|
zv4-@-!<s6HWL2nCSy)=>Ldj?a{vsKoL||w(BFA-n*`A0W`&=8+vtt`q2^CgxSII)r
zQvu0a0YNDMVItCn!%X!+?>`c^V~2<IM6}~7Vd5%MFmfR+ssSsi2Jw~vT!v$&fM^%&
zu03(qz5fgkDV}>90D!e`<%|iNe1Sl7C{zTWKLAOVAqYHEyyG~qZ40SX3cWoipeGV{
z0{8{KM*G1p8YD@2FMZ+gva-@&{OG|S{@X<}rbYp10L-`EZVkM&>Xkd9vG^UDmNF!P
z%>nQMzDCdPom{4LQ>`L5H@aOID@&jP>yG(+uUih_jYJj%2q{e24t6CH*u{jLc{b^t
zT#)Fd!ZC7l=>!!8V`Qj)0ckmyK;SrlW!OkYEQI$Z5IvCFb7a?XF3EILMXjf~c8VVr
zHEwtdM92yQ<C*!%Ttv|x17GZn;P6f>r>FII2~L^3N%B`VtD$<NpCQNrP(cQv0+5+!
z16HaR$)0@>llyY#IrbSRqPmHdPH1M8Gmw4;@f3lGE`V{su`?GD`nzEs`54~Lk52QD
zss)b%08-o6tw@)>d_-9}f(4;W>4XsE!Z37nhr?!Rac~-dk1@#GZ*P%BQEc=20;RK?
zF8cn9Pp|wFfF1z70BoFuE?9W$)hGJ;SKFr9^{sg=697bT3cq>!*%j&1mp6GmsCTJw
zQ;H*m?9?x~;IWP`I}_TTJx@2v(&P~$N78oiVJvPQudY5dPljLZ-MXT%QeHmh0uPF7
z6evCh$_UQD0knjTqdVfJKVEZ&=e^&5uwrDbd->$qg{Z9e!W$HGdK?Ex#Z0t)*k@W@
z<!5+a`R6?=<iObF?$T+HgX6$t9|&Q{705OcNOo;E>x!dic(%O1Wd$pnxSZFufQ_00
z>Mj7NfLwu`R6nc(@0yM5i>!ytd*DTUvi2W?imG;$Rg6-KN0er^_e|+SkV+;H={>Rj
z&DVc@-B)LIMOFPh>6QLsO-=QaZ>)ahUjZBikic1(KKt@VWS9FVLBIEj|N70#PYg^y
zFuA-kSS<36xqhEA!{bH~7hpIJ_Bq&TPMx;&Pal2MYlORgIoIoM7^f%@DZz2m#5=aL
ze%U))vUx@~R0iFJykqPPuQGD78}0%Dgc1xzyw}9BzhM1;tT^czH?<}hES5T^TpUvB
zX9VFdl0b*?^c~l*`wyx0&p)ss>j_oP2nuSb!&5#}ag{WJD}?}|7z#7l1Jd`w`fY0;
z&Uyk9<^-v~v_qOWPvNz*LA@maWf%%O8iu<6ckA1Jz9{1ne*4TlsCeU-^IUF^;tvF&
zs48eiGBlE+g2nn<cDL;<25<xbIOo@^E_dJ0pMC080LL?>4?$OV*Uv(PJRB8-`2d~=
z&<6mFka?w|sMN?Z3b+792rK~9FxCD@pSv&8Z*tpmS<b-9ARq`ZhEZCw<>1a;**uhy
zdA=f135{0Z4sb9=FdQzQ0A)n7Wl#I*p29Lksi^V5S0I5gisA4EBovNLwH!F~S=Ix}
z=85hSMGjPgs|A^@eK-Wg4=rBS@@d<ltcRpMLa;&+O6$NpArLlfUzr^t=^xwj$^N4m
zkC6TYjInyv<;s*U2psu9DTSh_%BIcl)ZKUY_d5VMTofmHeV+Hq%Suz35{Kc7&py`2
zPq}D|tSFZO@O<?iPDp(b7ZBhKoRI9#4^D|7Fh)g@bDpF!C&=zh0wE}1RQdF`Z`NJ4
z?4e8^CXo8{iig`TfODFW3<nhmTz;l(+4g4LoeKs$T%`4ZVkvW`3lam#!y||U-a=7%
zf7hnE@6G#R#zQHu7nBgV90X+|@(Dzk2r37aH{RY+_qX@V&v=L+)-!(zxVHd|^L#V_
z72tS_l>hwmzPgn+&FK&(SC@e?7La5GoQs)4^F`ns{qeXD00*F_Jswq2`v4>XILHIj
zFv6B;xd2EQM4}|%X95&TK`{Uu7@#J5dJACal1hn7A_Vl5>Enc=KN`vA5kwkLy#geK
zWmbE>h$2Hmk7hkwq5-#$Lsl6mL*57?Maqn2J%mXCF8jbl6_g6d7bZxE#-b-ZTnY$k
zAnjK&M^3&tTtp;gWjq2hkT4|_j0vEW<u7u8Q5EIK0nh;r?Y1nd1b~&l1SJ4YWF488
z5GI6x5t3celn^My3gTF}*=yM;zreh*z_MeU!4D0M9(^(o5T=wM^B^SOJa8-r<)M=v
z$4Pq_0cF`ddCiTT_LLM>oR)`B215SlL6LtT8<isqvmOE`^Sv0QU?l&t9Lqq}h(N|8
z45*#0rfC3#fU^9Gw=4^eZHK>m-PK=yRkUqeTQyz3><{n$*#{tuVYu;@+eNp>+boLw
z{ZlU%+riejZNYL*u4dvm03lJ?=RV@IZNnRn$7MqNl#t9A-F6TI!t-yrIh)6^vDHY}
zAojo{<%6l)uu?R9ZSzg1c$}@NxCPrn-Yd+|Z0K<(eEB8c&3XWPt8F9z;(+A2z#K~l
zagyQNzccrwCq2*5VqlI5Mv<S#(7}voc*zwF8P7oRw%wXcBsvUTD|fry$Ok=@g2dTc
z&ba}=007H0Up7ta>VNp<%S+b$`kx*EU=MSjaIYjufpIm}YXKxNh~YS`$8@73Vw&Y4
zE`R_7E1J-Vr+nmg!8({q#i9|@ar_v#AP}=G^m)B&1yRiAu}!BncHHRDVph4YPz1>E
ziz}J5L1NyuTsY}54X5=)r`8dV8s&jv8H5bK+LLh;sb1Gwf$^-zvCUR9(bZw={pHkE
zgnR-SS^|C^T`O{y^$^Qwb^1CwNTR1)@E0MUK&BQ!$Ox|$g#k~d`1U=YX!TQOE{*i{
zuJ-u+5JeF~VHi4+iFo+#+rIZN0HPRV|IUv#Ol`VsOC%=WwP^9Z8v(q9v(Vbwda3Gi
zKk4y!T3>qZC-0p4JN|>i2ef(B6PE5zYOBjc5iT~crMn!f_~?!ucbU<CQ>C;RgC~5_
za?oK~;aLwpa9W;y@9)>9Tv5OD_`%fbV2K1tWkZ&(YZm%Dt?<nk-*=j4#~<6Yrf)Ya
zJ+d#hI#i)TaS20~t|v`&AJN0}r`>&;C%kuyHmc#erK!I5)sm+iOmq!dx@{z3B|F29
zF23+IPuKR1TE*-eml~aWR!fCxkL($;bX$*u^c)N?zj{*ELmbD!S2SIA^_np?bqoA~
zASk6d91aX!M>NtGJ%8f3MfWbb^-}-`PhHuylcxtu%PNkR6o+1T_2riyA5+&L1L!;D
zhzO?L_p2_~BVMns?a9X<z2)NBGmio2z}NV}dFQYBmd~@GQkF8s8@7!TripIDgg^+2
zIR~Qzhjk+=U2^fFmLETMYMv}tE}6DwQj32<Xrz4Be{|8}7J53gXsvI~qT8-qmeaHF
zq1kI@UQ@K7tjZ0R%VRr8#w>ImOhilQg^Rw^bbn4y-6apL@m2oS0@*(%_m=P2u#7nL
z=;7$44SdmxyP9))YW{k~8qc)r7DywiL8y@1lZwIU`aF6?xv^;F4O6q8(?&-lk;+se
z4$I?(ojE&CxpA^g=$eL9GQMr@%xTMRS$JJLfTLdzW9a(mR5Hnefr1mWF1zNfbh%Bo
z<Lq&4`-}7=L>X%kC7F7>-dCUh=@ZXPo;0BsKqqq0nMhQ2t1^nYki9u`QwC8a&>aU6
z(?nQc+sYPPyKLqU?#`7bLvJ`y8S8Pu7ZO1Re*zMkJ<~H~#lmFUv@zE#yJ60fvwFHa
zW0k#KJ_JkTfwP`q;6ic+&h&WHKu?FgZG6!c%jQk{o3nZn(Qu`jJOR;N3_>UdLeaq+
zOAV&p51Q)Sc71d4viokFepXNF#L-Gu<QRy*c<=%uAe6#^1IN%{C8D5_!`l{4^e?-A
z{?yYv+2Tjno;N*E5Ngyl9sQBsHn&UJ7>^~YEZYeYN>YrVy{dB5<{v%q#7BeYaX8oi
zt`U&Mlres&<LFbW>RMM23_fF7<|JJ=gMf3)?RGueGPh~-9k<?iXmES)&h@1lE5=T*
zlI6xe!@@q(ZZiuDHYTI7DrOoXN?9r;i|vKu$8Wy=*}oq$Pxbj#(~Cx{jj5=Ko_4cM
za)KM<$!L{Btq>(7<>*rT=;Cpk|L)eOhV)Fhuy%TRwWl#2G10MCYh#9QV<OpKMFcBE
zKvR|`w3h|PZ+>w8kB9UW*ECI60%IF3Jqok`KwF{9+!&3gs!WT8C?zS0YVA|T1~)(V
z@Kr;5Ce59$l-4vliC#FzcDEI9eZWJ65G6=ScB;L;%)j}$yP9))GR2RoJ&y{4u#$`X
zwe#w0*FFBoeSZO<0q6%H4vupM-x$k?axi+_<SDYOT;%t8pT7O(8`fQQ<)vMN<8=TM
z01Cj$xU;XHNkhe0DigAjbKx~#)68|({_N><%2PF^igJmr6w{vC!VA|uc+*eM$uoXN
zEfr;!$y4q+f8CPHSDcflxVni_K}vhX*Je-jtb5_HdFSSt)J!Q?(tQc9%^t<qy>REO
z|FtK3##c|8O84*huynAPzRcirLk3VX7$O3|#5pi)R5g9~(Ee|nr@m<%-Sx*k-`F#y
zWdhyy``zEzQ##>F`sMC7zp<xm>U?_i!wu)``ELn?$#42DcFX_(002ovPDHLkV1oBp
BmDm6P
--- a/content/media/RtspMediaResource.h
+++ b/content/media/RtspMediaResource.h
@@ -217,17 +217,21 @@ public:
   private:
     nsRefPtr<RtspMediaResource> mResource;
   };
   friend class Listener;
 
 protected:
   // Main thread access only.
   // These are called on the main thread by Listener.
-  NS_DECL_NSISTREAMINGPROTOCOLLISTENER
+  nsresult OnMediaDataAvailable(uint8_t aIndex, const nsACString& aData,
+                                uint32_t aLength, uint32_t aOffset,
+                                nsIStreamingProtocolMetaData* aMeta);
+  nsresult OnConnected(uint8_t aIndex, nsIStreamingProtocolMetaData* aMeta);
+  nsresult OnDisconnected(uint8_t aIndex, nsresult aReason);
 
   nsRefPtr<Listener> mListener;
 
 private:
   // Notify mDecoder the rtsp stream is suspend. Main thread only.
   void NotifySuspend(bool aIsSuspend);
   bool IsVideoEnabled();
   bool IsVideo(uint8_t tracks, nsIStreamingProtocolMetaData *meta);
--- a/gfx/layers/apz/src/GestureEventListener.cpp
+++ b/gfx/layers/apz/src/GestureEventListener.cpp
@@ -49,17 +49,19 @@ float GetCurrentSpan(const MultiTouchInp
   return float(NS_hypot(delta.x, delta.y));
 }
 
 GestureEventListener::GestureEventListener(AsyncPanZoomController* aAsyncPanZoomController)
   : mAsyncPanZoomController(aAsyncPanZoomController),
     mState(GESTURE_NONE),
     mSpanChange(0.0f),
     mPreviousSpan(0.0f),
-    mLastTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0)
+    mLastTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0),
+    mLongTapTimeoutTask(nullptr),
+    mMaxTapTimeoutTask(nullptr)
 {
 }
 
 GestureEventListener::~GestureEventListener()
 {
 }
 
 nsEventStatus GestureEventListener::HandleInputEvent(const MultiTouchInput& aEvent)
@@ -378,16 +380,18 @@ nsEventStatus GestureEventListener::Hand
   }
 
   return rv;
 }
 
 nsEventStatus GestureEventListener::HandleInputTouchCancel()
 {
   SetState(GESTURE_NONE);
+  CancelMaxTapTimeoutTask();
+  CancelLongTapTimeoutTask();
   return nsEventStatus_eIgnore;
 }
 
 void GestureEventListener::HandleInputTimeoutLongTap()
 {
   GEL_LOG("Running long-tap timeout task in state %d\n", mState);
 
   mLongTapTimeoutTask = nullptr;
--- a/mobile/android/base/favicons/LoadFaviconTask.java
+++ b/mobile/android/base/favicons/LoadFaviconTask.java
@@ -135,35 +135,52 @@ public class LoadFaviconTask extends UiA
             // Was the response a failure?
             int status = response.getStatusLine().getStatusCode();
 
             // Handle HTTP status codes requesting a redirect.
             if (status >= 300 && status < 400) {
                 Header header = response.getFirstHeader("Location");
 
                 // Handle mad webservers.
-                if (header == null) {
-                    return null;
+                final String newURI;
+                try {
+                    if (header == null) {
+                        return null;
+                    }
+
+                    newURI = header.getValue();
+                    if (newURI == null || newURI.equals(faviconURI.toString())) {
+                        return null;
+                    }
+
+                    if (visited.contains(newURI)) {
+                        // Already been redirected here - abort.
+                        return null;
+                    }
+
+                    visited.add(newURI);
+                } finally {
+                    // Consume the entity before recurse or exit.
+                    try {
+                        response.getEntity().consumeContent();
+                    } catch (Exception e) {
+                        // Doesn't matter.
+                    }
                 }
 
-                String newURI = header.getValue();
-                if (newURI == null || newURI.equals(faviconURI.toString())) {
-                    return null;
-                }
-
-                if (visited.contains(newURI)) {
-                    // Already been redirected here - abort.
-                    return null;
-                }
-
-                visited.add(newURI);
                 return tryDownloadRecurse(new URI(newURI), visited);
             }
 
             if (status >= 400) {
+                // Consume the entity and exit.
+                try {
+                    response.getEntity().consumeContent();
+                } catch (Exception e) {
+                    // Doesn't matter.
+                }
                 return null;
             }
         }
         return response;
     }
 
     /**
      * Retrieve the specified favicon from the JAR, returning null if it's not
--- a/mozglue/build/Nuwa.cpp
+++ b/mozglue/build/Nuwa.cpp
@@ -135,17 +135,31 @@ struct thread_info : public mozilla::Lin
 
   // The thread specific function to recreate the new thread. It's executed
   // after the thread is recreated.
   void (*recrFunc)(void *arg);
   void *recrArg;
 
   TLSInfoList tlsInfo;
 
-  pthread_mutex_t *reacquireMutex;
+  /**
+   * We must ensure that the recreated thread has entered pthread_cond_wait() or
+   * similar functions before proceeding to recreate the next one. Otherwise, if
+   * the next thread depends on the same mutex, it may be used in an incorrect
+   * state.  To do this, the main thread must unconditionally acquire the mutex.
+   * The mutex is unconditionally released when the recreated thread enters
+   * pthread_cond_wait().  The recreated thread may have locked the mutex itself
+   * (if the pthread_mutex_trylock succeeded) or another thread may have already
+   * held the lock.  If the recreated thread did lock the mutex we must balance
+   * that with another unlock on the main thread, which is signaled by
+   * condMutexNeedsBalancing.
+   */
+  pthread_mutex_t *condMutex;
+  bool condMutexNeedsBalancing;
+
   void *stk;
 
   pid_t origNativeThreadID;
   pid_t recreatedNativeThreadID;
   char nativeThreadName[NATIVE_THREAD_NAME_LENGTH];
 };
 
 typedef struct thread_info thread_info_t;
@@ -496,17 +510,18 @@ static thread_info_t *
 thread_info_new(void) {
   /* link tinfo to sAllThreads */
   thread_info_t *tinfo = new thread_info_t();
   tinfo->flags = 0;
   tinfo->recrFunc = nullptr;
   tinfo->recrArg = nullptr;
   tinfo->recreatedThreadID = 0;
   tinfo->recreatedNativeThreadID = 0;
-  tinfo->reacquireMutex = nullptr;
+  tinfo->condMutex = nullptr;
+  tinfo->condMutexNeedsBalancing = false;
   tinfo->stk = MozTaggedAnonymousMmap(nullptr,
                                       NUWA_STACK_SIZE + getPageSize(),
                                       PROT_READ | PROT_WRITE,
                                       MAP_PRIVATE | MAP_ANONYMOUS,
                                       /* fd */ -1,
                                       /* offset */ 0,
                                       "nuwa-thread-stack");
 
@@ -1011,29 +1026,42 @@ extern "C" MFBT_API int
   THREAD_FREEZE_POINT1_VIP();
   if (freezePoint2) {
     RECREATE_CONTINUE();
     RECREATE_PASS_VIP();
     RECREATE_GATE_VIP();
     return rv;
   }
   if (recreated && mtx) {
-    if (!freezePoint1 && pthread_mutex_trylock(mtx)) {
+    if (!freezePoint1) {
+      tinfo->condMutex = mtx;
       // The thread was frozen in pthread_cond_wait() after releasing mtx in the
       // Nuwa process. In recreating this thread, We failed to reacquire mtx
       // with the pthread_mutex_trylock() call, that is, mtx was acquired by
       // another thread. Because of this, we need the main thread's help to
       // reacquire mtx so that it will be in a valid state.
-      tinfo->reacquireMutex = mtx;
+      if (!pthread_mutex_trylock(mtx)) {
+        tinfo->condMutexNeedsBalancing = true;
+      }
     }
     RECREATE_CONTINUE();
     RECREATE_PASS_VIP();
   }
   rv = REAL(pthread_cond_wait)(cond, mtx);
   if (recreated && mtx) {
+    // We have reacquired mtx. The main thread also wants to acquire mtx to
+    // synchronize with us. If the main thread didn't get a chance to acquire
+    // mtx let it do that now. The main thread clears condMutex after acquiring
+    // it to signal us.
+    if (tinfo->condMutex) {
+      // We need mtx to end up locked, so tell the main thread not to unlock
+      // mtx after it locks it.
+      tinfo->condMutexNeedsBalancing = false;
+      pthread_mutex_unlock(mtx);
+    }
     // We still need to be gated as not to acquire another mutex associated with
     // another VIP thread and interfere with it.
     RECREATE_GATE_VIP();
   }
   THREAD_FREEZE_POINT2_VIP();
 
   return rv;
 }
@@ -1047,24 +1075,31 @@ extern "C" MFBT_API int
   THREAD_FREEZE_POINT1_VIP();
   if (freezePoint2) {
     RECREATE_CONTINUE();
     RECREATE_PASS_VIP();
     RECREATE_GATE_VIP();
     return rv;
   }
   if (recreated && mtx) {
-    if (!freezePoint1 && pthread_mutex_trylock(mtx)) {
-      tinfo->reacquireMutex = mtx;
+    if (!freezePoint1) {
+      tinfo->condMutex = mtx;
+      if (!pthread_mutex_trylock(mtx)) {
+        tinfo->condMutexNeedsBalancing = true;
+      }
     }
     RECREATE_CONTINUE();
     RECREATE_PASS_VIP();
   }
   rv = REAL(pthread_cond_timedwait)(cond, mtx, abstime);
   if (recreated && mtx) {
+    if (tinfo->condMutex) {
+      tinfo->condMutexNeedsBalancing = false;
+      pthread_mutex_unlock(mtx);
+    }
     RECREATE_GATE_VIP();
   }
   THREAD_FREEZE_POINT2_VIP();
 
   return rv;
 }
 
 extern "C" int __pthread_cond_timedwait(pthread_cond_t *cond,
@@ -1082,24 +1117,31 @@ extern "C" MFBT_API int
   THREAD_FREEZE_POINT1_VIP();
   if (freezePoint2) {
     RECREATE_CONTINUE();
     RECREATE_PASS_VIP();
     RECREATE_GATE_VIP();
     return rv;
   }
   if (recreated && mtx) {
-    if (!freezePoint1 && pthread_mutex_trylock(mtx)) {
-      tinfo->reacquireMutex = mtx;
+    if (!freezePoint1) {
+      tinfo->condMutex = mtx;
+      if (!pthread_mutex_trylock(mtx)) {
+        tinfo->condMutexNeedsBalancing = true;
+      }
     }
     RECREATE_CONTINUE();
     RECREATE_PASS_VIP();
   }
   rv = REAL(__pthread_cond_timedwait)(cond, mtx, abstime, clock);
   if (recreated && mtx) {
+    if (tinfo->condMutex) {
+      tinfo->condMutexNeedsBalancing = false;
+      pthread_mutex_unlock(mtx);
+    }
     RECREATE_GATE_VIP();
   }
   THREAD_FREEZE_POINT2_VIP();
 
   return rv;
 }
 
 extern "C" MFBT_API int
@@ -1398,18 +1440,27 @@ RecreateThreads() {
   pthread_mutex_unlock(&sThreadCountLock);
 
   RECREATE_START();
   while (tinfo != nullptr) {
     if (tinfo->flags & TINFO_FLAG_NUWA_SUPPORT) {
       RECREATE_BEFORE(tinfo);
       thread_recreate(tinfo);
       RECREATE_WAIT();
-      if (tinfo->reacquireMutex) {
-        REAL(pthread_mutex_lock)(tinfo->reacquireMutex);
+      if (tinfo->condMutex) {
+        // Synchronize with the recreated thread in pthread_cond_wait().
+        REAL(pthread_mutex_lock)(tinfo->condMutex);
+        // Tell the other thread that we have successfully locked the mutex.
+        // NB: condMutex can only be touched while it is held, so we must clear
+        // it here and store the mutex locally.
+        pthread_mutex_t *mtx = tinfo->condMutex;
+        tinfo->condMutex = nullptr;
+        if (tinfo->condMutexNeedsBalancing) {
+          pthread_mutex_unlock(mtx);
+        }
       }
     } else if(!(tinfo->flags & TINFO_FLAG_NUWA_SKIP)) {
       // An unmarked thread is found other than the main thread.
 
       // All threads should be marked as one of SUPPORT or SKIP, or
       // abort the process to make sure all threads in the Nuwa
       // process are Nuwa-aware.
       abort();